Текст
                    Этот файл был взят с сайта
http://all-ebooks.com
Данный файл представлен исключительно в
ознакомительных целях. После ознакомления с
содержанием данного файла Вам следует его
незамедлительно удалить. Сохраняя данный файл
вы несете ответственность в соответствии с
законодательством.
Любое коммерческое и иное использование кроме
предварительного ознакомления запрещено.
Публикация данного документа не преследует за
собой никакой коммерческой выгоды.
Эта книга способствует профессиональному росту
читателей и является рекламой бумажных изданий.
Все авторские права принадлежат их уважаемым владельцам.
Если Вы являетесь автором данной книги и её распространение
ущемляет Ваши авторские права или если Вы хотите
внести изменения в данный документ или опубликовать
новую книгу свяжитесь с нами по email.


Х.М. Деител, П.Дж. Деител, СИ. Сантри ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ НА Java™ 2 КНИГА 3 КОРПОРАТИВНЫЕ СИСТЕМЫ, СЕРВЛЕГЫ, JSP, WEB-СЕРВИСЫ Перевод с английского под редакцией Ю.А. Левчука Москва Издательство БИНОМ 2003
УДК 004.43 ББК 32.973.26-018.1 Д27 Х.М. Дейтсл, П.Дж. Дсйтсл, СИ. Сантри Технологии программирования на Java 2: Книга 3. Корпоративные системы, сервлеты, JSP, Web-сервисы. Пер. с англ. — М.: ООО «Бином-Пресс», 2003 г. — 672 с: ил. Предлагаемая книга является переводом третьей части оригинального издания «Advanced Java 2 Platform Mow to Program». Оригинал содержит более 1800 страниц, поэтому было принято решение русское издание разбить на три части. Первая часть посвящена созданию графического пользовательского интерфейса, двухмерной и трехмерной графике, компонентам JavaBeans, взаимодействию с базами данных. Вторая часть посвящена созданию распределенных приложении, а третья часть, которую вы держите в руках, посвящена созданию серверных приложений и корпоративных систем. В первых главах книги рассматривается применение технологий сервлетов и JavaServer Pages для создания серверных приложений. После этого читатели познакомятся с технологиями Enterprise JavaBeans, J2ME, Java Message Service и SOAP, которые находят применение при создании корпоративных систем. Будут также рассмотрены некоторые популярные серверы приложений. Книга насыщена многочисленными примерами и упражнениями. Особое место занимает большой практический пример приложения для электронного бизнеса, в котором нашли отражение все рассматриваемые а книге технологии. Все права защищены. Никакая часть этой книги не мажет быть воспроизведена в любой форме U.1U любыми средствами, электронными или механическими, включая фотографирование, магнитную щпись или иные средства копирования или сохранения информации без письменного разрешения издательства. Translation copyright © 2002 by Binom Publishers. Advanced Java 2 How to program, First Edition by Harvey Deitsl, Copyright © 2002, All Right Reserved Published by arrangement with the original publisher, PRENTICE HALL, INC, a Pearson Education Company. © PRENTICE 11 ALL, INC, a Pearson Education Company. 2002 ISBN 5-9518-0034-Х (рус.) © Издание на русском языке. ISBN 0-13-0S9560-1 (англ.) Издательство Бином, 2003 Научно-техническое издание Харви Дейтел, Пол Дейтел, СИ. Сантри Технологии профаммировапия на Java 2 Книга 3. Корпоративные системы, сервлеты, JSP, Web-сервисы Компьютерная верстка С. Д Лычагина Подписано в печать 24.03.03. Формат 70х100У|6. Усл. печ. л. 54,6. Гарнитура «Школьная». Бумага газетная. Печать офсетная. Тираж 3000 экз. Заказ N9 1080. Издательство «Бином-Пресс», 2003 г. 170026, Тверь, Комсомольский просп., !2 Отпечатано в издательско-полиграфическом комплексе «Звезда» 6И990, I. Перми ['СП-131, ул. Дружбы, 34
Содержание Предисловие редактора русского перевода . 11 Предисловие 13 Особенности книги 14 Некоторые замечания для преподавателей 16 Подход к обучению 18 Благодарности 21 Об авторах 23 О компании Deitel & Associates, Inc 24 Консорциум World Wide Web (W3C) 25 Глава 1. Введение .27 1.1. Введение 28 1.2. Архитектура книги 29 1.2.1, Серверные приложения и Web-сервисы 29 1.2.2, Корпоративные приложения Enterprise Java 30 1.2.3, Практический пример корпоративного приложения 31 1.3. Краткий путеводитель по книге 31 1.4. Выполнение примеров 36 1.5. Паттерны проектирования 37 1.5.1. История паттернов проектирования 38 1.5.2. Обзор паттернов проектирования 39 1.5.3. Паттерны параллельного выполнения 42 1.5.4. Архитектурные паттерны проектирования ' 43 1.5.5. Дополнительные ресурсы по паттернам проектирования 44 Глава 2, СервлетЫ 47 2.1. Введение 48 2.2. Обзор технологии сервлетов и их архитектура 51 2.2.1. Интерфейс Servlet и жизненный цикл сервлета 52 2.2.2. Класс HttpServlet 53 2.2.3. Интерфейс HttpServletReqtiest 54 2.2.4. Интерфейс HttpServletResponse 55 2.3. Обработка HTTP-запросов get 56 2.3.1. Установка сервера Apache Tomcat 60 2.3.2. Развертывание Web-приложения 62
6 Технологии программирования на Java 2 2.4. Обработка HTTP-запросов get, содержащих данные 66 2.5. Обработка HTTP-запросов post 69 2.6. Переадресация запросов 72 2.7. Отслеживание состояния сеанса 76 2.7.1. Cookies 77 2.7.2. Отслеживание сеанса с помощью интерфейса HtlpSession 85 2.8. Многоуровневые приложения: использование средств JDBC из сервлета 93 2.9. Класс HttpUtils 100 2.10. Ресурсы в Internet и во Всемирной паутине 101 Глава 3. JavaServer Pages {JSP) 109 3.1. Введение : 110 3.2. Обзор технологии JavaServer Pages Ill 3.3. Первый пример JSP-страницы 112 3.4. Неявные объекты 114 3.5. Сценарии 116 3.5.1. Компоненты сценария 116 3.5.2. Пример сценария 117 3.6. Стандартные действия 120 3.6.1. Действие <jsp:inelude> 121 3.6.2. Действие <jsp;forward> 126 3.6.3. Действие <jsp:plugin> 129 3.6.4. Действие <jsp:useBean> 133 3.7. Директивы 149 3.7.1. Директива page 150 3.7.2. Директива include 152 3.8. Библиотеки нестандартных тегов 154 3.8.1. Простой нестандартный тег 155 3.8.2. Нестандартный тег с атрибутами 159 3.8.3. Обработка тела нестандартного тега 162 3.9. Ресурсы в Internet и во Всемирной паутине 169 Глава 4. Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 177 4.1. Введение 178 4.2. Архитектура приложения книжного Internet-магазика 179 4.3. Доступ в Internet-магазин 181 4.4. Получение списка книг из базы данных 184 4.5. Просмотр информации о книге 192 4.6. Добавление элемента в магазинную тележку 199 4.7. Просмотр содержимого магазинной тележки 202 4.8. Подсчет стоимости и оформление заказа 205 4.9. Обработка заказа 208 4.10. Развертывание приложения в J2EE 1.2.1 210 4.10.1. Настройка источника данных books 211 4.10.2. Запуск сервера Cloudscape и сервера J2EE 212 4.10.3. Запуск средства разпертывания приложений J2EE 213
Содержание 7 4.10.4. Создание приложения книжного Internet-магазина 213 4.10.5. Создание Web-компонентов BookServlet и AddToCartServlet . . , 214 4.10.6. Добавление в приложение компонентов, не являющихся сервлетами 220 4.10.7. Задание контекста Web, ссылок на ресурсы, имен JNDI и файлов приветствия 223 4.10.8. Развертывание и выполнение приложения 225 Глава 5. Разработка приложений для беспроводной связи на базе Java и J2ME 233 5.1. Введение 234 5.2. Обзор сервлета WelcomeServlet 237 5.3. Обзор сервлета TipTestServlet 243 5.3.1. Запрос от браузера Internet Explorer 256 5.3.2. Запрос от браузера WAP 263 5.3.3. Запрос от браузера i-motie Pixo 266 5.3.4. Запрос от клиента J2ME 271 5.4. Java 2 Micro Edition 274 5.4.1. Connected Limited Device Configuration (CLDC) 274 5.4.2. Mobile Information Device Profile (MIDP) 275 5.4.3. Обзор мидлета TipTestMIDlet 276 5.5. Инструкции по установке 297 5.6. Ресурсы в Internet и во Всемирной паутине 300 Глава 6. Сеансовые компоненты EJB и распределенные транзакции 307 6.1. Введение 308 6.2. Обзор технологии EJB 308 6.2.1. Удаленный интерфейс 309 6.2.2. Собственный интерфейс 309 6.2.3. Реализация EJB 310 6.2.4. Контейнер EJB 310 6.3. Сеансовые компоненты 310 6.3.1. Сеансовые компоненты EJB с состоянием 311 6.3.2. Развертывание сеансовых компонентов EJB 323 6.3.3. Сеансовые компоненты EJB без состояния 330 6.4. EJB-транзакции 340 6.4.1. Собственный и удаленный интерфейс EJB MoneyTransfer 340 6.4.2. Разграничение транзакций с управлением на стороне компонента 341 6.4.3. Разграничение транзакций с управлением на стороне контейнера 347 6.4.4. Клиентский EJB-компонент MoneyTransfer 352 6.4.5. Развертывание EJB-компонента MoneyTransfer 357 6.5. Ресурсы в Internet и во Всемирной паутине 357 Глава 7. Компоненты EJB с данными 363 7.1. Введение 364 7.2. Обзор EJB-компонентов с данными 365
8 Технологии программирования на Java 2 7.3. Компонент-сущность EJB Employee, хранящий информацию о сотруднике 365 7.4. Собственный и удаленный интерфейсы EJB-компонента Employee 366 7.5. EJB-компонент Employee с персистентностью, управляемой компонентом 368 7.5.1. Реализация EJB-компонента Employee 368 7.5.2. Развертывание EJB-компонента Employee 378 7.6. EJB-компонент Employee с персистентяосткю, управляемой контейнером 380 7.7. Клиент EJB-компонента Employee 384 7.8. Ресурсы в Internet и во Всемирной паутине 394 Глава 8. Обмен сообщениями с помощью Java Message Service (JMS) 397 8.1. Введение 398 8.2. Установка и настройка J2EE 1.3 399 8.3. Обмен сообщениями «от точки к точке» 400 8.3.1. Приложение для голосования Voter: обзор 400 8.3.2. Приложение Voter: серверная сторона 400 8.3.3. Приложение Voter: принимающая сторона 405 8.3.4. Приложение Voter: настройка и выполнение 410 8.4. Обмен сообщениями в модели * издатель/подписчик » 411 8.4.1. Приложение Weather: обзор 412 8.4.2. Приложение Weather: часть, относящаяся к издателю 412 8.4.3. Приложение Weather: часть, относящаяся к подписчику 417 8.4.4. Приложение Weather: настройка и выполнение 426 8.5. Компоненты Enterprise JavaBeans, управляемые сообщениями . . . 426 8.5.1. Приложение Voter: обзор , 427 8.5.2. Приложение Voter: принимающая сторона 427 8.5.3. Приложение Voter: настройка и выполнение 436 Глава 9. Практический пример корпоративного приложения. Обзор архитектуры 449 9.1. Введение 450 9.2. Приложение книжного Internet-магазина Deitel Bookstore 451 9.3. Общая архитектура системы 451 9.4. Компоненты Enterprise JavaBeans 452 9.4.1. EJB-сущности 453 9.4.2, Сеансовые EJB-компоненты с состоянием 454 9.5. Логика управления, реализуемая сервлетами 454 9.6. Логика внешнего представления данных посредством XSLT .... 454 Глава 10. Практический пример корпоративного приложения. Логика представления данных и логика управления .... 469 10.1. Введение 470 10.2. Базовый класс XMLServlet 471 10.3. Сервлеты, реализующие магазинную тележку 482
Содержание 9 10.3.1. Сервлет AddToCartServlet 483 10.3.2. Сервлет ViewCartServlet 486 10.3.3. Сервлет RemoveFromCartServlet 495 10.3.4. Сервлет UpdateCartServlet 497 10.3.5. Сервлет CheekoutServlet 499 10.4. Сервлеты, обслуживающие каталог товаров 501 10.4.1. Сервлет GetAllProductsServlet 502 10.4.2. Сервлет GetProductServlet 505 10.4.3. Сервлет ProductSearchServlet 508 10.5. Сервлеты для обслуживания покупателей 511 10.5.1. Сервлет RegisterServlet 512 10.5.2. Сервлет LoginServlet 515 10.5.3. Сервлет ViewOrderffistoryServlet 519 10.5.4. Сервлет ViewOrderServlet 522 10.5.5. Сервлет GetPasswordffintServlet 525 Глава 11. Практический пример корпоративного приложения. Бизнес-логика: часть 1 531 11.1. Введение 532 11.2. Архитектура компонентов EJB 532 11.3. Реализация магазинной тележки 534 11.3.1. Удаленный интерфейс ShoppingCart 534 11.3.2. Реализация ShoppingCartEJB удаленного интерфейса ShoppingCart 535 11.3.3. Собственный интерфейс ShoppingCartHome 542 11.4. Реализация EJB-компонента Product 543 11.4.1. Удаленный интерфейс Product 543 11.4.2. Реализация ProductEJB удаленного интерфейса Product ..... 543 11.4.3. Собственный интерфейс ProductHome 546 11.4.4. Класс ProductModel 547 11.5. Реализация EJB-компонента Order 551 11.5.1. Удаленный интерфейс Order 551 11.5.2. Реализация OrderEJB удаленного интерфейса Order 551 11.5.3. Собственный интерфейс OrderHome 557 11.5.4. Класс OrderModel 558 11.6. Реализация EJB-компонента Order Product 563 11.6.1. Удаленный интерфейс OrderProduct 563 11.6.2. Реализация Order ProductEJB удаленного интерфейса OrderProduct 564 11.6.3. Собственный интерфейс OrderProductHome 566 11.6.4. Класс первичного ключа OrderProductPK 567 11.6.5. Класс OrderProduct Model 569 Глава 12, Практический пример корпоративного приложения. Бизнес-логика: часть 2 573 12.1. Введение 574 12.2. Реализация EJB-компонента Customer 575 12.2.1. Удаленный интерфейс Customer 575 12.2.2. Реализация CustomerEJB удаленного интерфейса Customer 575
10 Технологии программирования на Java 2 12.2.3. Собственный интерфейс CustomerHome 582 12.2.4. Класс CustomerModel 583 12.3. Реализация EJB-компонента Address 589 12.3.1. Удаленный интерфейс Address 589 12.3.2. Реализация AddressEJB удаленного интерфейса Address 589 12.3.3. Собственный интерфейс AddressHome 593 12.3.4. Класс AddressModel 593 12.4. Реализация EJB-компонента SequenceFactory 599 12.4.1. "Удаленный интерфейс SequenceFactory 599 12.4.2. Реализация SequenceFactoryBJB удаленного интерфейса SequenceFactory 600 12.4.3. Собственный интерфейс SequenceFactoryHome 602 12.5. Развертывание приложения Deitel Bookstore средствами J2EE . . 603 12.5.1. Развертывание компонентов-сущностей EJB с персистентностью, управляемой контейнером 603 12.5.2, Развертывание сервлетов 611 Глава 13. Серверы приложений 615 13.1. Введение 616 13.2. Спецификация J2EE и ее преимущества 616 13.3. Коммерческие серверы приложений 617 13.3.1. BEA WebLogic 6.0 61Т 13.3.2. iPlanet Application Server 6.0 618 13.3.3. IBM WebSphere Advanced Application Server 4.0 619 13.3.4. Сервер приложений JBoss 2.2.2 619 13.4. Развертывание приложения Deitel Bookstore на сервере BEA WebLogic 620 13.5. Развертывание приложения Deitel Bookstore на сервере IBM WebSphere 644 13.6. Ресурсы в Internet и во Всемирной паутине 646 Глава 14. Введение в Web-сервисы и SOAP 649 14.1. Введение 650 14.2. Простой протокол доступа к объектам (SOAP) 651 14.3. Служба погоды, реализованная посредством SOAP 657 14.4. Ресурсы в Internet и во Всемирной паутине 670
Предисловие редактора русского перевода Книга, которую Вы держите в руках, является переводом третьей части оригинального издания «Advanced Java™ 2 Platform. How to Program». Поскольку объем оригинала превышает 1800 страниц, при переводе было принято решение русское издание разбить на три части. Данная книга посвящена компонентам Enterprise JavaBeans, программированию серверных приложений и корпоративных приложений. Первая часть посвящена программированию графического пользовательского интерфейса приложений, двухмерной и трехмерной графике, взаимодействию с базами данных, компонентам JavaBeans. Вторая часть посвящена созданию распределенных приложении. Книга насыщена многочисленными примерами и упражнениями. Читатели могут использовать программный код, загрузив примеры с сайта издательства, и запускать программы при изучении книги. Весь код доступен для загрузки через Internet на следующих Web-сайтах: www.deitel.com www.prenthall.com/deitel www.binom-press.ru/rfr_prog_adv_j ava2.htm Хотелось бы обратить внимание читателей на то, что файлы примеров размещены в папках, имена которых соответствуют номерам глав оригинала. В то же время, нумерация глав, принятая в данной книге, не совпадает с нумерацией глав в оригинале. В этой связи предлагаем читателям при работе с примерами пользоваться следующей таблицей соответствий. Номер главы 2 3 4 5 б 7 S Имя папки с примерами ch.09 chIO chll chl2 ch14 rhIS chl6 Номер главы 9 10 11 12 13 14 Имя папки с примерами ch17 сМ8 ch19 ch20 ch21 ch29
Предисловие Жизнь состоит из фрагментов. Надо только их соединить. Эдвард Морган Форстер Добро пожаловать в волнующий мир передовых концепций программирования, реализованных в трех основных платформах Java: Java™ 2 Enterprise Edition (J2EE), Java 2 Standard Edition (J2SE) и Java 2 Micro Edition (J2ME). Принимая участие в работе конференции Internet/World Wide Web, которая состоялась в ноябре 1995 г. в Бостоне, мы едва ли могли представить, к каким результатам это приведет: четыре издания книги Как программировать на Java (книга, ставшая мировым бестселлером среди книг по Java), а теперь и данная книга, посвященная технологиям для разработки программ на Java, которая будет полезной при углубленном изучении компьютерных технологий на старших курсах высших учебных заведений, а также для профессиональных разработчиков. До появления Java мы были убеждены, что в течение следующего десятилетия С~+ заменит С в качестве доминирующего языка разработки приложений и языка системного программирования. Однако World Wide Web и Java в совокупности способствовали выдвижению Internet на первые роли при планировании и реализации информационных систем. Организации хотят напрямую, без излишних издержек, интегрировать Internet в свои информационные системы. Java для этой цели подходит больше, чем C++. Согласно заявлению Sun Microsystems, по состоянию на 2001 г. более 95% корпоративных систем поддерживают технологию J2EE. В книге мы рассмотрим технологии Java, которые могут быть незнакомыми для большинства программистов на Java и вызвать их интерес. Каждая глава построена таким образом, чтобы читатель мог получить представление о передовых и достаточно сложных технологиях Java, не углубляясь при этом во все нюансы. Фактически, каждой из рассматриваемых тем может быть посвящена отдельная книга объемом 600-800 страниц. Для примеров в этой книге мы используем подход, отличный от того, который применялся в других наших книгах. Количество программ уменьшилось, но эти программы увеличились в объеме и иллюстрируют довольно изощренные приемы проектирования программного обеспечения. Создавая книгу для разработчиков, мы интегрировали в нее множество технологий, чтобы вы могли продвинуться дальше и экспериментировать с современными технологиями и наиболее широко применяемыми, принципами и.рииктщюва.нин программного обеспечения. Это ли не лучший способ научиться работать с реальными технологиями и программным кодом'.-' Прежде, чем принять решение, какие темы включить в эту книгу, мы прочли десятки журналов, изучили информацию на Web-сайте Sun Microsystems и приняли участие в многочисленных конференциях и презентациях. Мы пересмотрели наш материал в свете новейших технологий, представленных на конференции JavaOne. В работе этой конференции, спонсором которой выступила корпорацией Sun Microsystems, приняли участие ведущие разработчики на Java, Мы также про-
14 Технологии программирования на Java 2 смотрели ряд книг по Java, посвященных специальным темам. После проведения такого обширного исследования мы создали основной набросок книги и отдали его для рецензирования специалистам по Java. Наше желание включить достаточно большое количество тем привело к тому, что объем книги превысил 1800 страниц. Мы готовы принести извинения за неудобства, связанные со столь большим объемом книги, но материал и количество изучаемых тем слишком обширны. При переиздании книги мы, возможно, разделим ее на два тома1. Эта книга существенно выиграла от тщательного ее рецензирования многочисленной группой экспертов. Нам также очень помогла подробная документация, которая имеется на Web-сайте корпорации Sun (www.sun.com). Рецензенты, работающие в корпорации Sun и многих других известных компаниях, оказали большую помощь при формировании структуры книги. Нам хотелось, чтобы опытные разработчики просмотрели наш текст и примеры кода, и мы могли предоставить советы специалистов, которые реально работают с рассматриваемыми технологиями, применяя их на практике. Мы решили включить в книгу обзор серверов приложений (глава 13). Тремя наиболее популярными программными продуктами для серверов приложений являются BEA WebLogic, IBM WebSphere и Sun/Netscape iPlanet. Компания iPlanet на момент издания этой книги готовила к выпуску новую версию сервера приложений. В соответствии с соглашением между iPlanet и Deitel & Associates, Inc. компания iPlanet предоставила ссылку на сайт www.iplanet.com/ias__deitel, специально посвященный этой книге, с которого читатели смогут загрузить последнюю версию программного продукта iPlanet. Мы также приводим указания по развертыванию нашего учебного примера на сервере iPlanet. Эти указания можно найти на Web-сайте www.deitel.com. Особенности книги Эта книга имеет следующие особенности: • Технология Enterprise Java и комплексный пример корпоративного приложения Java. Разработчики используют Java для построения сложных и ответственных корпоративных приложений. В главах 2-4, 6—8 и 13 исследуются компоненты, необходимые для реализации корпоративных решений, В частности, сервлеты, страницы JavaServer Pages, распределенные транзакции, промежуточное программное обеспечение, ориентированное на обмен сообщениями, и серверы приложений. В комплексном практическом примере корпоративного приложения Enterprise Java в главах 9-12 нашли применение многие технологии, такие как Enterprise JavaBeans, сервлеты, RMI-II0P, XML, XSLT, XHTML, а также WML и cHTML (для разработки приложений для беспроводных устройств). В примере книжного Internet-магазина Deitel Bookstore демонстрируется, как использовать архитектуру модель—вид— контроллер (model—view—controller — MVC) для построения корпоративного приложения. В данном приложении используются технологии, которые дают возможность поддерживать клиентов практически любого типа, в том числе сотовые телефоны, мобильные устройства и Web-браузеры. В современном мире проводных и беспроводных сетей деловая информация должна надежно и безопасно доставляться прЕДПолагаемым получателям. • Java 2 Micro Edition (J2ME) и приложения для беспроводных устройств. Согласно прогнозам, к 2003 г. большая часть людей во всем мире будет осуществлять доступ в Internet через беспроводные устройства и через настольные 1 В связи с этим при переводе было принято решение русское издание разбить на три части. — Ирам. ред.
Предисловие 15 компьютеры. Java 2 Micro Edition (J2ME) представляет собой платформу Java для беспроводных устройств, имеющих ограниченные функциональные возможностями, таких как сотовые телефоны и карманные компьютеры, В главе 5 представлен комплексный практический пример, в котором содержимое из централизованного хранилища данных доставляется нескольким клиентам, в том числе клиенту J2ME, по беспроводной связи, Web-сервисы.. Web-еервисы — это приложения, предоставляющие общедоступные интерфейсы, которые могут использоваться другими приложениями в Web. Web-сервисы построены на базе известных протоколов, таких как HTTP, и осуществляют обмен информацией с помощью XML-сообщений. Службы (сервисы) каталогов дают возможность клиентам выполнять поиск для обнаружения имеющихся Web-сервисов. Протокол простого доступа к объектам Simple Object Access Protocol (SOAP) использует XML для предоставления коммуникационных возможностей Web-сервисам, Технологии SOAP посвящена глава 14, Многие из рассматриваемых в этой книге технологий могут быть использованы для построения Web-сервисов. Применение паттернов проектирования. Наиболее крупные практические примеры в книге, такие как трехуровневое приложение с использованием сервлетов и страниц JavaServer Pages в главе 4, трехуровневое приложение для беспроводного доступа в главе 5 и корпоративное приложение книжного Internet-магазина Deitel Bookstore в главах 9-12, содержат тысячи строк кода. Реальные системы, такие как системы для банкоматов или для управления воздушным движением, могут содержать сотни тысяч или даже миллионы строк кода. При построении таких сложных систем важную роль приобретает эффективное проектирование. За последнее десятилетие в индустрии разработки программного обеспечения был достигнут значительный прогресс в области паттернов проектирования — проверенных архитектурных решений для построения гибкого и хорошо управляемого объектно-ориентированного программного обеспечения.1 Использование паттернов проектирования может существенно уменьшить сложность процесса проектирования программного обеспечения. При создании программ в этой книге мы применяли множество паттернов проектирования. В главе 1 приведены начальные сведения о паттернах проектирования, рассказывается, какую пользу они приносят, и перечислены паттерны проектирования, применяемые в этой книге. XML. Использование расширяемого языка разметки Extensible Markup Language (XML) становится правилом при разработке программных продуктов, и мы постоянно используем его в материале книги. Обладая синтаксисом для создания языков разметки, не зависящим от платформы, переносимые данные XML хорошо интегрируются с переносимыми приложениями и сервисами Java. Если вы не знакомы с XML, вам следует предварительно получить представление об основах XML, прочитав какую-либо книгу по этой тематике.2 Библиография и ресурсы. Главы этой книги содержат списки соответствующей литературы и унифицированные указатели ресурсов (URL), предлагающих дополнительную информацию о рассматриваемых технологиях. Это сделано, чтобы те читатели, которые хотели бы более подробно изучить данную тему, могли обратиться к ресурсам, которые мы сочли полезными. Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влнссидес «Приемы объектно-ориентированного проектирования. Паттерны проектирования». СПб.: Питер, 2001 г., 368 с. Можно порском он доиать читателю книгу Х.М. ДейтеХ П.Дж. Дейтел, Т.Р. Нието и др. «Как программировать на XML*. М.: Издательство Вином, 2001 г,, 944 с. — Прим.ред.
16 Технологии программирования на Java 2 Некоторые замечания для преподавателей Мир объектно-ориентированного программирования Когда мы писали первое издание книги Пак программировать на Java, университеты еще были ориентированы на процедурные языки программирования, такие как Pascal и С. В некоторых учебных заведениях уже изучали объектно-ориенти рованный язык программирования C++, но и здесь по большей части использовалось процедурное программирование в сочетании с объектно-ориентированным программированием — такое сочетание характерно для C++, но не для Java. На момент выхода третьего издания книги Как программировать на Java многие университеты начали переходить с изучения C++ на изучение основ Java, а преподаватели стали склоняться к чисто объектно-ориентированному подходу к программированию. Одновременно с этим сообщество разработчиков программных продуктов стандартизировало подход к моделированию объектно-ориентированных систем с помощью UML. Сложилась также практика применения паттернов проектирования. Данная книга использует 100% объектно-ориентированный подход и уделяет особое внимание паттернам проектирования. Необходимым предварительным условием для изучения этой книги является знакомство с книгой Как программировать на Java. Издание 4-е (или эквивалентной книгой по Java), которая предоставляет прочный фундамент для программирования на Java. Ниже перечислены главы и приложения, вошедшие в книгу Как программировать на Java. Издание 4-е, более подробное описание содержания можно найти на сайте www.deitel.com: Введение в компьютеры, Internet и Web; Введение в приложения Java; Введение в апплеты Java; Структуры управления, часть 1; Структуры управления, часть 2; Методы; Массивы; Программирование на основе объектов; Объектно-ориентированное программирование; Строки и символы; Графика и Java 2D; Компоненты графического интерфейса пользователя, часть 1; Компоненты графического интерфейса пользователя, часть 2; Обработка исключений; Программные потоки; Файлы и потоки; Сетевые средства; Мультимедиа: Изображения, анимация, аудио и видео; Структуры данных; Пакет утилит Java и манипулирование битами; Коллекции; Инфраструктура Java Media и Java Sound; Ресурсы по Java; Диаграмма приоритета операторов; Набор символов ASCII; Системы счисления; Создание HTML-документации с помощью javadoc; Интерфейсы событий и слушателей Elevator; Модель Elevator; Представление Elevator; Карьерные возможности; Unicode; Литература. Почему студентам нравится Java Студенты имеют высокую мотивацию, поскольку осознают, что изучают перспективный язык (Java) и передовую парадигму программирования (объектно-ориентированное программирование) для построения больших систем. Java сразу же демонстрирует свои преимущества в мире, где Internet и Всемирной паутине отводится основополагающая роль, и работодателям требуются программисты для разработки сложных корпоративных систем. Студенты быстро обнаруживают, что при помощи Java они достаточно быстро могут сделать многие вещи, поэтому они с удовольствием затрачивают дополнительные усилия на изучение Java. Java помогает программистам реализовать свой творческий потенциал. Мы хорошо видим это в ходе преподавания базового и расширенного курсов по Java, которое осуществляет компания Deitel & Associates, Inc. Главное назначение книги Наша цель была вполне очевидна — создать книгу по Java-технологиям для изучения студентами старших курсов высших учебных заведений по специальностям, связанным с информационными технологиями, для программистов на Java
Предисловие 17 средней квалификации, а также для более глубокого изучения ряда теоретических и практических вопросов профессионалами. Чтобы удовлетворить этим требованиям, мы создали книгу, которая вызовет интерес у программистов на Java. Мы представляем полноценные примеры по изучаемым темам и часто обращаемся к уже известному материалу. Мы следуем стилю и сложившейся практике при написании исходного кода при его форматировании, правильном использовании интерфейсов прикладного программирования Java, конструкций и технологий. В этой книге представлены приложения Java, которые читатели могут использовать, чтобы немедленно приступить к работе с рассматриваемыми технологиями. Эволюция книги Данная книга была закончена сразу же после книги Как программировать на Java. Издание 4-е. Сотни тысяч студентов университетов и профессионалов по всему миру изучали Java с помощью нашей книги. После своего издания книга Как программировать на Java 2 использовалась в университетах, корпорациях и правительственных организациях по всему миру. Компания Deitel & Associates, Inc. осуществляла преподавание курсов по Java среди тысяч студентов, для чего использовались написанные нами книги. Мы тщательно следим за эффективностью подачи материала и соответствующим образом меняем содержимое наших книг. Реализация концепций Java Мы верим в Java. Реализация концепций Java корпорацией Sun Microsystems, является просто блестящей. В основу нового языка Sun были положены языки С и C++: два наиболее популярных и широко используемых в мире языка. Это немедленно привлекло на сторону Java большую группу высококвалифицированных программистов, которые участвовали в реализации коммуникационных систем, систем управления базами данных, приложений для персональных компьютеров и системного программного обеспечения. Разработчики Sun отбросила более сложные и порождающие ошибки функции C/C++ (такие, как указатели, перегрузка операторов, множественное наследование и некоторые другие). Языку был придан компактный вид за счет удаления специализированных средств, используемых лишь небольшой частью сообщества программистов. Язык стал реально переносимым, что дало возможность реализовывать приложения для Internet и Web. В него были включены такие необходимые для разработчиков средства, как работа со строками, графикой, компонентами графического пользовательского интерфейса, обработка исключений, организация многопоточной обработки, мультимедийные возможности (изображения, анимация, аудио и видео), структуры данных, взаимодействие с системами управления базами данных, сетевые средства взаимодействия клиент/сервер на базе Internet и Web, технологии для создания распределенных и корпоративных систем. Язык был сделан свободно доступным для миллионов потенциальных программистов по всему миру, 2,5 миллиона разработчиков на Java Язык программирования Java был создан в 1995 г. как средство для добавления динамического содержимого в Web-страницы. В дополнение к традиционному статическому тексту и графики Web-страницы могут теперь быть оживлены с помощью аудио и видео, анимации, интерактивных средств и, в скором времени, трехмерных изображений.^Однако помимо этого, в Java есть и множество других достоинств. Возможности Java полностью отвечают современным требованиям компаний и организаций к обработке информации. Мы сразу же разглядели имеющийся в Java потенциал, который позволит Java стать одним из основных языков программирования общего назначения. Фактически технологии Java вызвали революцию в разработке программного обеспечения, предоставив возможности для создания активно использующего средства мультимедиа, не зависящего от плат-
18 Технологии программирования на Java 2 формы, объектно-ориентированного кода для широко используемых, функционирующих в Internet и корпоративных сетях приложений и апплетов. Java сейчас объединяет 2,5 млн. разработчиков по всему миру — впечатляющее достижение, если учесть, что Java доступен только в течение всего шести лет. Ни один другой язык программирования до сих пор не приобретал столь большую аудиторию разработчиков за такое короткое время. Подход к обучению Данная книга содержит множество примеров и упражнений из многих областей практической деятельности, дающих читателю возможность решать интересные практические задачи. Книга концентрирует внимание на принципах хорошего стиля и техники программирования, что особенно важных при создании больших программ, подобных рассматриваемым в этой книге. Мы старались избегать хитроумных терминов и описаний синтаксиса, отдавая предпочтение обучению на примерах. Наши примеры были протестированы на популярных платформах Java. Книга написана преподавателями, которые посвящают большую часть времени обучению передовым технологиям практического программирования и написанию книг по этой тематике. Подход к обучению Java посредством живого кода (live-code™) Книга насыщена примерами живого кода (live-code™). Это основа нашего метода преподавания программирования и написания учебных пособий, а также основа нашего мультимедийного курса обучения Cyber Classrooms и курсов дистанционного обучения на базе Web (Web-Based Training Courses). Каждое новое понятие представлено в контексте законченной, работающей программы, за которой следует одна или несколько врезок с входным и/ выходными данными программ. Мы называем такой стиль обучения и написания учебных пособий методом живого кода (live- code™). Мы используем язык программ для обучения языкам программирования. Чтение программного кода во многом заменяет ввод его в компьютер и выполнение. Доступ в World Wide Web Весь код доступен для загрузки через Internet на следующих Web-сайтах: www.deitel.com www.prenhall.com/deitel www.binom-press.ru/books/adv_java2.htm Цели Каждая глава начинается с раздела Цели. Здесь говорится, чего ожидать и что можно узнать, прочитав главу, чтобы читатели определились, нужен ли им материал главы. Это повышает доверие и способствует позитивному восприятию. Цитаты За задачами обучения следуют цитаты. Некоторые из них юмористические, некоторые — философские, некоторые представляют собой интересные точки зрения. Нашим читателям нравится, что материал главы сопровождается цитатами. Многие из цитат стоит прочитать снова после прочтения главы. План План главы поможет читателям подойти к изучению материала по принципу от простого к сложному. Это также поможет им заранее узнать, какие темы будут рассматриваться дальше, и установить для себя удобный и эффективный темп обучения.
Предисловие 19 Тысячи строк хода, большое число примеров программ (с результатами их выполнения) Мы представляем возможности Java в контексте законченных, работоспособных программ. Программы в этой книге являются довольно объемными и содержат сотни или даже тысячи строк кода (например, пример приложения для книжного Internet-магазина содержит около 10000 строк кода). Читатели могут использовать программный код, загрузив примеры с сайта издательства, и запускать программы при изучении книги. Иллю страц uu/рисунки Многие из рисунков являются примерами кода, но в книге также имеется множество диаграмм, рисунков и копий экранов с результатами выполнения программ. Например, весьма выразительны структурные схемы, поясняющие архитектуру комплексного учебного приложения Enterprise Java в главе 9. Советы по программированию Мы включили советы по программированию, чтобы помочь читателям сосредоточиться на наиболее важных аспектах создания программ. Мы выделили эти советы в виде рубрик Хороший стиль программирования, Типичные ошибки программирования, Советы по тестированию и отладке, Советы по повышению эффективности. Советы по переносимости программ, Общие методические рекомендации и Внешний вид программы. Эти советы и рекомендации представляют собой лучшее, что мы собрали за несколько десятилетий активного программирования и преподавания. Одна из наших студенток — математик — говорила иам, что этот подход подобен выделению аксиом, теорем и следствий в книге по математике; это создает основу, на которой строится хорошее программное обеспечение. Хороший стиль программирования Мы выделяем рекомендации по хорошему стилю программирования при написании программ, которые позволяют создавать более ясные, более понятные для восприятия, легко отлаживаемые и сопровождаемые программы. Типичные ошибки программирования Особое выделение типичных ошибок программирования помогает читателям не делать таких ошибок в своей риботе. Советы по тестированию и отладке Когда мы впервые решили включить советы по тестированию и отладке, мы думали, что они будут подсказывать, как тестировать и отлаживать программы на Javu. Фактически большая часть из этих советов описывает различные аспекты Java, которые уменьшают вероятность возникновения ошибок и, тем самым, упрощают процесс тестирования и отладки. Советы по повышению эффективности Мы включили советы по повышению эффективности, которые раскрывают возможности увеличения эффективности работы программ, — как сделать, чтобы программы работала быстрее, или как мшшмизироаать объем памяти, который требуется для их выполнения. Советы по переносимости программ Одним из основных преимуществ Java является переносимость, поэтому некоторые программисты полагают, что если они реализуют приложение
20 Технологии программирования на Java 2 на Java, это приложение будет автоматически обладать переносимостью на все платформы, поддерживающие Java. К сожалению, это не всегда так. Мы включили советы по переносимости, чтобы помочь читателям научиться писать обеспечивающий переносимость код и предоставить информацию о том, как Java достигает столь высокую степень переносимости. Общие методические рекомендации Парадигма объектно-ориентированного программирования требует полного переосмысления способа построения программных систем. Java является эффективным языком для разработки качественного программного обеспечения. В этой рубрике выделяются архитектурные проблемы, которые влияют на построение систем программного обеспечения, особенно больших систем. Внешний вид программы Мы предусмотрели эту рубрику, чтобы особо подчеркнуть соглашения, принятые при построении графического интерфейса пользователя. Эти наблюдения помогут читателям разрабатывать свои собственные графические интерфейсы пользователя в соответствии с принятыми нормами. Резюме Каждая глава заканчивается дополнительным методическим материалом. Мы представляем подробное, представленное в виде основных положений, резюме главы. На каждую главу в среднем приходится по 26 пунктов резюме. Это поможет читателям быстро просмотреть и закрепить ключевые понятия. Терминология Мы включили раздел Терминология, содержащий перечень всех основных терминов, используемых в главе, в алфавитном порядке, — опять же, для закрепления изученного. На каждую главу в среднем приходится по 51 термину. Упражнения для самоконтроля с ответами на них Для самостоятельного обучения предусмотрены многочисленные упражнения для самоконтроля и ответы на них. Это дает читателю возможность научиться уверенно ориентироваться в изученном материале и подготовиться к выполнению основных упражнений. Читателям следовало бы попробовать выполнить все упражнения для самоконтроля и проверить свои ответы. Упражнения Каждая глава завершается рядом упражнений. Преподаватели могут использовать эти упражнения для домашних заданий, зачетов и экзаменов. Решения для большинства упражнений включены в Руководство Инструктора и в Компакт- диск Инструктора, доступные только для инструкторов через представителей издательства Prentice Hall. {ПРИМЕЧАНИЕ. Пожалуйста, не обращайтесь к нам с просьбами предоставить Руководство Инструктора. Распространение их строго ограничено преподавателями высших учебных заведений, которые осуществляют обучение на основе этой книги. Инструкторы могут получить Руководство только от официальных представителей издательства Prentice Hall. Мы сожалеем, что не можем предоставить решения профессионалам.) Решения примерно к половине упражнений имеются на мультимедийном CD-ROM Advanced Java 2 Platform Multimedia Cyber Classroom, который также является составной частью полного обучающего курса The Complete Advanced Java 2 Platform Training Course. Информацию но заказу вы можете найти, посетив наш Web-сайт www.dei- tel.com.
Предисловие 21 Благодарности Нам бы очень хотелось поблагодарить за проделанную работу многих людей, чьих фамилий нет на обложке, но чья упорная работа, сотрудничество, дружеская помощь и понимание сыграли очень важную роль в создании этой книги. Многие другие сотрудники компании Deitel & Associates, Inc. посвятили не один час работе над этой книгой, • Джонатан Гэдаик, выпускник школы технических и прикладных наук Колумбийского Университета (бакалавр компьютерных наук), принимал участие в написании введения и главы 5. • Кайл Ломели, выпускник колледжа Оберлин, специализирующийся по компьютерным наукам, рецензировал главу 5. • Мэтью Ковалевски, выпускник колледжа Бентли, главный специалист по системам учета информации и директор по разработке приложений для беспроводного доступа компании Deitel & Associates, Inc., внес вклад в написание главы 5. • Кейт ШтеЙнбюлер, выпускница Бостонского колледжа по специальности английский язык и средства общения занималась получением разрешений на публикацию. • Бэтси ДуВальд, выпускница колледжа Метрополитен в Денвере, дипломированный специалист в области технических средств общения (набор и редактирование текстов) и имеющая вторую специальность в области компьютерных информационных систем, является директором редакционного отдела компании Deitel &. Associates, Inc. Она принимала участие в написании Предисловия и помогала готовить рукопись к публикации. Мы также хотели бы поблагодарить участников нашей программы College Internship компании Deitel & Associates, Inc.1 • Крис Хэнсон, студент-старшекурсник университета Брендейс (компьютерные науки), участвовал в написании главы 14. Он также рецензировал главу 13 и участвовал в техническом редактировании глав 6, 7 и 14. • Одри Ли, студентка-старшекурсница колледжа Уэллесли, специализирующийся по компьютерным наукам и математике, участвовал в написании главы 8 и внес вклад в создание главы 10. • Джеффри Хэмм, студент второкурсник Северо-Восточного университета, специализирующийся по компьютерным наукам, принимал участие в написании главы 13. • Варун Ганапати, студент-второкурсник университета Корнелл, специализирующийся в области компьютерных наук и электротехники, участвовал в написании главы 5 и написал исходный код для реализации клиентов i-mode и WML в практическом примере в главе 10. • Сьюзен Уоррен, студентка младшего курса, специализирующаяся по компьютерным наукам в университете Браун, работала над Руководством Инструктора и дополнительными материалами для глав 2 и 3. 1 Программа College Internship Program компании Deitel & Associates, Inc. предлагает ограниченное количество платных вакансий для студентов колледжей в районе Бостона, специализирующихся главным образом в области компьютерных наук, информационных технологий, маркетинга или английского языка. Студенты работают в нашем офисе у С&дбзри, штат Массачусетс, полный рабочий день летом и/или по вечерам в течение учебного года. Для выпускников колледжей имеются вакансии на полный рабочий день. Для получения более полной информации об этой конкурсной программе, пожалуйста, свяжитесь с Эбби Дейтел по адресу deitel@deitel.com и справьтесь на нашем Web-сайте www.deitel.com.
22 Технологии программирования на Java 2 • Юджин Изумо, студент-второкурсник университета Браун, специализирующийся по компьютерным наукам, работал над Руководством Инструктора и дополнительными материалами для глав 2 и 3. • Кристина Корней, студентка-старшекурсница, изучающая психологию и бизнес в Высшей школе Фрэмингхэм, помогала готовить Предисловие и библиографию для нескольких глав. • Эми Гипс, студентка-второкурсница Бостонского колледжа, специализирующаяся по маркетингу и финансам, занималась подбором цитат для всей книги и помогала готовить предисловий. • Фабиан Морган (выпускник Массачусетского технологического института лета 2000 г.) написал первоначальные версии примеров для глав 6, 7 и комплексного практического примера корпоративного приложения в главах 9~12. • Джош Гоулд (выпускник университета Кларк) участвовал в работе над главами 2 и 3. • Нам выпало счастье работать над этим проектом вместе с группой талантливых и квалифицированных специалистов по издательскому делу из компании Prentice Hall. Мы особенно высоко ценим выдающиеся усилия нашего редактора по компьютерным наукам, Петры Ректер, и ее шефа — нашего наставника в издательском деле — Марши Хортен, главного редактора отделения Технических к компьютерных наук издательства Prentice Hall. Вине О'Брайен и Камилла Трентакост проделали громадную работу по организации издательского процесса. Мультимедийный курс Advanced Java 2 Platform Multimedia Cyber Classroom был разработан параллельно с данной книгой. Мы вполне оценили преимущества нового подхода к обучению, прошедшего техническую экспертизу нашего главного редактора по мультимедийному и компьютерному обучению, наставника и друга Марка Тауба. Он и наш редактор по мультимедиа, Карен МакЛин, проделали значительную работу по подготовке курса к публикации в сжатые сроки. Майкл Руэл проделал огромную работу в качестве менеджера проекта Cyber Classroom. Мы должны высказать особую благодарность в адрес Тамары Ньюнэм Ковалло {smart_art@earthliiik.net), которая проделала оформительскую работу с нашими значками-рисунками к советам по программированию и с обложкой. Она придумала этого восхитительного жука, который делится с вами советами по программированию. Барбара Дейтел также помогала создавать образы жуков, которых вы видите на обложке. Мы хотели бы отметить усилия наших рецензентов: Джеф Аллен (Sun Microsystems) Дибиенду Баски (Sun Microsystems) Тим Бодро (Sun Microsystems) Пол Бирн (Sun Microsystems) Онно Клуйт (Sub Microsystems) Питер Корн (San Microsystems) Петр Козел (Sun Microsystems) Ион Пай к вист (Sun Microsystems) Томас Павек (Sun Microsystems) Мартин Ризл (Sun Microsystems) Даванум Сринивас (менеджер по JNI-FAQ, Кип Microsystems) Брэндон Тэйлор (Sun Microsystems) Вики Аллан (университет штата Юта) Джавад Аслам (аналитик/разработчик при ложений, Tektronix) Генри Болен (автор CORBA) Кати Баршацки (Javakathy.com) Дон Бенши (Ben-Cam Intermedia) Кейт Биделоу (Lutris) Даррин Бишон (Levi, Ray and Shoup, Inc.) Карл Бурнхэм (Southpoint) Джоя Конли {институт ДеВряй) Чарльз Костарелла (колледж Antelope Valley) Джонатан Эрл (Technical Training Consultants) Джесс Глик (NctBeans) К ев Гил л юр (Amdocs, Inc.) Джейсон Гордое (Verizon) Кристофер Грин (Colorado Springs Technical Consultants) Майкл Гай (XOR) Дебора Хукен (Mnemosyne Consulting)
Предисловие 23 Элизабет Кальман (Национальная библноте- Срикапт Раджу (штатный инженер, Sun ка Лос-Аламос) Microsystems) Салви Каруппасвами (EDS) Робин Роу (MovicEditor.com) Джоди Крогалис (Compuware) Майкл Шмальц (Accenture) Энтони Левенсейлор (Compuware) Джошуа Шарф (Joshua Sharif Associates) Дерек Лэйн (президент компании Дэн Шеллман (инженер-программист) Gunalinger Software and Consulting, Inc.) Иен Сигел (OMG) Рик Лаек. (Callidus Software} Ума Саббьх (Urographies) Ашши Махиджани (главный ааалитик, про- Арун Таксали (jataayusoft) граммист) Вадим Ткаченко (Sera Nova) Пол МакЛохлан (Compuware) Ким Топли (автор книг Core Java Рэнди Мейерс (Net Com) Foundation Classes и Core Swing: Advanced Пол Манди (Imation) Programming, изданных Prentice Hall) Стивен Ньютон (ведущий программист/ака- Джон Воргезе (университет Рочестер) литик, Standard Insurance Company) Ксинчжу Ванг (Emerald Solutions) Виктор Питере (NextStepEducation) Карен Вислевски (Titan Insurance) Брайаи Понтарелли (консультант) Джесс Уилкннс (Metalinear Media) В сжатые сроки эти рецензенты тщательно прочитали текст и внесли многочисленные предложения, позволяющие повысить точность и полноту представления материала. Мы искренне заинтересованы выслушать ваши комментарии, замечания, поправки и предложения по совершенствованию книги. Пожалуйста, посылайте всю корреспонденцию на наш электронный адрес: deitel@deitel. com. Мы ответим незамедлительно. Кажется, все. Добро пожаловать в увлекательный мир программирования на Java. Мы надеемся, что вам понравится наш взгляд на разработку современных компьютерных приложений. С наилучшими пожеланиями Д-р Харви М. Дейтел Пол Дж. Дейтел Шоп Э. Сзнтри Об авторах Д-р Харви М. Дейтел, руководитель фирмы Deitel & Associates, Inc., обладает 40-летним опытом в области информатики компьютерной техники, включая обширный опыт работы в науке и промышленности. Он — один из ведущих в мире инструкторов и руководителей семинаров в области компьютерных наук. Д-р Дейтел получил степень бакалавра и магистра В Массачусетсом Технологическом институте и степень доктора философии в Бостонском университете. Он работал над первыми проектами операционных систем с виртуальной памятью в компании IBM и Массачусетском Технологическом институте, где были разработаны технологии, ныне широко используемые в таких системах, как UNIX, Linux и Windows NT. Он обладает 20-летним стажем преподавания в колледже, в том числе в качестве председателя Департамента компьютерных наук в Бостонском колледже до создания компании Deitel & Associates, Inc. совместно с Полом Дж. Дейтелем. Он является автором или соавтором нескольких десятков книг и мультимедийных курсов, он продолжает писать и сейчас. Работы д-ра Харви М. Дейтела были опубликованы в переводах на японский, русский, испанский, китайский, корейский, французский, польский, португальский и итальянский языки, они получили международное признание. Д-р Дейтел проводил профессиональные семинары по всему миру для сотрудников крупных корпораций, правительственных и военных учреждений.
24 Технологии программирования на Java 2 Пол Дж. Дейтел, Главный Технолог компании Deitel & Associates, Inc., закончил Школу менеджмента Слоун Массачусетского Технологического института, где он изучал информационные технологии. Работая в компании Deitel & Associates, Inc., он преподавал программирование на Java, С, С—К а также обучал созданию приложений для Internet и Всемирной паутины сотрудников различных организаций, в том числе Compaq, Sun Microsystems, White Sands Missile Range, Rogue Wave Software, Computerviaion, Stratus, Fidelity, Cambridge Technology Partners, Boeing, Lucent Technologies, Adra Systems, Entergy, CableData Systems, NASA (Космический центр имени Кеннеди), National Severe Storm Laboratory, IBM, и многих других организаций. Он читал лекции по C++ и Java на Бостонском отделении Ассоциации по вычислительной техники, а также вел курсы по Java в совместном предприятии Deitel & AssociatBS, Inc., Prentice Hall и Technology Education Network. Он и его отец, д-р Харви М. Дейтел, авторы учебников по компьютерным наукам, ставших мировыми бестселлерами. Шон Э. Сэатри, директор по разработке программных продуктов компании Deitel & Associates, Inc., выпускник Бостонского колледжа, где он изучал компьютерные науки и философию. Обучаясь в колледже, он выполнил оригинальное исследование по применению метафизических систем при проектированию объектно-ориентированного программного обеспечения. Работая в компании Deitel & Associates, Inc., он преподавал ознакомительные и углубленные курсы для сотрудников таких промышленных компаний, как Sun Microsystems, EMC2, Dell, Compaq, Boeing и других. Он принимал участие в создании ряда публикаций Deitel, таких как Java Bow 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 e-Commerce How to Program (Как программировать приложения для электронного бизнеса и электронной коммерции) и e-Buainens and e-Commerce for Managers (Электронный бизнес и электронная коммерция для менеджеров). До поступления на работу в Deitel & Associates он разрабатывал приложения для электронного бизнеса в одной из ведущих в регионе Бостона консалтинговых фирм. О компании Deitel & Associates, Inc. Компания Deitel & Associates, Inc. является широко известной организацией, осуществляющей обучение и специализирующейся на языках программирования, Web-технологиях, объектных технологиях, электронном бизнесе и коммерции. Компания Deitel & Associates, Inc. является членом консорциума Всемирной паутины (W3C). Компания осуществляет обучение современным курсам программирования на Java™, C++, Visual Basic*, С, С#, Perl, Python, XML™, для Internet и Всемирной паутины, объектным технологиям, созданию приложений для Internet и Всемирной паутины, электронного бизнеса и электронной коммерции. Руководят компанией Deitel & Associates, Тпс. д-р Харви М. Дейтел и Пол Дж. Дейтел. В числе клиентов компании многие крупнейшие компьютерные компании, правительственные учреждения, отделения военных и коммерческих организаций. Благодаря сотрудничеству с издательством Prentice Hall, компания Deitel & Associates, Inc. издает современные учебники по программированию, книги для профессионалов, интерактивные мультимедийные курсы из серии Cyber Classrooms на CD-ROM и дистанционные обучающие курсы. С компанией Deitel & Associates, Inc. и авторами можно связаться по электронной почте по адресу deitelSdeitel.com
Предисловие 25 Чтобы больше узнать о компании Deitel & Associates, Inc., ее публикациях и программах обучения, посетите сайт: www.deitel.com Компания Deitel & Associates, Inc. предлагает конкурсные вакансии в своей Программе интернатуры для студентов в Бостонском регионе. Для нс-лучения информации, пожалуйста, свяжитесь с Эбби Дейтел по электронному адресу deitel@deitel.com. Желающие приобрести книги Х.М. Дейтела и П.Дж. Дейтела и учебные курсы (Complete Training Courses) могут сделать это на сайте www.deitel.com Консорциум World Wide Web (W3C) Г» Компания Deitel & Associates, Inc. является членом консорциума World Wide Web Consortium (W3C). W3C был основан в 1994 г. \л «дли разработки протоколов для развития Всемирной паутины». Как член W3C, мы имеем представительство в Совещательном комитете W3C (нашим представителем в Совещательном комитете является Пол Дейтел). Члены Совещательного комитета помогают обеспечивать «стратегическое направление» деятельности W3C через проведение конференций и встреч по всему. Организации-члены также помогают разрабатывать рекомендации по стандартам для Web-технологий (таких как HTML, XML) путем участия в деятельности W3C и рабочих группах. Членами W3U могут быть крупные компании и организации. Для получения информации о том, как стать членом W3C, посетите сайт www .w3.org/Consortium /Prospe с t us/J oinin g.
/ Введение Цели • Познакомиться со структурой книги. • Узнать об особенностях работы с примерами. • Получить представление о паттернах проиктирования и способах их применения в контексте данной книги. • Познакомиться с кратким путеводителем по книге. Прежде чем начать, следует все тщательно спланировать. Марк Туллий Цицерон Дела лучше всего идут в самом начале. Близ Паскаль Высокие мыли должны излагаться высоким языком. Аристофан Наша жизнь растрачивается на мелочи... Упрощайте, упрощайте. Генри Торо Благоволите смелому началу. Вергилий Думаю, я начинаю кое-что в этом понимать. Огюст Ренуар
28 Глава 1 • гкч. й.2:1:у'сЩ|ернЁ1е пр^оженияи^ргсерЩОй •-. ^и1.2;2Й1СорГ10рэтив1№1еприлож€Нйя(Еп4^гюте Java, "13:^ .,Кра1»сии|Г1>Ш^Дит^|»:Й0 книге/ -:%y#.-. ■r r^v ^ ^^^бШЙСГОР^ . Г ^1*5;2^0вд!йр^|йттернов; проектирования fei .---?■ . : J; ■-■ -"•кегельного выполнения л.: .< те1<турнь»ш паттерны проектирования , 1.1. Введение *а^ е ресурсы по. паттернам проектирования Добро пожаловать в мир разработки приложений на платформе Java 2! Мы много потрудились, чтобы создать книгу, которая, как мы надеемся, будет информативной, увлекательной и познавательной1. Технологии Java, с которыми вы познакомитесь, предназначены для разработчиков и программных средств. Книга рассчитана на читателя, который имеет представление об основах Java и объектно-ориентированном программировании, прочитав, например, книгу Как программировать на Java. Издание 4-е, В нашей книге многие вопросы программирования на Java рассматриваются более глубоко. Также обсуждается ряд новых тем, сопровождаемых несколькими тысячами строк исходного кода и многочисленными иллюстрациями, позволяющими лучше понять принципы программирования. Рассматриваемые технологии нашли свое отражение в корпоративных системах, демонстрирующих эти технологии во взаимодействии. Подобный подход мы называем методом «живого кода» (live-code™). Мы познакомимся с технологиями, входящими в состав трех редакций Java: Java 2 Standard Edition (J2SE), Java 2 Enterprise Edition (J2EE) и Java 2 Micro Edition (J2ME). В книге основное внимание уделяется дополнительным функциональным возможностям J2EE, и предоставляются примеры корпоративных приложений. Помимо этого, мы рассмотрим передовые технологии J2ME и средства программирования приложений с беспроводным доступом. Данная книга является переводом третьей части книги «Advanced Java™ 2 Platform. How to Program». Оригинал содержит более 1800 страниц, поэтому было принято решение русское издание разбить на три части. Третья часть, Которую Вы дернейте в руках, посвящена созданию серверных приложений и корпоративных систем. Первая часть посвящена программированию графического пользовательского интерфейса, двухмерной и трехмерной графике, взаимодействию с базами данных, компонентам JavaBeans. Вторая часть посвящена созданию распределенных приложений. — Прим.. ред.
Введение 29 Объектно-ориентированное программирование и паттерны проектирования играют важную роль в разработке приложений и программных систем с использованием многих рассматриваемых в книге технологий. Эти инструментальные средства обеспечивают модульность, давая возможность программистам эффективно разрабатывать классы и программы. Паттерны проектирования особенно важны для построения серьезных программ, которые мы представляем в этой книге. Многие из рассматриваемых в этой книге приложений используют возможности расширяемого языка разметки Extensible Markup Language (XML), который фактически является стандартом для создания языков разметки, позволяющим описывать структурированные данные не зависимым от используемой платформы образом. XML могут использовать все приложения, начиная от обычных Web- страниц и заканчивая сложными системами предприятие предприятие (business- to-business — В2В). Принцип переносимости данных XML дополняет принцип переносимости программ, разработанных для платформы Java 2. Возможности XML для описания данных позволяют системам, построенным на основе различных технологий, совместно использовать данные, не заботясь о совместимости на уровне машинного кода, что очень важно для разработки сложных, в том числе распределенных систем на Java. Мы предполагаем, что читатель имеет представление о XML и об интерфейсе прикладного программирования XML Java, Для тех, кто не знаком с этими вопросами, рекомендуем предварительно прочесть какую-либо книгу по XML1. По мере чтения книги у вас может возникнуть желание обратиться на наш Web-сайт www.deitel.com для получения обновлений и дополнительной информации по передовым технологиям, которые вы будете изучать. 1.2. Архитектура книги 1.2.1. Серверные приложения и Web-сервисы Главы 2-5, 14 Популярность Всемирной паутины и значимость ее для ведения бизнеса за последние несколько лег резко возросли. Web-сервисы дают возможность осуществлять совместное использование информации, ведение бизнеса и иные взаимодействия компаний между собой и компаний с потребителями с использованием стандартных Web-протоколов. Web-сервисы эволюционировали из существующих Web-технологий, таких как формы HTML, и корпоративных технологий, таких как системы обмена сообщениями и электронными документами (EDI). Web-сервисы основаны на существующих протоколах и стандартах. Глава 2 знакомит с сервлетами. Сервлеты способны динамически генерировать документы (например, XHTML-документы), отправляемые клиентам в ответ на их запросы на получение информации. В главе 3 вы познакомитесь с серверными страницами Java Server Pages (JSP)y которые также осуществляют доставку клиентам динамического содержимого. JSP-страницы динамически обрабатывают Web-еодержимоё с помощью скриптлетов (scriptlets) и компонентов JavaBeans в контексте документа. Эти документы транслируются в сервлеты контейнером JSP, который представляет собой серверное приложение, ответственное за обработку запросов к JSP-страницам. В главе 4 представлен практический пример, в котором нашли воплощение технологии, рассмотренные в главах 2 И 3. В нем объединены возможности компонентов JavaBeans, сервлетов, JSP-страниц, XML и XSLT для создания книжного Internet-магазина. 1 Можно порекомендовать читателю книгу Х.М. Дейтел, П,Дж. Дейтел, Т.Р. Нието и др. «Как программировать на XML*. М.г Издательство Бином, 2001 г., 944 с. — Прим.ред.
30 Глава 1 Для поддержки работы с беспроводными устройствами было создано несколько новых технологий, таких как Wireless Application Protocol (WAP), Wireless Markup Language (WML), i-mode и Java 2 Micro Edition (J2ME). В главе 5 вы познакомитесь с этими технологиями и примените их для построения трехуровневого приложения, в котором сервлеты и XML используются для доставки содержимого нескольким типам беспроводных устройств. Глава 14 знакомит с Web-сервисами — приложениями, предоставляющими общедоступные интерфейсы, которые могут использоваться другими приложениями в Web. Web-сервисы доступны через протокол HTTP и другие Web-протоколы и осуществляют обмен информацией с. помощью XML-сообщений, Службы каталогов дают возможность клиентам выполнять поиск для обнаружения доступных Web-сервисов. Протокол простого доступа к объектам Simple Object Access Protocol (SOAP) использует XML для предоставления многим Web-сервисам средств коммуникационного взаимодействия. SOAP дает возможность приложениям осуществлять удаленные вызовы процедур для обращения к общедоступным методам Web-сервиса. В этой главе будет реализован сервис, который предоставляет информацию о погоде в различных регионах США, получая эти сведения с сервера Национальной метеослужбы National Weather Service с помощью SOAP. 1.2.2. Корпоративные приложения Enterprise Java Главы 6—8, 13 Язык Java стал чрезвычайно популярен для построения корпоративных приложений. Изначально Java задумывался как язык программирования для создания небольших программ, встраиваемых в Web-страницы, но постепенно Java разросся до мощного языка разработки корпоративных приложений. Согласно заявлению Sun Microsystems, на сегодняшний день более 95% корпоративных систем поддерживают технологию J2EE. Бизнес-логика образует ядро функциональных возможностей корпоративного приложения. Бизнес-логика отвечает за реализацию сложных бизнес-правил, которые необходимы для проведения транзакций и обработки информации. В главе 6 рассказывается о компонентной модели Enterprise JavaBeans (EJB) и применении ее для построения бизнес-логики корпоративных приложений. В частности, будут рассмотрены сеансовые компоненты EJB для бизнес-логики и распределенные транзакции, которые позволяют EJB-компонентам работать с несколькими базами данных, обеспечивая при этом целостность данных. В главе 7 будут рассмотрены EJB-компоненты управления данными (EJB-сущности), которые дают возможность разработчикам формировать объектный уровень для доступа к информации, содержащейся в долговременном хранилище, таком как база данных. Корпоративные приложения требуют для своей работы многочисленных сервисов и средств поддержки выполнения для доступа к базам данных, проведения распределенных транзакций, управления производительностью и т.д. Серверы приложений предоставляют окружение выполнения для компонентов корпоративного приложения. В главе 13 вы познакомитесь с тремя наиболее популярными коммерческими серверами приложений: BEA WebLogic, IBM WebSphere и iPlanet Application Server. Мы также предоставим полные инструкции для развертывания на серверах BEA WebLogic и IBM WebSphere практического примера корпоративного приложения.
Введение 31 1.2.3. Практический пример корпоративного приложения Главы 9-12 В главах 9-12 представлено приложение, в котором нашли отражение все основные темы по Enterprise Java, рассматриваемые в книге. Это практический пример применения многих технологий Java в большом приложении (почти 10000 строк кода) для книжного Internet-магазина. Приложение Deitel Bookstore построено с использованием компонентов Enterprise JavaBeans с перс и стентн остью (сохранемостью), управляемой контейнером сервлетов, а также с использованием технологий RMI-ПОР, XML, XSLT, XHTML, WML и cHTML. Главной особенностью этого примера является применение XML и XSLT для обеспечения поддержки клиентов практически любого типа, включая стандартные Web-браузеры и мобильные устройства, такие как сотовые телефоны. Расширяемая модульная архитектура дает возможность разработчикам добавлять поддержку для новых типов клиентов, просто предоставляя соответствующих XSLT-документы, которые транслируют XML-документы в содержимое, подходящее для клиентов этого типа. Практический пример Deitel Bookstore также демонстрирует архитектуру модель— вид—контроллер (model—view—controller — MVC) в контексте приложения Enterprise Java. 1.3. Краткий путеводитель по книге В этом разделе мы вкратце пройдемся по главам книги и выделим многочисленные технологии Java, которые обсуждаются в книге. Возможно, вы встретитесь с терминами, которые окажутся для вас незнакомыми, — они будут определены в последующих главах книги. В конце многих глав имеется раздел «Ресурсы в Internet и во Всемирной паутине», где перечислены Web-сайты, которые вам следует посетить, чтобы получить дополнительную информацию по обеуждаемым в этой главе технологиям. Вы также можете посетить Web-сайты www.deitel.com и www.prenhall.com/deitel, где получите последнюю информацию, сведения об ошибках и опечатках и узнаете о дополнительных информационных и обучающих ресурсах. Глава 1. Введение Эта глава содержит обзор технологий, представляемых в книге, и знакомит с архитектурой книги. Мы включили в эту главу краткий путеводитель по книге с обзором каждой из глав. Для рассматриваемых в книге примеров мы приводим инструкции по установке и выполнению. Здесь же вкратце обсуждаются паттерны проектирования и принципы их применения в наших примерах. Глава 2. Сервлеты Сервлеты расширяют функциональные возможности серверов (обычно Web- серверов). Сервлеты эффективны при разработке Web-приложений, в которых осуществляется взаимодействие с базами данных в интересах клиента, динамически генерируется содержимое, отображаемое браузерами, и сохраняется уникальная для каждого клиента информация о сеансе. Многие разработчики осознают, что сервлеты являются верным выбором для интенсивно работающих с базами данных приложений, которые взаимодействуют с так называемыми топкими клиента ми — приложениями, которые требуют минимальной обработки на стороне клиента. Клиенты осуществляют соединение с сервером с помощью стандартных протоколов, таких как HyperText Transfer Protocol (HTTP), доступных для Web-браузеров большинства клиентских платформ. Таким образом, логика приложения может быть написана один раз и размещена на сервере для доступа к ней клиентов.
32 Глава 1 Интерфейс прикладного программирования API Java Servlet позволяет разработчикам добавлять в Web-серверы функциональные возможности для обработки клиентских запросов. В отличие от интерфейса Common Gateway Interface (CGI), в котором для каждого клиентского запроса может быть запущен отдельный процесс, сервлеты обычно являются потоками одного процесса в виртуальной машине Java (JVM). Сервлеты также допускают многократное применение на различных Web-серверах и на различных платформах. В этой главе демонстрируется механизм запрос/ответ (главным образом, посредством HTTP-запросов get и post), возможности отслеживания состояния сеанса, переадресации запросов другим ресурсам и взаимодействия с базами данных через JDBU. Глава 3. Java Server Pages (JSP) В этой главе рассматривается расширение технологии сервлетов под названием Java Server Pages (JSP), Технология JSP позволяет осуществлять доставку динамически генерируемого Web-содержимого и используется главным образом для разработки логики визуального представления данных в корпоративных приложениях Enterprise Java. JSP-страницы могут содержать код Java в виде скриптле- тов, а также могут использовать компоненты JavaBeans. Библиотеки нестандартных тегов дают возможность дизайнерам Web-страниц, не знакомым с Java, усовершенствовать Web-страницы за счет добавления динамического содержимого и новых возможностей обработки, создаваемых разработчиками на Java. Для повышения производительности каждая JSP-страница компилируется в сервлет Java; это обычно происходит при первом запросе JSP-страницы клиентом. Последующие запросы клиента обрабатываются уже откомпилированным сервлетом. Глава 4. Книжный Internet-магазин, реализованный с использованием, сервлетов и JSP Эта глава является основополагающей для изучения технологий JSP и сервлетов. В ней мы реализуем Web-приложение для книжного Internet-магазина, в котором интегрированы технологии JDBC, XML, JSP и сервлетов- В этой главе осуществляется развертывание приложения для книжного Internet-магазина на сервере приложений эталонной реализации J2EE 1.2.1. В состав эталонной реализации J2EE 1.2.1 входят средство Apache Tomcat JSP и контейнер сервлетов. Изучив эту главу, вы сможете реализовать большое распределенное Web-приложение с множеством компонентов, а также осуществить развертывание этого приложения на сервере приложений J2EE 1,2.1. Глава 5. Разработка приложений для беспроводной связи на базе Java и J2 ME Одной из тем, вызывающих особый интерес при изучении вопросов создания приложений для электронного бизнеса и электронной коммерции, является технология беспроводного коммуникационного взаимодействия через Internet. Беспроводные технологии превращают электронный бизнес (e-business) в мобильный бизнес ( m-business ). Они позволяют вам подключаться к Internet в любое время и практически в любом месте. Вы можете использовать эти технологии для проведения сетевых транзакций, покупок товаров в Internet-магазинах, игры на бирже и отправки сообщений электронной почты. Новые технологии дают возможность организовать мобильный офис, в котором компьютеры, телефоны и другое офисное оборудование подключены к сети без помощи кабелей. Эта глава знакомит с некоторыми наиболее популярными беспроводными технологиями, такими как WAP, i-mode и Java 2 Platform Micro Edition™ (J2ME). J2ME расширяет технологию Java на встроенные устройства и бытовые устройства, имеющие ограниченные мощности по обработки данных и небольшие ресурсы памяти. J2ME имеет в своем составе специализированные API для многих бытовых устройств, таких как сото-
Введение 33 вые телефоны, смарт-карты, приборы, имеющие возможность подключения в Internet, и карманные компьютеры, например, Palm™ и PocketPC. Система К Virtual Machine представляет собой усеченную версию виртуальной машины Java для бытовых устройств, которая обеспечивает возможности для выполнения Java-программ на этих устройствах. Мы рассмотрим практический пример трехуровневого приложения с использованием сервлетов и XML, которое способно работать с беспроводными устройствами нескольких различных типов. Глава 6, Сеансовые компоненты EJB и распределенные транзакции Компоненты Enterprise JavaBeans (EJB) дают возможность разработчикам на Java строить надежные и устойчивые многоуровневые приложения, В многоуровневом приложении ответственность по предоставлению услуг клиенту может быть поделена между несколькими серверами. Типовое двухуровневое приложение состоит из уровня клиента и уровня сервера. В трехуровневом приложении в качестве среднего уровня между клиентским Web-браузером и сервером базы данных часто используется сервер приложений. Компоненты Enterprise JavaBeans предоставляют инфраструктуру для построения реализаций бизнес-логики среднего уровня. Используя RMT и контейнеры EJR, компоненты Enterprise JavaBeans также способствуют распределению средств бизнес-логики по различным узлам в сети. Мы познакомимся с компонентами Enterprise JavaBeans, которые обеспечивают компонентную модель для построения бизнес-логики в корпоративных приложениях Java. Будут рассмотрены две формы сеансовых компонентов EJB: с состоянием и без состояния, и продемонстрировано, как разрабатывать сеансовые компоненты EJB. Мы также познакомимся с поддержкой EJB-компонентами распределенных транзакций, которая помогает обеспечить целостность данных в базах данных и на серверах приложений. Мы покажем, как создавать EJB-kom- поненты, которые используют средства поддержки распределенных транзакций J2EE для атомарного обновления данных в нескольких базах данных. Глава 7. EJB-компоненты с данными В этой главе будет продолжено наше обсуждение технологии Enterprise JavaBeans и рассмотрены компоненты Enterprise JavaBeans с данными, или компоненты-сущности EJB. В отличие от сеансовых компонентов EJB, компоненты-сущности EJB содержат информацию в долговременных хранилищах, таких как базы данных. Компоненты-сущности EJB обеспечивают объектно-ориентированное представление постоянных данных, таких как данные, хранящиеся в реляционных базах даяных или в наследованном приложении. Компоненты-сущности EJB могут быть использованы для построения мощных и гибких приложений для работы с данными. Имеется два типа компонентов EJB с данными: с персистентно- стью (сохраняемостью), управляемой самим компонентом и с персистентно- стъю, управляемой контейнером. Компоненты-сущности EJB, которые используют управление персистентностью на стороне компонента, сами реализуют код для сохранения и извлечения данных из источников данных. Например, компонент-сущность EJB, использующий персистентность, управляемую компонентом, может использовать JDBC для сохранения и извлечения данных из реляционной базы данных. Для компонентов-сущностей EJB, в которых используется персистентность, управляемая контейнером, обращения к данным, хранящимся в постоянном источнике, реализуются контейнером EJB. Разработчик должен предоставить информацию о постоянном источнике данных при развертывании EJB-kom- поиента. В этой главе демонстрируются оба типа компонентов-сущностей EJB. Глава 8. Обмен сообщениями с помощью Java Message Service (JMS) Технология Java Message Service (JMS) предоставляет интерфейс прикладного программирования для интегрирования корпоративных приложений Java с сие-
34 Глава 1 темами промежуточного программного обеспечения, ориентированными на работу с сообщениями (message-oriented middleware —- MOM), Подобные системы дают возможность приложениям взаимодействовать посредством отправки друг другу сообщений. Промежуточное программное обеспечение, ориентированное на работу с сообщениями, является популярной технологией для построения слабосвязанных приложений. Эта глава знакомит с двумя основными моделями систем обмена сообщениями: *от точки к точкеь и «издатель/подписчика. Мы продемонстрируем интерфейсы Java для обеих моделей. Мы также познакомимся с EJB-компо- нентами, управляемыми сообщениями, которые являются новой особенностью, появившейся в версии 1.3 J2EE. Глава 9. Практический пример корпоративного приложения. Обзор архитектуры Технологии в составе Java 2 Enterprise Edition (J2EE) позволяют разработчикам создавать устойчивые, расширяемые корпоративные приложения. В этом практическом примере мы построим приложение для электронного бизнеса, используя ряд функциональных возможностей J2EE, таких как сервлеты, компоненты Enterprise JavaBeans, XML и XSLT. Мы также интегрируем в наше приложение технологию беспроводного доступа к информации, в частности, WAP/WML и i-mode/cHTML. В этой главе содержится обзор архитектуры приложения Deitel Bookstore, в котором используются паттерны проектирования MVC в контексте корпоративного приложения. R последующих главах мы рассмотрим логику управления, реализованную с помощью сервлетов (глава 10), бизнес-логику и абстракцию данных, реализованные с помощью компонентов EJB (главы 11 и 12). Глава 10. Практический пример корпоративного приложения. Логика представления данных и логика управления В этой главе описана реализация логики управления и визуального представления данных для книжного Internet-магазина Deitel Bookstore. Логика управления (контроллер) представляет собой приложение, ответственное за обработку клиентских запросов. Логика управления в этом приложении реализована на сервле- тах Java. Каждый запрос пользователя обрабатывается сервлетом, который предпринимает соответствующее действие в зависимости от типа поступившего запроса (например, запрос на просмотр каталога товаров) и представляет содержимое клиенту. Мы используем XSLT-трансформации для реализации логики визуального представления данных для приложения. После вызова методов бизнес-логики для обработки клиентских запросов сервлет генерирует XML-документы, которые включают в себя содержимое, отображаемое клиенту. Эти XML-документы не являются специфичными для конкретных типов клиентов (например; Web-браузер, сотовый телефон и т.д.); они просто описывают данные, предоставленные бизнес-логикой. XSLT-трансформация применяется к XML-документам для отображения пользователю информации в соответствующем формате. Например, XSLT- трансформация может генерировать XHTML-документ для отображения его в Web-браузере, либо WML-документ для отображения его в WAP-браузере. XSLT-трансформащш необходимо выполнять для каждого поддерживаемого приложением типа клиента. Мы можем добавлять в приложение поддержку других типов клиентов, просто реализовав дополнительные таблицы стилей и отредактировав файл конфигурации. Глава 11. Практический пример корпоративного приложения. Бизнес-логика, часть 1 В этой главе мы рассмотрим бизнес-логику, реализующую магазинную тележку покупателя в модели Internet-магазина Doitel Bookstore, а также и компоненты-сущности EJB для хранения информации о товарах. Главное назначение при-
Введение 35 ложения для Internet-магазина — дать возможность пользователям покупать товары. EJB-компонент бизнес-логики реализует бизнес-правила, которые управляют этим процессом. Мы реализуем бизнес-логику для управления группой товаров, которые пользователь хотел бы купить, в виде EJB-компонента ShoppingCart. EJB-компонент ShoppingCart устанавливает бизнес-правила, которые определяют, каким образом товары добавляются в магазинную тележку, каким образом создаются магазинные тележки, и каким образом потребители совершают свои покупки. Мы также рассматриваем компоненты-сущности EJB, которые представляют товары в Internet-магазине и информацию о доставке заказов. Изучив эту главу, вы поймете, как использовать EJB-компоненты в контексте приложения для электронного бизнеса, а также получите представление о дополнительных возможностях EJB-компонентов, таких как собственные классы первичных ключей и отношения типа -«многие ко многим*. Глава 12. Практический пример корпоративного приложения. Бизнес-логика, часть 2 В этой главе мы рассмотрим бизнес-логику для управления взаимодействием с покупателем в нашем Internet-магазине Deitel Bookstore. Наличие информации о покупателях Internet-магазина может сделать покупки более удобными для пользователя. Информации об оплате и доставке при этом сохраняется на сервере. Отдел маркетинга Internet-магазина также может использовать собранные данные для распространения рекламных материалов и анализа демографического состава покупателей. Мы также представляем компонент-сущность EJB, который генерирует уникальные идентификаторы для EJB-компонентов Customer, Order и Address. Экземпляры этих EJB-компонентов создаются, когда потребитель проходит регистрацию, и когда потребитель размещает новые заказы. В реляционных базах данных необходимо иметь уникальные первичные ключи для обеспечения ссылочной целостности и выполнения запросов. Мы предоставляем EJB-компонент Seq- uenceFactory для генерирования этих уникальных идентификаторов, поскольку не все базы данных могут автоматически генерировать значения составных первичных ключей. Глава 13. Серверы приложений Эта глава знакомит с несколькими серверами приложений: программным обеспечением, которое интегрирует серверные компоненты логики, создавая условия для взаимодействия между различными компонентами и уровнями в архитектуре программы. Серверы приложений также управляют персистентностью, жизненным циклом, безопасностью и другими сервисами для логических компонентов. Мы обсудим концепции, связанные с серверами приложений, и рассмотрим три популярных коммерческих сервера приложений: BEA WebLogic, IBM WebSphere и iPlanet Application Server. Мы пройдем через все этапы развертывания приложения Deitel Bookstore на серверах BEA WebLogic и IBM WebSphere. На момент подготовки книги к публикации компания iPlanet выпустила новую версию сервера приложений. Пожалуйста, посетите сайт www.iplanet.com/ias-deitel, чтобы загрузить последнюю версию. Полные инструкции по развертыванию для учебного приложения Deitel Bookstore на сервере iPlanet можно будет найти на нашем Web-сайте www.deitel.coni. Глава 14. Введение в Web-сервисы и SOAP Функциональная совместимость, или беспроблемное взаимодействие между различными программными системами, является главной заботой компаний и организаций, которые используют в своей деятельности компьютерные и сетевые технологии. Эта глава знакомит с Web-сервисами, использующими протокол простого доступа к объектам Simple Object Access Protocol (SOAP), который призван
36 Глава 1 разрешить упомянутую выше проблему. Web-сервисы могут представлять собой приложения, доступные через Web, например, Web-страницы с динамическим содержимым. В более узком смысле Web-сервисы обеспечивают общедоступные интерфейсы для использования их Web-приложениями. SOAP — это протокол, который использует XML для осуществления удаленных вызовов процедур через HTTP, чтобы обеспечить совместимость между различными Web-приложениями. 1.4. Выполнение примеров1 Многие из примеров программ а книге являются достаточно сложными и требуют специального программного обеспечения. Так, в главах 9—12 представлен комплексный практический пример, для которого требуется сервер приложений, обеспечивающий окружение выполнения и сервисы для корпоративного приложения. Для этого комплексного примера также требуется база данных. Мы обновляем инструкции по установке программного обеспечения на нашем сайте www.deitel.com по мере выпуска новых версий Java SDK корпорацией Sun. При написании этой книги мы ориентировались на версию 1.2.1 эталонной реализации Java 2 Enterprise Edition. На сегодняшний день доступна версия 1.3, имеющая ряд усовершенствований и нововведений. Например, в версии 1.3 реализован сервис обмена сообщениями Java Messaging Service (JMS 1.0.2), технология J2EE Connector Technology и Java API for XML Processing (JAXP 1.1). В технологии сервлетов Java Servlets (версия 2.3) реализованы фильтры, упрощенная инфраструктуру передачи данных для запросов и ответов, мониторинг жизненных циклов приложений и усовершенствованная многоязыковая поддержка. В реализации Java Server Pages (версия 1.2) усовершенствована поддержка выполнения для библиотек тегов и проверки JSP-страницы на этапе трансляции. Реализация технологии Enterprise JavaBeans в версии 1.3 (EJB 2.0) поддерживает управление корпоративными компонентами с помощью сообщений, а также обеспечивает совместимость между контейнерами EJB и технологией управления персистентно- стью на стороне контейнера Container-Managed Persistence 2.0. В примерах в книге используется стандартное соглашение по именованию пакетов. Мы помещаем каждый из примеров в соответствующим образом именованный подпакет пакета com.deitel. Например, в объявлении package com.deitel.advjhtpl,gui.webbrowser; акроним advjhtpl в имени пакета соответствует начальным буквам слов в оригинальном названии книги (Advanced Java 2 Platform How to Program), 1 указывает на 1-е издание книги. Такая структура пакета требует, чтобы вы компилировали примеры с использованием соответствующей структуры каталогов. Управление пакетами с помощью компилятора командной строки Java и с помощью инструментальных средств может оказаться излишне обременительным, поэтому мы рекомендуем читателям использовать интегрированные среды разработки, чтобы упростить создание и выполнение примеров и упражнений в этой книге. Мы использовали для работы с примерами в этой книге интегрированную среду разработки Sun Forte for Java Community Edition, которая является производной от NetBeans (www.netbeans.org). Рекомендации по установке Forte и его использованию для разработки приложений вы можете найти, обратившись к справочной системе Forte или к документации по адресу www.sun.com/forte/ffj/documentation/index.html Примеры к книге можно загрузить по адресу http://www.deitel.com/books/downlo- ads.html (раздел, посвященный книге Advanced Java™ 2 Platform How to program), a также по адресу http://www.binom-press.ru/books/adv-java2.htni. — Прим. ред.
Введение 37 Большинство интегрированных сред разработки Java дают возможность разработчикам загружать структуры каталогов, содержащие пакеты Java, Чтобы облегчить работу с кодом, мы в примерах сохраняем полную структуру каталогов с соответствующим размещением исходных файлов. Мы рекомендуем вам скопировать эту структуру каталогов на жесткий диск своего компьютера. Скопировав структуру каталогов, вы можете загружать примеры в соответствии с инструкциями для вашей интегрированной среды разработки.1 1.5. Паттерны проектирования Большинство примеров, представляемых в книгах по Java начального уровня — таких как наша книга Как программировать на Java. Издание 4-е — содержат не более 150 строк кода. Эти примеры практически не требуют какого-либо проектирования, поскольку они используют всего несколько классов и иллюстрируют элементарные приемы программирования. Однако большинство программ в настоящей книге например, трехуровневое приложение для беспроводного доступа (глава 5) и приложение для книжного Internet-магазина Deitel Bookstore (главы 9-12), являются гораздо более сложными. Такие большие приложения могут потребовать для реализации тысячи строк кода, содержать множество взаимных связей между объектами и взаимодействий с пользователями. Для таких программ важно применять проверенные, эффективные стратегии проектирования. Системы для управления банкоматами или системы управления воздушным движением могут содержать миллионы или даже сотни миллионов строк кода. Эффективное проектирование крайне важно при разработке таких сложных систем. За последнее десятилетие индустрия разработки программного обеспечения добилась большого прогресса в области паттернов проектирования — проверенных архитектур для построения гибкого и легко управляемого объектно-ориентированного программного обеспечения [I].2 Применение паттернов проектирования может значительно уменьшить сложность процесса проектирования. Хорошо спроектированное объектно-ориентированное программное обеспечение дает возможность разработчикам повторно использовать и интегрировать ранее созданные компоненты в другие системы. Паттерны проектирования дают следующие преимущества для разработчиков: • Помогают создавать надежное программное обеспечение с использованием проверенных архитектур и накопленного опыта их практического применения. • Способствуют повторному использованию кода и проектных решений во вновь создаваемых системах. • Помогают выявлять типичные ошибки и заблуждения, которые имеют место при построении систем. • Помогают разрабатывать системы вне зависимости от языков, на которых они в конечном итоге будут реализованы. • В процессе проектирования устанавливают единый «словарь» для разработчиков. • Сокращают длительность фазы проектирования в процессе разработки программного обеспечения. Имена папок с примерами не соответствуют нумерации глав, аринятой в этой книге. Обратитесь к таблице соответствий в «Предисловии редактора русского перевода». — Прим. ред. См. раздел «Использованные источники» в конце главы. — Прим. ред.
38 Глава 1 Идея применения паттернов проектирования при проектировании программных систем позаимствована из архитектуры. Архитекторы используют готовые архитектурные элементы, такие как арки и колонны, при проектировании зданий. Проектирование с применением арок и колонн — проверенная стратегия при строительстве надежных сооружений. Эти элементы можно рассматривать как архитектурные паттерны проектирования. 1.5.1. История паттернов проектирования В течение 1991-1994 гг. Эрик Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес — вместе они известны как «группа четырех» — объединили свой опыт для написания книги Design Patterns, Elements of Reusable Object-Oriented Software (Addison-Wesley: 1995). В этой книге было показано, что паттерны проектирования естественным образом эволюционировали на протяжении развития отрасли. Джон Влиссидес утверждает, что «наиболее важным моментом при создании паттерна проектирования является трезвое размышление [2]». Это означает, что для создания паттернов разработчики должны типизировать и документировать свои успехи (и неудачи) при проектировании и реализации программных систем. Разработчики используют паттерны проектирования, чтобы заимствовать и применять этот коллективный опыт, который в конечном итоге помогает им совместно с другими разработчиками использовать успешные решения. В книге, написанной «группой четырех», описывается 23 паттерна проектирования, каждый из которых предоставляет решение одной из типовых задач при разработке программного обеспечения. В книге паттерны проектирования разбиты на три категории: порождающие, структурные и поведенческие. Эти паттерны проектирования перечислены в таблице на рис. 1.1. Порождающие Abstract Factory Builder Factory Method Prototype Singleton Структурные Adapter Bridge Composite Decorator Facade Flyweght Proxy Паттерны поведения Chain-of-Responsibility Command Iterator Interpreter Observer Mediate Memento State Strategy Template Method Visitor Рис. 1.1. Паттерны проектирования, рассмотренные «группой четырех» е своей книге Порождающие паттерны описывают способы реализации экземпляров объектов (или групп объектов). Эти паттерны проектирования решают задачи, связанные с созданием объекта, например, препятствование созданию системой более одного объекта класса (паттерн проектирования Singleton) или отсрочка принятия решения, какие типы объектов создавать, до момента выполнения (паттерн проектирования Factory Method). Например, предположим, что мы разрабатываем программу для трехмерного рисования, в которой пользователь может создавать несколько
Введение 39 трехмерных геометрических объектов, таких как цилиндры, сферы, кубы, тетраэдры и т.д. На этапе компиляции программа не может получить информацию, какие типы фигур пользователь выберет для добавления в рисунок. На основе данных, введенных пользователем в процессе выполнения, эта программа должна определить класс, для которого нужно создать экземпляр объекта. Если пользователь решил создать цилиндр, программа должна «знать», что следует создать экземпляр объекта класса Cylinder и добавить его в рисунок. Когда пользователь решает, какой геометрический объект рисовать, программа должна определить конкретный подкласс, для которого нужно создать экземпляр объекта. Структурные паттерны проектирования описывают типовые способы организации классов и объектов в системе. Разработчики часто сталкиваются с двумя проблемами, связанными с плохой организацией. Первая состоит в том, что классам поручается слишком много задач. Такие классы могут наносить ущерб сокрытию информации и нарушать инкапсуляцию, поскольку каждый класс может иметь доступ к информации, которая принадлежит другому классу. Вторая проблема связана с возможным перекрытием возложенных па классы задач. Загромождение системы ненужными классами отнимает лишнее время у разработчиков, поскольку им придется затратить много часов на расширение или модификацию классов, бея которых вполне можно обойтись. Как мы увидим, структурные паттерны проектирования помогают разработчикам избежать этих проблем. Поведенческие паттерны проектирования назначают задачи объектам. Эти паттерны также предоставляют проверенные стратегии для моделирования взаимодействия объектов друг с другом и предлагают особые способы поведения, предназначенные для широкого спектра применений. Паттерн проектирования Observer является классическим примером взаимодействия между объектами и назначения задач для объектов. Например, компоненты графического пользовательского интерфейса используют яти паттерны проектирования для взаимодействия со своими слушателями, которые реагируют на действия пользователей. Слушатели наблюдают за изменением состояний определенного компонента, для чего они должны быть зарегистрированы в качестве обработчиков событий этого компонента. Когда пользователь взаимодействует с компонентом, этот компонент уведомляет своих слушателей (их также называют наблюдателями), что состояние компонента изменилось (например, была нажата кнопка). 1.5.2. Обзор паттернов проектирования Паттерны проектирования реализуются в программном коде в виде набора классов и объектов. Чтобы эффективно использовать паттерны проектирования, разработчики должны иметь представление о наиболее популярных и эффективных паттернах проектирования, применяемых при построении программ. Ниже мы вкратце обсудим основные паттерны проектирования и архитектуры, а также их важную роль при построении качественных и эффективных приложений. В данной книге паттерны проектирования нашли применение в главе 5 при создании приложения с беспроводным доступом. В частности, мы будем использоваться порождающие паттерны Factory Method и Singleton, а также паттерн поведения Command. В главе 14 при реализации Web-сервиса прогноза погоды используется структурный паттерн проектирования Adapter. Мы используем не все паттерны проектирования из перечисленных в таблице на рис. 1.1, а лишь те, которые подходят для решения конкретных задач проектирования, с которыми мы сталкиваемся при написании примеров и учебных приложений в этой книге. Ниже мы рассмотрим другие популярные паттерны проектирования, рассмотренные «группой четырех», которые могут оказаться полезными при разработке программного обеспечения, хотя мы и не используем их в примерах в этой книге.
40 Глава 1 Prototype Иногда системе требуется сделать копию объекта, но до момента выполнения программы класс объекта неизвестен. Например, возьмем программу рисования с несколькими классами фигур (например, классы Line, Oval, Rectangle и т.д.), которые расширяют абстрактный суперкласс Shape. Пользователь этой программы должен в любой момент иметь возможность создавать, копировать и вставлять новые экземпляры классов Shape для добавления фигур в рисунки. Паттерн проектирования Prototype дает возможность пользователям делать это. Этот паттерн проектирования позволяет объекту — он называется прототипом — клонировать самого себя. Прототип подобен штемпелю, который может использоваться для создания нескольких идентичных оттисков. В программном компоненте каждый прототип должен принадлежать классу, который реализует типовой интерфейс, позволяющий прототипу клонировать самого себя. Например, Java API предоставляет метод clone интерфейса java.lang.Cloneable — любой объект класса, который реализует интерфейс Cloneable, использует метод clone для создания копии самого себя. Конкретно, метод clone создает копию объекта, а затем возвращает ссылку на этот объект. В программе рисования, если мы выбираем в качестве прототипа класс Line, то он должен реализовывать интерфейс Cloneable. Чтобы создать в нашем рисунке новый отрезок линии, мы клонируем прототип Line — этот прототип будет возвращать ссылку на другой экземпляр объекта Line. Чтобы скопировать ранее существующий отрезок линии, мы клонируем этот объект Line. Разработчики часто испсль&уют метод clone, чтобы не допустить изменений объекта по его ссылке, поскольку метод clone возвращает ссылку на копию объекта, а не ссылку на сам объект. Bridge Предположим, мы разработали класс Button для операционных систем Windows и Macintosh. Класс Button содержит специфическую информацию о кнопке, такую как слушатель действия ActionListener и строку текста надписи. Мы разработали классы Win32Button и MacBatton, чтобы расширить класс Button. Класс Win32Button содержит информацию о внешнем виде кнопки и способе ее отображения в Windows, а класс MacBatton содержит ту же информацию для отображения кнопки в операционной системе MacOS. При таком подходе возникают две проблемы. Во-первых, если мы создаем новые подклассы класса Button, мы должны создавать соответствующие подклассы классов Win32Button и Mac But ton. Например, если мы создаем класс ImageBut- ton (кнопку Button с изображением Image), который расширяет класс Button, мы должны создать дополнительные подклассы Win32ImageButton и Maclmage- Button. Фактически, мы должны создавать подклассы класса Button для каждой операционной системы, которую мы хотим поддерживать, что увеличивает время разработки. Вторая проблема заключается в том, что при появлении на рынке новой операционной системы мы должны создать дополнительные подклассы класса Button, специфичные для этой операционной системы. Паттерн проектирования Bridge решает эти задачи путем разделения абстракции (например, класса Button) и его реализаций (например, подклассы Win32- Button, MacButton и т.д.) на отдельные иерархии- Например, классы AWT Java используют паттерн проектирования Bridge, чтобы дать возможность разработчикам создавать подклассы AWT Button без необходимости создавать соответствующие подклассы, специфичные для каждой из операционных систем. Каждый класс AWT Button содержит ссылку на класс But ton Peer, который является суперклассом для специфичных для платформ реализаций, таких как Win32But- tonPeer, MacButtonPeer и т.д. Когда программист создает объект Button, класс Button определяет, какой специфичный для платформы объект ButtonPeer созда-
Введение 41 вать, и сохраняет ссылку на этот объект ButtonPeer — эта ссылка является мостом (bridge) в паттерне проектирования Bridge. Когда программист вызывает методы объекта Button, объект Button вызывает соответствующий метод объекта Button- Peer для удовлетворения запроса. Если разработчик создает подкласс Image- Button класса Button, ему не нужно создавать соответствующий класс Win32Ima- geButton или MadmageButton. Класс ImageButton является классом Button, поэтому, когда программист вызывает метод класса ImageButton — например, set Image — объекта ImageButton, суперкласс Button транслирует вызов метода в вызов соответствующего метода класса ButtonPeer — например, drawlmage. Совет по переносимости программ 1.1 Разработчики часто используют паттерн проектирования Bridge, чтобы повысить независимость своих систем от платформы. Мы можем разрабатывать подклассы класса Button, не беспокоясь о том, как операционная система будет реализовыватъ каждый из подклассов. Iterator Разработчики используют структуры данных, такие как массивы, связанные списки и хэш-таблицы, для организации данных в программе. Паттерн проектирования Iterator дает возможность объектам осуществлять доступ к отдельным объектам структур данных, не имея представления о реализации этой структуры данных или о том, как эта структура хранит ссылки на объекты. Инструкции по обработке структуры данных и доступа к ее элементам хранятся в отдельном объекте под названием итератор (iterator). Каждая структура данных имеет соответствующую реализацию итератора, способную обрабатывать эту структуру данных. Другие объекты могут использовать этот итератор, который реализует стандартный интерфейс, вне зависимости от базовой структуры данных или реализации. Интерфейс Iterator из пакета java.uti] использует паттерн проектирования Iterator. Рассмотрим систему, которая содержит структуры данных Set, Vector и List. Алгоритмы для извлечения данных из каждой структуры различаются для разных классов. При использовании паттерна проектирования Iterator каждый класс содержит ссылку на объект Iterator, который хранит информацию относительно обработки, специфичную для каждой структуры данных. Для объектов этих классов мы вызываем метод iterator для получения ссылки на итератор Iterator, относящийся к этому объекту. Мы вызываем метод next интерфейса Iterator для получения следующего элемента в структуре. При этом нам не нужно знать о деталях реализадии структуры. Memento Рассмотрим программу, которая дает возможность пользователю рисовать графические изображения. Бывает, что пользователь неправильно разместил изображение в области рисования. Программа может предоставлять функцию отмены, которая позволяет пользователю устранять подобные ошибки. В частности, программа может восстанавливать оригинальное состояние области рисования (до помещения в нее изображения пользователем). Более сложные программы рисования имеют журнал, который хранит список нескольких состояний, чтобы пользователь мог восстановить любое состояние программы из имеющихся в списке. Паттерн проектирования Memento позволяет объекту сохранять свое состояние, чтобы — если необходимо — пользователь мог восстановить предыдущее состояние объекта. Для паттерна проектирования Memento требуется три типа объектов. Объект хозяин (Originator) сохраняет некоторое состояние — набор значений атрибутов в определенный момент выполнения программы. В нашем примере с программой
42 Глава 1 рисования хозяином является область рисования, поскольку она может принимать несколько состояний. В начальном своем состоянии область не содержит каких-либо элементов. Объект хранитель (memento object) хранит копии всех атрибутов, ассоциированных с состоянием инициатора. Хозяин представляется в качестве первого элемента в журнале, который действует как объект механизма отката (Caretaker) —т.е. объект, который содержит ссылки на все объекты хранителя, ассоциированные с хозяином. Теперь предположим, что пользователь рисует окружность в области рисования. Область теперь принимает другое состояние — она содержит объект, центр которого задается координатами х-у. После этого область рисунка использует другой хранитель для хранения этой информации. Этот хранитель представляется как второй элемент в Журнале. Список элементов журнала отображается на экране, поэтому пользователь может выбрать, какое состояние восстановить. Предположим, пользователь хочет удалить окружность, — если пользователь выберет первое состояние из списка, область рисунка будет использовать первый хранитель для восстановления своего исходного состояния. Strategy Пакет java.awt предлагает несколько менеджеров компоновки LayoutManager, таких как классы FlowLayout, BorderLayout и GridLayout, с помощью которых разработчики строят графические интерфейсы пользователя. Каждый менеджер компоновки организует компоненты графического пользовательского интерфейса в контейнерах (объект Container) — однако каждая реализация интерфейса Layout Manager использует свой алгоритм для организации этих компонентов. Компоновка FlowLaycnit размещает компоненты в последовательности слева направо, BorderLayout помещает компоненты в пяти различных областях, a Grid- Layout организует компоненты по строкам и столбцам. Интерфейс LayoutManager играет роль стратегии в паттерне проектирования Strategy. Паттерн проектирования Strategy дает возможность разработчикам инкапсулировать набор алгоритмов — он называется стратегией, — каждый из которых имеет одну и ту же функцию (например, организации компонентов графического пользовательского интерфейса), но различные реализации. Например, интерфейс LayoutManager (стратегия) представляет собой набор алгоритмов, которые организуют компоненты графического пользовательского интерфейса. Каждый конкретный подкласс LayoutManager (например, FIowLayout, BorderLayout и Grid- Layout) реализует метод add Lay out Component для предоставления определенного алгоритма организации компонентов. 1.5.3. Паттерны параллельного выполнения С того момента, как «группа четырех» издала свою книгу, знакомящую с применением паттернов проектирования в объектно-ориентированных системах, было создано множество дополнительных паттернов проектирования. Некоторые из этих новых паттернов проектирования относились к особым типам объектно-ориентированных систем, таких как параллельные и распределенные системы. Языки программирования с ьозможностью организации множества программных потоков, такие как Java, позволяют разработчикам задавать параллельные действия — т.е. действия, которые выполняются одновременно. Неправильно спроектирован-- ныв параллельные системы могут привести к проблемам параллельного выполнения. Например, два объекта, пытающиеся одновременно изменить совместно используемые данные, могут повредить эти данные. Кроме того, если два объекта ожидают, пока другой объект завершит выполнение своей задачи, и если ни один из них не может завершить свою задачу, эти объекты потенциально могут нахо-
Введение 43 дится в состоянии ожидания неопределенно долгое время — такая ситуация называется тупиковой ситуацией (deadlock). Используя Java, Дуг Ли и Марк Гранд создали набор паттернов параллельного выполнения для многопотоковых архитектур, чтобы избежать различных проблем, связанных с многопоточной организацией выполнения программ. Вот несколько из этих паттернов проектирования: • Паттерн проектирования Single-Threaded Execution не дает нескольким программным потокам одновременно вызывать один и тот же метод другого объекта [3]. В Java разработчики могут использовать для применения этого паттерна проектирования ключевое слово synchronized. • Паттерн проектирования Balking обеспечивает, что метод будет отвергнут — т.е. отменен без выполнения каких-либо действий — если объект находится в состояний, в котором этот метод не может быть выполнен [4]. В другом варианте этого паттерна проектирования метод выдает исключение, описывающее, почему этот метод ие может быть выполнен, — например, метод выдает исключение при обращении к структуре данных, которая не существует. • Паттерн проектирования Read/Write Lock позволяет нескольким программным потокам одновременно получать доступ для чтения к объекту, но препятствует получению несколькими программными потоками одновременного доступа к этому объекту для записи. Только один программный поток в данный момент времени может получить доступ к объекту для записи, — когда этот поток получает доступ для записи, объект блокируется для всех других потоков [5]. » Паттерн проектирования Two-Phase Termination обеспечивает освобождение потоком ресурсов— например, других порожденных потоков — в памяти (первая фаза) перед завершением (вторая фаза) [6]. Б Java объект Thread может использовать этот паттерн проектирования в методе run. Например, метод run может содержать бесконечный цикл, который завершается в случае определенного изменения состояния, при завершении метод run может вызвать private метод, ответственный за останов порожденных потоков (первая фаза). Затем поток завершается после завершения метода run (вторая фаза). 1.5.4. Архитектурные паттерны проектирования Паттерны проектирования дают возможность разработчикам проектировать определенные фрагменты систем, например, абстрагироваться от создания экземпляров объектов, агрегировать классы в более крупные структуры, или назначать задачи для объектов. Архитектурные паттерны проектирования, со своей стороны, предоставляют для разработчиков проверенные стратегии для проектирования подсистем и задают порядок взаимодействия этих подсистем друг с другом [7]. Например, архитектурный паттерн проектирования модель—вид—контроллер отделяет данные приложения (содержащиеся в модели) от компонентов графического представления (вида) и логики обработки входных данных (контроллера). В проекте простого текстового редактора пользователь вводит текст с клавиатуры и форматирует этот текст с помощью мыши. Программа хранит этот текст и информацию о форматировании в наборе структур данных, а зятем отображает эту информацию яа экране, чтобы пользователь мог видеть введенные им данные. Модель, которая содержит данные приложения, может содержать только символы для разметки документа. Если пользователь предоставил некоторые входные данные, контроллер модифицирует данные модели в соответствии с введенными данными. При изменении модели она уведомляет вид об изменении, чтобы представление могло быть обновлено с учетом измененных данных, — например, могут отображаться символы с использованием определенного шрифта и определенного размера.
44 Глава 1 Архитектурный паттерн проектирования Layers разделяет функциональные возможности на отдельные группы, отвечающие за определенные задачи в системе. Такие группы называют уровнями. Например, трехуровневое приложение, в котором каждый уровень состоит из уникального системного компонента, является примером архитектурного паттерна проектирования Layers. Приложение подобного типа содержит три компонента, каждый из которых выполняет уникальную задачу. Информационный уровень (его также называют нижним уровнем) содержит данные для приложения, обычно храня данные в базе данных. Клиентский уровень (его также называют верхним уровнем) представляет собой пользовательский интерфейс приложения, например, стандартный Web-браузер. Средний уровень действует в качестве посредника между информационным уровнем и клиентским уровнем, обрабатывая запросы клиентского уровня, читая данные из базы данных и записывая данные в базу данных. Использование архитектурных паттернов проектирования способствует расширяемости при разработке систем, поскольку разработчики могут модифицировать компонент без необходимости модифицировать другой компонент. Например, текстовый редактор, который использует архитектурный паттерн проектирования модель—вид—контроллер, является расширяемым; разработчики могут модифицировать представление, которое отображает структуру документа, не модифицируя при этом модель, другие представления или логику. Система, разработанная с использованием архитектурного паттерна проектирования Layers, также является расширяемой; разработчики могут модифицировать информационный уровень, чтобы адаптировать программу к определенной системе управления базами данных, но им нет необходимости серьезно модифицировать клиентский или средний уровень. 1.5.5. Дополнительные ресурсы по паттернам проектирования Надеемся, что вы захотите больше узнать о паттернах проектирования. Мы рекомендуем просмотреть следующие ресурсы и прочесть упомянутые ниже источники по мере изучения паттернов проектирования в настоящей книге. Мы в особенности рекомендуем вам прочесть книгу, написанную «группой четырех». Паттерны проектирования www.hillside.net/patterns Эта страница содержит ссылки иа информацию по паттернам проектирования и языкам программирования. www.hillside.net/patterns/books/ Ha этом сайте перечислены книгя по паттернам проектирования. www.netobjectives.com/design.htm На этом сайте представлен обзор по паттернам проектирования и разъясняется их назначение. umbel .Tjmbc.edu/~tacr/dp/dp .html Этот сайт содержит ссылки па Web-сайты, относящиеся к паттернам проектирования, а также на учебные материалы и статьи, vfww.links2go.com/topic/Desigti_Pattecns Этот сайт содержит ссылки па сайты п информационные ресурсы по паттернам проектирования. www.с2.сош/ррс/ Этот сайт посаящен последним разработкам по паттернам проектирования и идеям для будущих проектов.
Введение 45 Паттерны проектирования и Java www. research,umbc.edu/~tarr/csJ91/fall007cs491 .html Этот сайт посвящен курсу по паттернам проектирования для Java, который изучается в университете штата Мэриленд. Здесь вы найдете массу примеров, а также правила применения паттернов проектирования в Java. www.anteгасt.com/~bradapp/javapats.html На этом сайте обсуждаются паттерны проектирования в Java, а также рассматривается применение их в распределенных системах, www.meurrens.org/ip-Links/java/designPatterns/ На этом сайте представлены многочисленные ссылки на ресурсы и информацию по паттернам проектирования в Java. Архитектурные паттерны проектирования compsci . about. coro/science/compsci/lit>rary/weeJcly/aa03060Qa.htin На этом сайте представлен обзор архитектуры модель—вид—контроллер. www.javaworld,com/javaworld/jw-04-1998/jw-04-howto.html Этот сайт содержит статьи, в которых рассматривается, как компоненты Swing используют архитектуру модель—вид—контроллер. www,ootips.org/mve-pattern.html На этом сайте представлена информация и сонеты по применению архитектуры модель—вид—контроллер (MVC). www.ftech.co.uk/~honeyg/article3/pda.htm Этот сайт содержит статьи, посвященные важной роли архитектурных паттернов проектирования при разработке программного обеспечения. www. tml .but. f i/Opinnot/Tik-109 . 450/l99fl/niska/sld001. htjn Этот сайт предоставляет информацию по архитектурным паттернам, паттернам проектирования и идиомам (паттернам, предназначенным для конкретных языков). Используемые источники 1. Е. Gamma, et al, Design Patterns; Elements of Reusable Object-Oriented Software (Boston, MA: Addison-Wesley, 1995). Русский перевод: Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес «Приемы объектно-ориентированного проектирования. Паттерны проектирования». С.Пб.: Питер, 2001 г., 368 с. 2. J. Vllssides, Pattern Hatching; Design Patterns Applied (Boston, MA: Addison-Wesley, 1998) 146. 3. M. Grand, Patterns in Java; A Catalog of Reusable Design Patterns Illustrated with UML (New York, NY: John Wiley and Sons, 1998) 399-407. 4. M. Grand, 417-420. 5. M. Grand, 431-439. 6. M. Grand, 449-453. 7. R. Hartman. «Building of Patterns». Application Development Trends May 2001: 19-26. Литература Carey, J., B. Carlson and T. Graser. San Francisco'"' Design Patterns: Blueprint for Building Software. Boston, MA: Addison-Wesley, 2000. Coad, P., M. Mayficld and Jon Kern. Java Design; Building Better Apps and Applets, Second Edition. Englewood Cliffs, NY: Yourdon Press, 1999. Cooper, J. Java Design Patterns; A Tutorial. Boston, MA: Addison-Wesley, 2000. Lea, D., Concurrent Programming "i Java. Second Edition; Design Principles and Patterns. Boston, MA: Addison-Wesley, 1999,
6 Глава 1 Gamma, R., R. Helm, R. Johnson and J. Vlissides. Design Patterns; Elements of Reusable Object Oriented Software. Boston, MA: Addison-Wesley, 1995. Русский перевод: Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес «Приемы объектно-ориентированного проектирования. Паттерны проектирования». СПб.: Питер, 2001 г., 368 с. Vlissides, J. «Composite a la Java, Part 1». Java Report. 6:. no. 6 (2001): 69-70, 72. Vlissides, J. «Pattern Hatching: GoF a la Java*. Java Report Online (March. 2001) <www.javareport.com/html/from_pages/article. asp ?id=355>
9 Сервлеты Цели • Научиться выполнять сервлеты с использованием сервера Apache Tomcat. • Научиться обрабатывать HTTP-запросы с помощью класса HttpServlet. • Научиться хранить информацию о сеансе с помощью cookies и объектов Http Session. • Научиться осуществлять доступ к базам данных из сер влетов. Справедливое требование надо исполнять безропотно. Данте Алигьери Большая часть путешествия состоит в прохождении через ворота. Марк Теренций Варрон Если меня выдвинут, я не смирюсь; если мепя изберут, я не буду прислуживать. Генерал Уильям Т. Шерман Я хочу коржик,' Коржик (Cookie Monster), персонаж из Улицы Сезам Как только время тихих размышлений настает Я воскрешаю в памяти своей былое... Уильям Шекспир Друзья делят с тобой все. Пифагор Если вы не. добились успеха с первого раза, уничтожьте все доказательства вашей попытки. Ньют Хейлшер
48 Глава 2 2.1. Введение В Internet и Всемирной паутине имеется масса интересного и волнующего. Internet связывает воедино мир информации. Всемирная паутина облегчает использование Internet и обогащает возможностями мультимедиа. Организации отводят Internet и Web важную роль в своих стратегиях построения информационных систем. Java предоставляет мощные сетевые средства, которые облегчают разработку приложений на основе Internet и Web. Они не только обеспечивают параллельное выполнение за счет многопоточности, но также позволяют программам осуществлять поиск информации и взаимодействовать с программами, выполняющимися на других компьютерах в других организациях и других странах. Java также дает возможность апплетам и приложениям, выполняющимся на одном компьютере, взаимодействовать друг с другом с соблюдением ограничений, устанавливаемых системой безопасности. Работа в сети и сетевые взаимодействия — большая и сложная тема. В программах учебных заведений, готовящих специалистов по информационным технологиям, на полное изучение этой тематики отводится целый семестр, а на старших курсах эти вопросы рассматривается еще более глубоко. Java предоставляет множество сетевых функциональных возможностей и как правило выбирается в качестве средства реализации при обучении основам создания корпоративного программно-
Сервлеты 49 го обеспечения. В этой книге мы познакомимся с некоторыми принципами и возможностями сетевого программирования. Сетевые возможности Java сгруппированы в нескольких пакетах. Основные сетевые возможности определяются классами и интерфейсами пакета java.net, посредством которых Java предоставляет возможность осуществлять такие взаимодействия на основе сокетов, что позволяет приложениям воспринимать такие взаимодействия кап обмен потоками данных — программа может прочесть из сокета или записать в сокет данные точно так же, как прочесть из файла или записать в файл. Классы и интерфейсы пакета java.net также позволяют осуществлять взаимодействия на основе пакетов, передавая отдельные пакеты информации, — обычно используемые для передачи через Internet данных аудио и видео. В нашей книге Как программировать на Java. Издание 4-е показано, как создавать и работать с сокетами, а также как осуществлять взаимодействие с помощью пакетов данных. На высоком уровне сетевые функциональные возможности предоставляется классами и интерфейсами, содержащимися в пакетах Java.rmi (пять пакетов) для технологии удаленного вызова методов (RMI — Remote Method Invocation), и в пакетах org.omg (семь пакетов) для технологии Common Object Request Broker Architecture (CORBA), которые являются составной частью API Java 2. Пакеты RMI дают возможность объектам Java, выполняющимся на разных виртуальных машинах Java (обычно и на различных компьютерах) взаимодействовать посредством удаленных вызовов методов. Такие вызовы методов внешне представляются адресованными объекту в этой же программе, но фактически имеют встроенные сетевые средства (на основе пакета java.net), которые передают вызовы методов другому объекту на другом компьютере. Пакеты CORBA могут быть использованы для организации взаимодействия между двумя приложениями, воспринимающими CORBA, в том числе между приложениями, написанными на других языках программирования. В главе 2 книги «Технологии программирования на Java 2. Книга 2» рассказывается о возможностях технологии RMI Java. В главах 7-8 этой же книги обсуждаются основные концепции CORBA и представлен практический пример, реализующий распределенную систему на CORBA. При обсуждении возможностей работы в сети в следующих двух главах мы сосредоточим внимание на обеих сторонах, участвующих во взаимоотношении клиент-сервер. Клиент запрашивает некоторое действие, а сервер выполняет действие и посылает ответ клиенту. Модель взаимодействия запрос-ответ является основой высокоуровневого представления сетевых взаимодействий в Java — сервлетов и серверных страниц JavaServer Pages (JSP). Сервлет расширяет функциональные возможности сервера. Пакеты javax.servlet и javax.servlet.http предоставляют классы и интерфейсы для функционирования сервлетов. Пакеты javax.eerv- let.jsp и javax.servlet.jsp.tagext предоставляют классы и интерфейсы, которые расширяют возможности сервлетов для взаимодействия со страницами JavaServer Pages. С помощью специального синтаксиса технология JSP дает возможность программистам создавать Web-страницы, которые используют инкапсулированные функциональные возможности Java, и даже включать скриптлеты, содержащие реальный код Java, непосредственно в страницы. Типичной реализацией модели запрос-ответ является взаимодействие между браузерами и серверами Web. Когда пользователь выбирает Web-сайт для просмотра с помощью своего браузера (клиентское приложение), на соответствующий Web-сервер (серверное приложение) посылается запрос. Сервер обычно отвечает клиенту отправкой соответствующей Web-страницы в формате XHTML. Сервлеты эффективны при разработке Web-решений, которые помогают предоставлять безопасный доступ к Web-сайту, взаимодействовать с базами данных в интересах клиента, динамически генерировать XHTML-документы для отображения в браузерах и сохранять уникальную информацию о сеансах клиентов.
50 Глава 2 ® Общая методическая рекомендация 2.1 Хотя сервлеты обычно используются в распределенных Web-приложениях, не все сервлеты обязательно предназначены для расширения функциональных возможностей Web-сервера. В этой главе мы начинаем обсуждение средств сетевого взаимодействия с рассмотрения сервлетов, которые расширяют функциональные возможности серверов World Wide Web и являются наиболее типичной на сегодняшний день формой сервлетов. В главе 3 рассматриваются JSP-страницы, которые транслируются в сервлеты. JSP-страницы являются удобным и мощным способом реализации Web-приложеяий, позволяющим не углубляться в низкоуровневую детализацию сервлетов. Совместно сервлеты и JSP-страницы образуют в пакете Java 2 Enterprise Edition (J2EE) уровень взаимодействия с Web. Многие разработчики считают, что сервлеты являются верным выбором для интенсивно работающих с базами данных приложений, которые взаимодействуют с так называемыми тонкими клиентами — приложениями, которые требуют минимальной поддержки на стороне клиента. Сервер отвечает за доступ к базам данных. Клиенты соединяются с сервером с использованием стандартных протоколов, доступных на большинстве клиентских платформ. Таким образом, код, реализующий логику внешнего представления динамического содержимого, может быть написан один раз и размещен на сервере для доступа к нему клиентов, чтобы дать возможность программистам создавать эффективно работающие тонкие клиенты. В этой главе примеры сервлетов демонстрируют применяемый в Web механизм запрос/ответ (главным образом, с помощью методов get и post), возможности отслеживания состояния сеанса, переадресацию запросов и возможности взаимодействия с базами данных через JDBC. (Предполагается, что читатель знаком с интерфейсом JDBC и базами данных. Технология JDBC рассматривалась в главе 8 книги «Технологии программирования на Java 2. Книга 1»). В главе 4 мы создадим Web-приложение для виртуального книжного магазина с использованием XML, JDBC, технологии сервлетов, рассматриваемой в этой главе, и технологии JSP, рассматриваемой в следующей главе. В комплексном учебном примере мы представим дополнительные возможности применения сервлетов. Корпорация Sun Microsystems через процесс Java Community Process отвечает за разработку спецификаций для сервлетов и JavaServer Pages. Эталонная реализация этих стандартов в настоящее время разрабатывается альянсом Apache Software Foundation (www.apache.org) как часть проекта Jakarta Project (Jakarta, ара che.org). Как заявлено на основной странице проекта Jakarta Project, «Цель Jakarta Project — предоставить серверные решения коммерческого качества, основанные на платформе Java, которые разрабатываются открыто и при взаимном сотрудничестве». В проекте Jakarta имеется множество подчиненных проектов, призванных оказать помощь коммерческим разработчикам серверных приложений. Сервлеты и JSP являются составной частью проекта Jakarta, получившей название Tomcat. Это официальная ятя лонная реализация стандартов для сервлетов и JSP. Мы используем Tomcat в этой главе для демонстрации возможностей сервлетов. Последней на момент написания этой книги реализацией Tomcat была версия 3.2.31. Последнюю версию Tomcat можно загрузить с Web-сайта Apache Group. Чтобы иметь возможность выполнять сервлеты в этой главе, нужно установить Tomcat или другой сервер приложений, поддерживающий сервлеты и JavaServer Pages. Об установке и настройке Tomcat мы поговорим в разделах 2.3.1 и 2.3.2 после того, как познакомимся с нашим первым примером. На момент подгатоикн к изданию перевода книги актуальной была версия 4.1.12 Tomcat. — Прим. ред.
Сервлеты 51 В указаниях по тестированию каждого из примеров в этой главе постоянно подчеркивается, что вам следует скопировать файлы в определенные каталоги Tomcat. Все файлы примеров для этой главы имеются на Web-сайте компании Deitel www.dcite\.com, а также на Web-сайте издательства «Бином» www.binom-press.ru/ books/ad v_] ava2.htm. [Замечание. В конце раздела 2.10 имеется список спецификаций Internet (упомянутых в спецификации Servlet 2,2 Specification) для технологий, имеющих отношение к разработке сервлетов. Для каждой спецификации указывается номер документа RFC (Request for Comments). Мы приводим URL Web-сайта, на котором можно найти и просмотреть каждую из спецификаций.] 2.2. Обзор технологии сервлетов и их архитектура В этом разделе представлен обзор технологии сервлетов Java, Мы рассмотрим классы, методы и исключения, относящиеся к сервлетам. В следующих нескольких разделах будут представлены примеры «живого кода», в которых мы создадим многоуровневые системы клиент-сервер с помощью сервлетов и технологии JDBC. internet использует множество протоколов. Протокол HTTP (Hypertext Transfer Protocol), который составляет основу Всемирной паутины, использует унифицированные идентификаторы ресурсов (Uniform Resource Identifiers — URI, их также иногда называют унифицированными указателями ресурсов — Universal Resource Locators, или URL) для определения местоположения ресурсов в Internet. Обычно URI указывают на файлы или каталоги, но также могут быть использованы для выполнения таких сложных задач, как поиск в базах данных и в Internet, Дополнительную информацию о форматах URL можно получить на сайте www.w3.org/Addressing За дополнительной информацией о протоколе HTTP обратитесь на сайт www.w3,org/Protocols/HTTP За общей информацией, связанной с Всемирной паутиной, посетите сайт www.w3.org Технология JavaServer Pages является расширением технологии сервлетов. Технология ЛЙР используется главным образом в том случае, если значительная часть содержимого, отправляемого клиенту, является статическим текстом и разметкой, и лишь небольшая его часть генерируется динамически с помощью кода Java, Сервлеты, как правило, используются в том случае, когда лишь малая часть содержимого, отправляемого клиенту, представляет собой статический текст или разметку. На самом деле некоторые сервлеты не выдают никакого содержимого. Вместо этого они выполняют определенную задачу в интересах клиента, а затем вызывают другие сервлеты или JSP-страницы для формирования ответа. Заметим, что в большинстве случаев технологии сервлетов и JSP являются взаимозаменяемыми. Сервер, на котором выполняется сервлет, часто называют контейнером сервлетов. Сервлеты и страницы JavaServer Pages стали настолько популярными, что на сегодняшний день они поддерживаются непосредственно или через подключаемые модули сторонних поставщиков большинством Web-серверов и серверов приложений, включая Netscape iPlanet Application Server, Microsoft Internet Information Server (IIS), Apache HTTP Server, сервер приложений ВЕА WebLogic, сервер приложений IBM WebSphere, Web-сервер Jigsaw консорциума World Wide Web и многие другие, В этой главе сервлеты демонстрируют взаимодействие между клиентом и сервером по протоколу HTTP. Клиент отправляет HTTP-запрос серверу или контейнеру сервлетов. Сервер или контейнер сервлетов получает запрос и направляет его для
52 Глава 2 обработки соответствующему сервлету. Сервлет выполняет обработку, которая может включать в себя взаимодействие с базой данных или с другими серверными компонентами, такими как другие сервлеты, JSP-страницы или компоненты Enterprise JavaBeans (глава 7). Сервлет возвращает результаты клиенту, обычно в виде документа в формате HTML, XHTML или XML, отображаемого браузером, однако могут быть использованы и другие форматы данных, такие как изображения и двоичные файлы. 2.2.1. Интерфейс Servlet. и жизненный цикл сервлета Архитектурно все сервлеты должны реализовыватъ интерфейс Servlet. Как и многие ключевые методы апплетов, методы интерфейса Servlet вызываются автоматически (сервером, на котором установлен сервлет). Этот интерфейс определяет пять методов, описанных в таблице на рис. 2.1. | Метод I Описание void init[ ServletConfig config ) Этот метод автоматически вызывается один рат при выполнении сррвгета для его инициализации. Параметр ServletConfig предоставляется контейнером сервлетов, который исполняет сервлет. ServletConfig getServletConfig() Этот метод возвращает ссылку на объект, который реализует интерфейс I ServletConfig. Этот объект предоставляет доступ к информации о конфигурации сервлета, такой как параметры иницизгмгации сервпета и объект контекста сервлета ServletContext, который дает доступ к сервлету и его окружению (т.е. контейнеру сеэвлетов, в котором исполняется сервлет). String getServletlnfoO Этот метод определяется программистом, создаощим сервлет, для возврата строки, содержащей информацию о сервлете, такую как данные об авторе сервлета и номере версии сервлета. void service( ServletRequest request, ServletResponse response ) Контейнер сервлета вызывает этот метод для отзетз на клиентский запрос сервлету. void destroy() Этот «чистящей» метод вызывается лосле завершения выполнения сервлета контейнером сервлетов. Данный метод следует применять для освобождения ресурсоо, используемых сервлетом, таких как скрытые файлы или открытые соединения с базами данных. Рис. 2.1. Методы интерфейса Servlet (пакет javax.servlet) S Общая методическая рекомендация 2.2 Все сервлеты должны реализовыватъ интерфейс Servlet из пакета ja- vax.servlet. Жизненный цикл сервлета начинается, когда контейнер сервлетоа загружает сервлет в память, обычно в ответ на первый запрос, который получает сервлет. Перед тем, как сервлет получит возможность обработать этот запрос, контейнер сервлетов вызывает метод init сервлета. После завершения выполнения метода init сервлет может ответить на свой первый запрос. Все запросы обслуживаются мето-
Сервлеты 53 дом service сервлета, который получает запрос, обрабатывает его и отправляет ответ клиенту. В течение жизненного цикла сервлета метод service вызывается один раз для каждого запроса. Каждый новый запрос обычно обрабатывается в новом программном потоке (создаваемом контейнером сервлетов), в котором выполняется метод service. Когда контейнер сервлета завершает выполнение сервлета, вызывается метод destroy сервлета для освобождения ресурсов сервлета. Совет по повышению эффективности 2.1 Создавать новый программный поток для каждого запроса более эффективно, чем создавать для этого новый процесс, как это делается в некоторых других серверных технологиях, таких как CGI. [Замечание. Подобно сервлетам, технология Fast CGI позволяет избежать затрат, связанных с созданием нового процесса для каждого запроса.] Пакеты сервлетов определяют два абстрактных класса, которые реализуют интерфейс Servlet: класс Generics e r vlet (из пакета javax,servlet) и класс HttpSer- vlet (из пакета javax.servlet.http). Эти классы предоставляют реализации по умолчанию для всех методов интерфейса Servlet. Большинство сервлетов расширяют либо класс GenericServlet, либо класс HttpServlet, и замещают некоторые или все их методы. Все примеры, рассматриваемые в этой главе, расширяют класс HttpServlet, который определяет возможности обработки запросов для сервлетов, обогащающие функциональные возможности Web-copBopa. Ключевым -методом в каждом сервле- те является метод service, который принимает в качестве параметров объект ServletRequest и объект ServletRespon.se. Эти объекты предоставляют доступ к потокам ввода и вывода, которые дают возможность сервлетам получать данные г. клиента и отправлять данные клиенту. Эти потоки могут быть либо байтовыми, либо символьными. Если в процессе выполнения сервлета возникает проблема, возбуждается либо исключение ServlctExeeption, либо исключение IOException. Общая методическая рекомендация 2.3 Сервлеты могут реализавывать интерфейс javax.servlet.SingleThread- Model для указания, что только один программный поток выполнения одновременно может обращаться к методу service в определенном экземпляре сервлета. Если сервлет реализует интерфейс SingleThreadModel, контейнер сервлетов может создавать множество экземпляров сервлета для обработки множества запросов к сервлету параллельно. В этом случае может потребоваться предоставлять синхронизированный доступ к коллективным ресурсам, используемым в методе service. 2.2.2. Класс HttpServlet Сервлеты, работающие с клиентами через Web, обычно расширяют класс HttpServlet. Метод service, как правило, переопределяется, чтобы иметь возможность различать стандартные методы запросов, получаемые от Web-браузера клиента. Двумя наиболее распространенными типами запросов HTTP (их также называют методами запросов) являются get и post. Запрос get получает (или извлекает) информацию. Типичное применение запросов get — получение HTML-документа или изображения. Запрос post помещает (или отправляет) данные на сервер. Типичное применение запросов post — отправка на сервер информации, например, для аутентификации, или данных из формы, в которую пользователь ввел информацию.
54 Глава 2 В классе HttpServlet определены методы doGet и doPost для реакции на запросы типа get и post клиента. Эти методы вызываются методом service класса HttpServlet, который, в свою очередь, вызывается при поступлении запроса на сервер. Метод service сначала определяет тип запроса, а затем вызывает соответствующий метод. Имеются и другие, менее употребительные типы запросов, но в этой книге мы не будем их касаться. Методы класса HttpServlet, которые реагируют на другие типы запросов, представлены в таблице на рис. 2.2. Все они принимают параметры типа HttpServletRequest и HttpServIetRespoase и ничего не возвращают. Методы, представленные на рис. 2.2, используются довольно редко. Для получения дополнительной информации относительно протокола HTTP, посетите сайт www,w3.org/Protocols Ш Общая методическая рекомендация 2.4 Не переопределяйте метод service в подклассе класса HttpServlet. Если это сделать, сервлет не будет способен различать типы запросов. Методы do Get и doPost принимают в качестве параметров объекты HttpServletRequest и HttpServletResponse, которые дают возможность осуществлять взаимодействие между клиентом и сервером. Методы интерфейса HttpServletRequest облегчают доступ к данным запроса. Методы интерфейса HttpServletResponse облегчают сервлету возврат результатов Web-клиенту в виде HTML. Интерфейсы HttpServletRequest и HttpServletRespon.se будут рассмотрены в следующих двух разделах. Метод doDelete doOptions doPut doTrace Описание Вызывается в ответ на HTTP-запрос delete. Такой запрос обычно используется для удзления файла с сервера. Это действие для некоторых серверов может быть запрещено из-за угроз внутренней безопасности (поскольку клиент может удалить файл, который является важным для сервера или приложения). Вызывается в ответ на HTTP-запрос options Этот метод возвращает клиенту информацию, указывающую на опции HTTP, поддерживаемые сервером, нагример, версию HTTP (1.0 или 1.1) и методы запросов, поддерживаемые сервером. Вызывается в ответ на HTTP-запрос put. Такой запрос обычно используется для сохранения файла на сервере. Это действие для некоторых серверов ыожег быть запрещено из-за угроз для внутренней безопасности (тзк, клиент может поместить исполняемое поиложение на сервер, которое, в случае выполнения, может нанести ущерб серверу - например, путем удаления взжных файлов или захвата ресурсов). Вызывается в ответ на HTTP-запрос trace. Такой запрос обычно используется при отлздке. Реализация этого метода автоматически возвращает клиенту | HTML-документ, содержащий информацию о заголовке запроса (данные, { посылаемые браузером как часть запросз). | Рис, 2.2. Другие методы клзсса HttpServlet 2.2.3. Интерфейс HttpServletRequest При каждом вызове методы doGet и doPost класса HttpServlet принимают в качестве параметра объект, который реализует интерфейс HttpServletRequest. Web-серъер, который исполняет сервлет, создает объект HttpServletRequest и передает его методу service сервлета (который, в свою очередь, передает его методу
Сервлеты 55 doGet или doPost). Этот объект содержит запрос, поступивший от клиента. Имеется множество методов, дающих возможность сери лету обрабатывать клиентский запрос. Некоторые из этих методов принадлежат интерфейсу ServletReqaest — интерфейсу, который расширяется интерфейсом HttpServletRequest. Некоторые ключевые методы, используемые в этой главе, представлены в таблице на рис. 2.3. Полный список методов интерфейса HttpServletRequest вы можете найти по адресу Java.sun.com/j2ee/32sdkee/techdocs/api/javax/servlet/http/ HttpServletRequest.html Вы также можете загрузить и установить сервер Tomcat (см. раздел 2.3.1) и просмотреть соответствующую документацию на него на вашем локальном компьютере. Метод Описание String getParameter( string паше ) Возвращает значение, ассоциированное с параметром, отправленным сервлету как част-, запроса get или post Параметр name представляет гобой имя параметра. Emuneration getParameterNames() Возвращает имена всех параметров, отгравленных сервлету как часть запроса post. String[] getParameterValues( String name ) Для параметра с несколькими качениями зю меюд возвращает ci роковый массив, содержащий значения указанного параметра. Cookie[] getCookies() Возвращает массив объектов Cookie, сохраненных на клиенте сервером. Cookie могут быть использованы для уникальной идентификации клиентов сервером. HttpSession getSess±on( boolean create ) Возвращает объект HttpSession, ассоциированный с текущим сеансом клиента. С помощью этого метода может быть создан объект HttpSession (в этом случае значением параметра является true), если объект HttpSession для клиента еще не существует. Объекты HttpSession могут использоваться энало'ично Cookie для уникальной идентификации клиентов. Рис. 2.3. Некоторые методы интерфейса HttpServletRequest 2.2.4. Интерфейс HttpServletResponse При каждом обращении методы doGet или do Post объекта HttpServlet принимают объект, который реализует интерфейс HttpServletResponse. Web-сервер, который исполняет сервлет, создает объект HttpServletResponse и передает его методу service сервлета (который, в свою очередь, передает его методу doGet или doPost). Этот объект содержит ответ клиенту. Имеется множество методов, дающих возможность сервлету сформировать ответ клиенту. Некоторые из этих методов принадлежат интерфейсу ServletResponse — интерфейсу, который расширяется интерфейсом HttpScrvletRespon.se, ЕГесколько ключевых методов, используемых, в этой главе, представлены в таблице на рис. 2.4. Полный список методов интерфейса HttpServletResponse вы можете найти по адресу Java.sun,com/j2ee/j2sdkee/techdocs/api/javax/servlet/http/ HttpServletResponse.html
56 Глава 2 Вы также можете загрузить и установить Tomcat (см. раздел 2.3.1) и просмотреть соответствующую документацию на вашем локальном компьютере. Метод Описание void addCookie{ Cookie cookie ) Используется для добавления Cookie в заголовок ответа клиенту Установленный максимальный возраст Cookie, а также то, разрешено ли клиентом хранение Cookie, определяют, будут ли Cookies сохранены на клиенте. ServletOutputSfcream getOutputStream () Получает бинарный поток вывода, позволяющий отправлять бинарные данные клиенту. Fr in tWr i tor getWriterH Получает символьный поток вывода, позволяющий отправлять текстовые данные клиенту. void setContentType{ String type ) Задает MIME-тип ответа браузеру, MIME-тип помогает браузеру определить, как отображать данные (или, возможно, какое другое приложение глрдует запустить для обработки данных). Например, MIME-тип "text/html" указывает, что ответ является HTML-документом, поэтому браузер отображает HTML-страницу. Рис. 2,4. Некоторые методь интерфейса HttpServletResponse 2.3. Обработка HTTP-запросов get Основное назначение HTTP-запроса get — извлекать содержимое, хранящееся по указанному URL — обычно содержимым является HTML-документ или XHTML-до- кумент (т.е. Web-страница). Сервлет, представленный на рис. 2.5, и XHTML-доку- мент на рис. 2.6 демонстрируют обработку HTTP-запроса get. Когда пользователь щелкает на кнопке Get HTML Document (рис. 2.6), запрос get посылается сервлету WelcomeServlet (рис. 2.5). Сервлет отвечает на запрос, динамически генерируя для клиента XHTML-доку мент, который отображает текст "Welcome to Servlets!" Па рис. 2.5 представлен исходный код сервлета WelcomeServlet. На рис. 2.6 показан XHTML-документ, который клиент загружает для доступа к сервлету. Здесь же показана копия экрана клиентского браузера до и после взаимодействия с серв- летом, [Замечание. В разделе 2.3.1 рассказывается, как установить и настроить Tomcat для выполнения этого примера.] В строках 5 и 6 импортируются пакеты javax.servlet и javax.http. В примере мы используем несколько типов данных из этих пакетов. Пакет javax.servlet.http предоставляет суперкласс HttpServlet для сервлетов, которые обрабатывают HTTP-запросы get и HTTP-запросы post. Этот класс реализует интерфейс javax.servlet.Scrvlet it добавляет методы, которые поддерживают запросы по протоколу HTTP. Класс WelcomeServlet в связи с этим расширяет класс HttpServlet (строка 9). Суперкласс HttpServlet предоставляет метод doGet для реакции на запросы get. В соответствии с установками по умолчанию, метод указывает на ошибку «Method not allowed» («Недопустимый метод»). Обычно эта ошибка отображается в Internet Explorer сообщением «This page cannot be displayed» («Страница не может быть отображена»), а в Netscape Navigator — сообщением «Error: 405» («Ошибка: 405»). R строках 12-44 замещается метод doGet, чтобы обеспечить собственную обработку запроса get. Метод doGet принимает два параметра: объект HttpServletRequest
Сервлеты 57 и объект HttpServletResponse (оба из пакета javax.servlet.http). Объект HttpSer- vletKequest представляет клиентский запрос, а объект HttpServletKesponse — ответ сервера клиенту. Если метод doGet не способен обработать клиентский запрос, он возбуждает исключение javax.servlet.ServletException. Если метод doGet сталкивается с ошибкой в процессе обработки потока (чтение с клиента или запись на клиент), он возбуждает исключение java.io.IOException. 1 // Рис. 2.5. WelcomeServlet.Java 2 // Простой сервлет для обработки запросов get. 3 package com.dieitel .advjhtpl. servlets ; 4 5 import javax.servlet.*; 6 import javax.servlet.http.*; 7 import 3ava.io.*; a 9 public class WelcomeServlet extends HttpServlet { 10 11 // обработка клиентских запросов get 12 protected void doGet( HttpServletHequest request, 13 HttpServletResponse response ) 14 throws ServletException, IOException 15 { 16 response,setContentType( "text/html" ); 17 PrilitWriter out = response.getWriter(); 18 19 // отправка XHTML-страницы клиенту 20 21 // начало XHTML-документа 22 out.println( "<?xml version = \"1.0\"?>" ); 23 24 out.printlnf "<!DOCTYPE html PUBLIC V-//W3C//DTD " + 25 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 26 "/TR/xhtmll/DTD/xhtjnll-strict.dtd\,,>" ); 27 28 out.println( 29 "<html xmlns = \"http://www.w3,org/1999/xhtml\">" ); 30 31 // заголовок документа 32 out.println< "<head>" ); 33 out.println( "<title>A Simple Servlet Example</title>" ); 34 out.println( "</head>" ); 35 36 // тело документа 37 out.printlnC "<body>" ); 38 out.println( "<hl>Welcome to Servlets!</hl>" ); 39 owt.pri»tln{ "</body>" ); 40 41 // колец XHTML-документа 42 out.println( "</ht*nl>" ); 43 out.closet); // закрытие потока и конец страницы 44 } 45 } Рис. 2.5. Сервлет WelcomeServlet, который обрабатывает простой HTTP-запрос get
58 Глава 2 Реагируя на запрос get, наш сервлет создает XHTML-док у мент, содержащий текст приветствия "Welcome to Servlcts!" Ответом клиенту является текст XHTML-доку- мента. Ответ посылается клиенту через объект PrintWriter, полученный из объекта HttpServletResponse. В строке 16 используется метод SetContentType объекта response для задания типа содержимого данных, которые будут посылаться в качестве ответа клиенту. Это дает возможность клиентскому браузеру правильно обрабатывать полученное содержимое. Тип содержимого также известен как М/М£-тип [Multipurpose Internet Mail Extension) данных. В этом примере тип содержимого text /html указывает браузеру, что ответом является XHTML-документ. Браузер знает, что он должен прочесть теги XHTML, содержащиеся в документе, отформатировать документ в соответствие с тегами и отобразить документ в окне браузера. Для получения более подробной информации о MIME-типах, посетите сайт www.irvine.com/-mime. В строке 17 используется метод get Write объекта response для получения ссылки на объект PrintWriter, который дает возможность сервлету отправлять содержимое клиенту. [Замечание. Если ответом являются двоичные данные, например, изображение, для получения ссылки на объект ServletOutputStream используется метод getOutputStream.] В строках 22-42 создается XHTML-документ путем записи строк с помощью метода prititln объекта out. Этот метод выводит символ новой строки после вывода строки, представленной параметром типа String. При отображении Web-страницы браузером символ новой строки не используется, но он присутствует в исходном коде XHTML, который вы можете увидеть, выбрав Source из меню View в Internet Explorer или Page Source из меню View в Netscape Navigator. В строке 43 осуществляется закрытие потока вывода, очищается буфер вывода, и отправляется информация клиенту. Тем самым завершается ответ клиенту. XHTML-документ, представленный на рис. 2.6, содержит форму form, которая вызывает сервлет, код которого приведен в листинге на рис, 2.5. Атрибут action (/advjhtpl/welcome) элемента form содержит URL, указывающий на сервлет, а атрибут method элемента form указывает, что браузер отправляет серверу запрос get, что приводит к вызову метода doGct сервлета. Об URL, задаваемом в качестве значения атрибута action в этом примере, мы подробнее поговорим в разделе 2.3.2 после того, как установим и настроим сервер Apache Tomcat для выполнения сервлета, представленного в листинге на рис. 2.5, Обратите внимание, что на копиях экрана в адресной строке браузера присутствует имя localhost — стандартное имя Web-сервера на локальном компьютере. Мы часто используем имя localhost при демонстрации работы сетевых программ на локальном компьютере, чтобы читатели, не имеющие подключения к сети, могли, тем не менее, ознакомиться с принципами сетевого программирования. В этом примере localhost указывает, что сервер, на котором установлен сервлет, выполняется на локальной машине. За именем хоста следует :8080, что соответствует номеру порта TCP, на котором сервер Tomcat ожидает запросы клиента. В Web-браузерах по умолчанию используется порт 80 TCP, но сервер Tomcat ожидает клиентские запросы на порту 8080 TCP. Это позволяет выполнять Tomcat на том лее компьютере, что и стандартный Web-сервер. Если мы явно не зададим номер порта в URL, сервлет не получит наши запросы, а браузер отобразит сообщение об ошибке. 1 <?xml version = "1.0"?> 2 <!D0CTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtBill/DTD/xhtrftll-strict.dtd"> 4 5<!-- Рис. 2.Б. WelcomeServlet.html --> 6 7 <html xmlns = "http://www.w3.org/1999/xhtml">
Сервлеты 59 8 <head> 9 <titl£>Handling an HTTP Get Request</title> 10 </head> 11 12 <body> 13 <forro action = "/advjhtpl/welcomel" method = "get"> 14 15 <pXlabel>Cliclc the button to invoke the servlet 16 <input type = "submit" value = "Get HTML Document" /> 17 </labelX/p> IS 19 </form> 20 </body> 21 </html> mmm штшшашыт Revert» Tools" МНр'^^> шё. <ЙЕ*Г..-1в»Ы ИШ i'T*W Seardi |^^Jgjhttp:;)lIicahii!t'B080f«Ktt»lfM'Vfcfe<W'fc™'Se'vfe'-№i|1 z\:&& Click Iha button to invoke the soviet SA-'Gei. HTML Doom anl 5 ШШШРЗШ"' ~3 ш/тщдт*штшттлпттшт^^г-^и „1 fiUf,4J-<] Slop Hsfre* Нам -• -Sestft ..'J ■ ftdAnii jg] Ь1ф:№сд»»Я!аз801«>»Нр1/«№к<гёГ» 1Г*М Welcome to SeiMets! HP™ Рис. 2.6. HTML-документ, в котором атрибут action элемента form используется для вызова сервлетэ WelcomeServlet через псевдоним welcomel, заданный в документе web.xml Общая методическая рекомендация 2.5 В документации на Tomcat указывается, как интегрировать Tomcat с популярными Web-серверами, такими как Apache HTTP Server и Microsoft IIS. Порты в данном случае не являются физическими аппаратными портами, к которым подключаются кабели; они являются логическими адресами с целочисленными значениями, которые дают возможность клиентам запрашивать различные сервисы на одном сервере. Номер порта задает логическое местоположение, где сервер ожидает и принимает соединения от клиентов. Когда клиент соединяется с сервером для запроса сервиса, клиент должен указать номер порта для этого сервиса; в противном случае клиентский запрос не будет обработан. Номера портов представляют собой положительные целые числа со значениями до 65535. Нумерация портов осуществляется отдельно для протоколов TCP и TJDP. Многие операционные системы резервируют номера портов до 1024 для системных сервисов (таких как
60 Глава 2 электронная почта и Всемирная паутина). Эти порты не следует использовать в своих серверных программах. Некоторые операционные системы требуют специальных привилегий для использования номеров портов с номерами, меньшими 1024. Имея такое множество портов для выбора, как клиент может узнать, какой именно порт использовать при запросе сервиса? Термин общеизвестный номер порта (well-known port number) часто используется при описании популярных сервисов в Internet, таких как Web-серверы и серверы электронной почты. Например, Web-сервер по умолчанию ожидает запросы клиентов на порту 80. Все Web-браузеры знают этот номер как общеизвестный порт Web-серверов, через который поступают запросы на HTML-документы. Поэтому, когда вы вводите URL в Web-браузере, браузер обычно соединяется с портом 80 сервера. Аналогично, сервер Tomcat использует порт 8080 в качестве своего номера порта. Таким образом, в запросах к Tomcat на Web-страницы или вызовах сервлетов и страниц JavaServer Pages должно быть указано, что сервер Tomcat ожидает запрос на порту 8080. Клиент может осуществить доступ к сервлету только в том случае, если сервлет установлен на сервере, который реагирует на запросы к сервлетам. В некоторых случаях поддержка сервлетов непосредственно встроена в Web-сервер, и для обработки запросов к сервлетам специальной настройки не требуется. В других случаях необходимо интегрировать контейнер сервлетов с Web-сервером (как это делается для сервера Tomcat или Web-серверов Apache и IIS). Для Web-серверов, которые поддерживают сервлеты, обычно предусмотрена процедура установки средств работы с сервлетами. Если вы намерены выполнять ваш сервлет под управлением Web-сервера, пожалуйста, обратитесь к документации на ваш Web-сервер за информацией, как установить сервлет. В наших примерах мы демонстрируем работу с сервлетами с помощью сервера Apache Tomcat. В разделе 2.3.1 описывается процесс установки и настройки Tomcat для использования его в этой главе. В разделе 2.3.2 описывается процесс развертывания сервлета, представленного на рис. 2.5. 2.3.1. Установка сервера Apache Tomcat Tomcat является полнофункциональной'реализацией стандартов JSP и сервлетов. В его состав входит Web-сервер, что позволяет использовать Tomcat как автономный контейнер для тестирования JSP-страниц и сервлетов. Tomcat также может применяться в качестве обработчика запросов JSP-страниц и сервлетов, принимаемых популярными Web-серверами, такими как Apache, разработанный Apache Software Foundation, или Microsoft Internet Information Server (IIS). Tomcat включен в эталонную реализацию Java 2 Enterprise Edition корпорации Sun Microsystems. Последняя версия Tomcat (версия 3.2.3) может быть загружена с сайта jaJtarta.apache.org/builds/iakarta-tomeat/release/v3.2 .З/Ып/ где содержится ряд архивных файлов. Полная реализация Tomcat содержит файлы, начинающиеся с имени jakarta-tomcat-3.2.3. Предоставляются файлы zip, tar и сжатые файлы tar для Windows, Linux и Solaris. Извлеките содержимое архивных файлов в каталог на вашем жестком диске. По умолчанию именем каталога, содержащего файлы Tomcat, является jakarta- tomcat-3.2.3. Чтобы Tomcat работал корректно, нужно определить переменные окружения JAVA_HOME и ТОМСАТ^НОМЕ. JAVA_HOME должна указывать на каталог, содержащий Java (у нас это d:\jdkl.3.1), а ТОМСАТ_НОМЕ должна указывать на каталог, который содержит Tomcat (у нас это d:\jakarta-tomcat-3.2.3).
Сервлеты 61 Совет по тестированию и отладке 2.1 На некоторых платформах вам может потребоваться перезагрузить ваш компьютер, чтобы новые переменные окружения вступили в действие. Задав переменные окружения, можно запустить сервер Tomcat. Откройте окно команд и перейдите к подкаталогу bin в jakarta-tomcat-3.2.3. В этом каталоге находятся файлы totncat.bat и tomcat, »h для запуска сервера Tomcat, соответственно, под Windows и под UNIX (Linux или Solaris). Чтобы запустить сервер, введите tomcat start Эта команда запускает сервер Tomcat. Сервер Tomcat выполняется на ТСР-порту 8080, чтобы не допустить конфликтов со стандартными Web-серверами, которые обычно выполняются на ТСР-порту 80. Для проверки функционирования Tomcat откройте ваш Web-браузер и введите в адресную строку http://localhost:8080/ Отобразится основная страница документации на Tomcat (рис. 2.7), localhost указывает Web-браузеру, что он должен запросить основную страницу с сервера Tomcat на локальном компьютере. Если основная страница документации на Tomcat не отображается, попробуйте задать URL http://127.0.0.1:8080/ localhost соответствует IP-адресу 127.0.0.1. Совет по тестированию и отладке 2.2 Если имя localhost на вашем компьютере не работает, попробуйте заменить его на IP-адрес 127.0.0.1. Чтобы завершить работу сервера Tomcat, выполните команду tomcat stop из командной строки (или командной оболочки). :-i»~~i""Y".0 |j**tt»-[a httjKftkxfltafl'HWrhfe* tin* *#(glj Ш*, Tomcat Tfrni a tfie sJrUuit Tvciildl bijnir page Ttur fAjfl эtrues W £ qu:cJt rrftmct fniLile to rtkwd «<^iKCts lud Ы l/icaled at м- </path/to/tomcat ^/wefcpages/index, htnl Tn&hidcd wi&aB tin* ntlt ue tr* fo-jch^nsl «rampJej with uiociited s oute 11 odi, API docuniEncaaop far stMtU mi J?Pr a README, a (eebnical FAQ on this rde« ? яа.4 an аа^оПшый of jar S1j5£ whyih art pf e-г; quuitea £>T tmHmucd development tffwtb I«hnol*£es induing JSP and Smriets, EsaatpL*!' тщ- Рис. 2.7. Основная страница документации на Tomcat (публикуется с разрешения Apache Software Foundation)
62 Глава 2 2.3.2. Развертывание Web-приложения JSP-страницы, сервлеты я их вспомогательные файлы развертываются как часть Web-приложения. Обычно Web-приложения развертываются в подкаталоге webapps каталога jakarta-tomcat-3.2.3. Web-приложение имеет известную структуру каталогов, в которых размещаются все файлы, являющиеся составной частью приложения. Эта структура каталогов может быть создана администратором сервера в каталоге webapps, либо вся структура каталогов может быть заархивирована б архивном файле Web-приложения. Такой архив известен как WAR-файл и имеет расширение .war. Если WAR-файл помещен в каталог 'webapps, то в начале выполнения сервер Tomcat извлекает содержимое WAR-файла в соответствующую структуру подкаталогов webapps. Для простоты при изучении сервлетов и JavaServer Pages мы создаем структуру каталогов для всех примеров в этой главе и в главе 3. Структура каталогов Web-приложения состоит из корня контекста — каталога верхнего уровня для всего Web-приложения — и нескольких подкаталогов. Эти подкаталоги описаны в таблице на рис. 2.8. Типичная ошибка программирования 2.1 При использовании в качестве кор}ш контекста каталога «servlet» или «servlets», на некоторых серверах сервлет не будет работать корректно. Для настройки корня контекста для Web-приложения в Tomcat достаточно просто создать подкаталог в каталоге webapps. Когда Tomcat начинает выполняться, он создает корень контекста для каждого подкаталога в webapps, используя имя подкаталога в качестве имени корня контекста. Чтобы протестировать примеры из этой главы и из главы 3, создайте подкаталог advjhtpl в каталоге webapps Tomcat. Каталог Корень контекста WEB 3NF web_INF/classes WEB_INF/lib Описание Это корневой каталог для Web-приложения. Имя этого каталога выбиоается разработчиком Web-приложения. Все JSP-страницы. HTML-доку менты, сервлеты и вспомогательные файлы, такие как изображения и файлы классов, размещаются в этом каталоге или в его подкаталогах. Имя этого каталога задается создателем Web-приложения, Чтобы задать структуру в Web-гриложении, подкаталоги могут быть помещены в корень контекста. Например, если приложение использует много изображений, можно поместить в этот каталог подкзтзгог изображений. Примеры из этой главы и из главы 3 используют в качестве корня контекста advjhtpl Этот каталог содержит дескриптор развертывания (web.xml) Web-приложения. Этот <аталог содержит файлы классов сервлета и другие файлы классов поддержки, используемые а Web-приложении. Если классы язляются частью пакета, здесь должна присутствовать полная структура каталогов пакета. Этот каталог содержит файлы архивов J3va (JAR). JAR-файлы могут содержать файлы классов сервлета и другие файлы классов поддержки, используемые в Web-приложении. Рис. 2.8. Стандартные катзлоги Web-приложения После настройки корня контекста, мы должны сконфигурировать наше Web- приложение для обработки запросов. Это конфигурирование осуществляется в дескрипторе развертывания, который хранится в файле с именем web.xml. Деск-
Сервлеты 63 риптор развертывания задает различные параметры конфигурации, такие как имя, используемое для вызова сервлета (т.е. псевдоним), описание сервлета, полное имя класса сервлета и карта сервлета (т.е. путь или пути, которые используются при вызове сервлета контейнером сервлетов). Для этого примера вы должны создать файл web.xml самостоятельно. Многие средства программирования на Java при развертывании Web-приложения сами создают файл web.xml. Файл web.xml для первого примера в этой главе представлен на рис. 2.9. На протяжении этой главы мы усовершенствуем этот файл по мере добавления других сервлетов в Web-приложение. 1 <!DOCTYPE web-app PUBLIC 2 "-//Sun Hicrosysterns, Inc.//DTD Web Application 2.2//EH" 3 "http://javs.sun,com/;)2ee/dtds/web-app_2_2-dtd"> 4 5 <web-app> 6 7 <!-- общее описание вашего Web-приложения --> 8 <display-name> 9 Advanced Java How to Program JSP 10 And Servlet Chapter Examples 11 </display-name> 12 13 <description> 14 This is the Web application in which we 15 demonstrate our JSP and Servlet examples. 16 </description> 17 18 <!— определение сервлета --> 19 <servlet> 20 <servlet-name>weleomel</servlet-name> 21 22 <deseription> 23 A simple servlet that handles an HTTP get request. 24 </description> 25 26 <servlet-class> 27 com.deitel.advjhtpl.servlets 28 </servlet-class> 29 <servlet> 30 31 <!-- задание карты сервлета --> 32 <servlet-mapping> 33 <servlet-name>welcomel</servlet-name> 34 <url-pattern>welcomel</url-pattern> 35 </servlet-mapping> 36 37 </web-app> Рис. 2.9. Дескриптор развертывания (web-xml) для Web-приложения advptpl В строках 1-3 задается тип документа для дескриптора развертывания и местонахождение DTD для этого XML-файла. Элемент web-app (строки 5-37) определяет конфигурацию каждого сервлета в Web-приложении и путь к сервлету. Элемент display-name (строки 8-11) задает имя, которое может быть отображено администратору сервера, на котором установлено Web-приложение, Элемент description
64 Глава 2 (строки 13-16) задает описание Web-приложения, которое может быть отображено администратору сервера. Элемент servlet (строки 19—29) описывает сервлет. Элемент servletname (строка 20) представляет собой имя, которое мы выбрали для этого конкретного сервле- та. Опять же, это имя может быть отображено администратору Web-сервера. Элемент servlet-class (строки 26-28) задает полное имя класса сервлета. Так, сервлет welcomel определяется классом com.deitel.advjhtpl.servlets.WelcomeServIet. Элемент servlet-mapping (строки 32—35) задает элементы servlet-name и url- pattern. Шаблон URL помогает серверу определять, какие запросы посылаются сервлету (welcomel). Наше Web-приложение будет установлено как часть корня контекста advjhtpl, рассмотренного в разделе 2,3.2. Так URL, который мы предоставили браузеру для вызова сервлета в нашем примере, имеет вид /advjtitpl /welcomel где /advjhtpl задает корень контекста, который помогает серверу определить, какое Web-приложение обрабатывает запрос, a /welcomel задает шаблон URL, который соответствует сервлету welcomel, обрабатывающего запрос. Обратите внимание, что сервер, на котором размещен сервлет, здесь не указывается, хотя можно это сделать, например, следующим образом: http://localhost:8080/advjhtpl/weleomel Если сервер и номер порта в URL явно не указаны, браузер полагает, что обработчик формы (т.е. сервлет, указанный в свойстве action элемента form) размещен на этом же сервере и том же номере порта, откуда браузер загрузил Web-страницу, содержащую форму form. Может быть использовано несколько форматов шаблонов URL. Формат /welcomel URL требует точного соответствия шаблону. Можно также задать соответствия по пути, соответствия по расширению и сервлет по умолчанию для Web-приложения. Формат с соответствием по пути начинается с /, а заканчивается /*. Например, шаблон URL /advjhtpl/exainple/* указывает, что данные будут отправлены сервлету, имеющему URL, который начинается с /advjhtpl/example/. В формате с соответствием по расширению в начале указывается *,, а в конце — расширение файла. Например, шаблон URL *.jsp указывает, что любой запрос на файл с расширением .jsp будет отправлен сервлету, который обрабатывает JSP-запросы. Фактически, серверы с контейнерами JSP имеют неявно установленное соответствие по расширениям .jsp с сервлетом, который обрабатывает JSP-запросы. Шаблон URL / представляет сервер по умолчанию для Web-приложения. Здесь есть аналогия с документом по умолчанию для Web-сервера. Например, если вы введете URL wwwr.deitel.com в вашем Web-браузере, то документом, который вы получите с Web-сервера, по умолчанию будет документ irtdex.html. Если шаблон URL соответствует сервлету по умолчанию для Web-при л ожени я, этот сервлет вызывается, чтобы возвратить клиенту ответ по умолчанию. Это может быть полезным при персонализации Web-содержимого для определенных пользователей. О персонализации мы поговорим в разделе 2.7, «Отслеживание состояния сеанса». Наконец, мы готовы поместить наши файлы в соответствующие каталоги, чтобы закончить развертывание нашего первого сервлета и приступить к его тестированию. Имеются три файла, которые мы должны поместить в соответствующие каталоги: WeleomeServlet.html, WelcomeServIet.class и web.xml. В подкаталоге webapps каталога jakarta-tomcat-3.2.3 создайте подкаталог advjhtpl, который представляет корень контекста для нашего Web-приложения. В этом каталоге соз-
Сервлеты 65 дайте подкаталоги с именами servlet и WEB_INF. Мы помещаем наши HTML- файлы для сервлетов из этой главы в каталог servlets. Скопируйте файл Welcome- ServJet.html в каталог servlets. В каталоге WEB_INF создайте подкаталог classes, затем скопируйте файл web.xml в каталог WEB_INF и скопируйте файл Welcome- Servlet.class, включая все каталоги его пакета, в каталог classes. Структура файлов и каталогов в каталоге webapps должна иметь вид, представленный на рис. 2.10 (имена файлов выделены курсивом). Структура каталогов и файлов Web-приложен и я WekomeServlet advjhtpl servlets HelcomeServlet. htntl WEB-INF veb.jcni classes com deitel advjhtpl servlets WelcameServlet.class Рис. 2.10. Структура каталогов и файлов Web-приложения WekomeServlet Совет по тестированию и отладке 2.3 Перезапустите сервер Tomcat после внесения изменений в файл дескриптора развертывания web.xml. В противном случае Tomcat не сможет распознать новое Web-приложение. Поместив файлы в соответствующие каталоги, запустите сервер Tomcat, откройте браузер и введите следующий URL http://localhost:8080/advjhtpl/servlets/WelcomeServlet.htjnl чтобы загрузить документ WelcomeServlet.btml в Web-браузер. Затем щелкните на кнопке Get HTML Document, чтобы вызвать сервлет. Вы должны увидеть результат, показанный на рис. 2,6. Можно попробовать испытать работу этого серв- лета в нескольких различных Web-браузерах, чтобы убедиться в идентичности выдаваемых им результатов. Типичная ошибка программирования 2.2 Если не поместись сервлет или другие файлы классов в соответствующую структуру каталогов пакета, сервер не сможет обнаружить эти классы. Это, в свою очередь, приведет к выдаче Web-браузером клиента сообщения об ошибке. Такое сообщение в Netscape Navigator обычно имеет вид «Not Found (404)» («Не найдено»), а в Microsoft Internet Explorer — «Thepage cannot be found» («Страница не найдена»), и содержит необходимые пояснения. На самом деле HTML-файл, представленный на рис. 2.6, не является обязательным для вызова этого сервлета. Запрос get может быть отправлен на сервер путем простого ввода URL в адресной строке Web-браузере. Фактически, именно это вы и делаете, когда запрашиваете Web-страницу в браузере. В этом примере вы можете ввести http://localhost:8080/advjhtpl/welcomel в поле Address или Location вашего браузера, чтобы вызвать сервлет напрямую
66 Глава 2 Совет по тестированию и отладке 2.4 Вы можете протестировать сервлет, который обрабатывает HTTP-запросы get, введя в поле Address или Location вашего браузера URL, кото рый непосредственно вызывает сервлет. 2А. Обработка HTTP-запросов get, содержащих данные При запросе документа или ресурса с Web-сервера можно передать данные как часть запроса. Сервлет WelcomeServlet2, показанный на рис. 2.11, реагирует на HTTP-запрос get, который содержит имя, предоставленное пользователем. Сервлет использует имя как часть ответа клиенту. 1 // Рис. 2.11. WeleomeServlet2.Java 2 // Обработка HTTP-запросов get, содержащих данные. 3 package com.deitel.advjhtpl.servlets; 4 5 import javax.servlet.*; 6 import j avax.servlet.http.*; 7 import java.io.*; 8 9 public class WelcomeServlet2 extends HttpServlet { 10 11 // обработка клиентского запроса get 12 protected void doGet( HttpServJLetRequest request, 13 HttpServletRespqnse response ) 14 throws ServletException, IOExceptiort 15 t 16 String firstName = request.getParameter( "firstname" ); 17 18 response.setContentType( "text/html" ); 19 PrintWriter out = response.getWriter[); 20 21 // отправка XHTML-документа клиенту 22 23 // начало XHTML-документа ' 24 out.println( "<?xml version = V'1.0\"?>" ); 25 26 out.println( "<!DOCTYPE html PUBLIC V-//W3C//DTD " + 27 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 28 "/TR/xhtmll/DTD/xhtInll-strict.dtd\,,>,, ); 29 30 out.printlnf 31 H<html xmlns = V'http: //www. w3 . org/1 gag/xhtmiy:»" ) ,- 32 33 // заголовок документа 34 out.println( "<head>" ); 35 o«t.println( 36 "<title>Proceasing get requests with data</title>" ); 37 out.println( "</head>" >; 38 39 // тело документа 40 out.println( "<body>" ); 41 out.println( "<hl>Hello " + firstWame + ",<br />" ); 42 out.println( "Welcome to Servlets!</hl>" ); 43 out.println( "</body>" );
Сервлеты 67 44 45 // конец XHTML-документа 46 out.println( "</html>" ); 4 7 out.close(); // закрытие потока и конец страницы 48 } 49 } Рис. 2.11. Сервлет WekomeServlet2 реагирует на запрос get, содержащий данные Параметры в запросе get передаются в виде пар имя/значение. В строке 16 демонстрируется, каким образом получить информацию, которая передается сервле- ту как часть клиентского запроса. Метод getParameter объекта request принимает в качестве аргумента имя параметра и возвращает соответствующее строковое (String) значение или null, если параметр не является частью запроса. В строке 41 результат выполненного в строке 16 действия помещается в ответ клиенту. Документ WeIcomeServlet2.html (рис. 2.12) предоставляет форму (элемент form), в которой пользователь может ввести имя firstname в качестве текста, запрашиваемого элементом input (строка 17) и щелкнуть на кнопке Submit, чтобы вызвать сервлет WelcomeServlet2. Когда пользователь нажимает кнопку Submit, значения, содерисащиеся в элементах input, помещаются в пары имя/значение в составе запроса, отправляемого серверу- Взглянув на вторую копию экрана на рис. 2.12, вы можете увидеть, что браузер добавил ?firsfcname=Paul в конец URL, указанного в параметре action. Символ ? отделяет строку запроса (т.е. данные, передаваемые как часть запроса get) от остальной части URL в запросе get. В паре имя /значение имя отделяется от значения символом =. Если имеется более одной пары имя/значение, пары отделяется друг от друга символом &. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <1— Рис. 2.12. WelcomeServlet2.html --> 6 7 <html xmlns = "http://www.w3.org/1999/xhtnil"> 8 <head> 9 <title>Processing get requests with data</title> 10 </head> 11 12 <body> 13 <form action = "/advjhtpl/welcome2" method = "get"> 14 15 <pxiabel> 16 Type your first name and press the Submit button П <br /Xinput type = "text" name = "firstname" /> 18 <input type = "submit" value = "Submit" /> 19 </pX/label> 20 21 </form> 22 </body> 23 </html> Рис. 2.12. HTML-документ, в котором действие action формы form приводит к вызову сервлета WelcomeServlet2 через псевдоним wekome2, заданный в документе web.xml (часть 1)
68 Глава 2 * . ■■+■ Г~'Э Щ Га"-7"/"#""":'"""' . В»*. S»** [^w»)@htlii:fAotallitKM81!Ki,'«Witpi;sgvleaWelciira5efvfet;.htoil jj. t^S° j Type your first name afid press the Submit button [i^i [ Submit J SSS£T :Г^Ш&«<«КУ Trf Ji ft Qccsuni^ qct * cojut-tf* W*b del Л - MiCrtrttift IHliajflilSf^ J:jfc.-JE*. a»t^a^ «я». Fjyfftli Тонь Нф ]М*|Я,Л Sttfl ■ Itefttil) bant j .S«Mh W*^*jC] t*tP' tflccahKt 'вОЩ* sdvMp 1 ^4fccme2*irstr4nie-P5ul ' *ZJ ,.£><*! Hello Paul, Welcome to Servlets! ieiBssfe .^штШШ: ■mmmgms*? Данные, полученные \лз фор^ы, присутствуют a URL как часть запроса get Рис. 2.12. HTML-документ, в котором действие action формы forrn приводит к вызову сервлета WekomeServlet2 через псевдоним welcome2, заданный в документе web.xml (часть 2) Мы, как и ранее, используем наш корень контекста advjhtpl для демонстрации работы сервлета, представленного на рис. 2.11. Поместите документ Welcome- Servlet2.html в каталог servlets, созданный в разделе 2.3.2. Поместите класс WelcomeServlet2.class в подкаталог classes каталога WEB_INF в корне контекста advjhtpl. Помните, что классы в пакете должны быть помещены в соответствующую структуру каталогов пакета. Затем отредактируйте дескриптор развертывания web.xml в директории WEB_INF, чтобы включить в него информацию, приведенную в таблице на рис. 2.13. Эта таблица содержит данные для элементов servlet и servletmapping, которые добавляются в дескриптор развертывания web.xml. Текст, выделенный курсивом, вводить в дескриптор развертывания не следует. Перезапустите Tomcat и введите следующий URL в вашем Web-браузере: http://localh.ost:8080/advjhtpl/servlets/WelcomeServlet2.html Введите ваше имя в текстовом доле Web-страницы, затем щелкните на кнопке Submit для вызова сервлета. Еще раз заметим, что запрос get можно ввести и непосредственно в поле Address или Location браузера следующим образом: bttp://localhost:8080/advjhtpl/welcome2?firstname=Paul Попробуйте проделать это с вашим собственным именем. Элемент Дескриптора servlet элемент servlet-name Description servlet-class Значение welcome2 Handling HTTP get requests with data com.deitel.advjhtpl,servlets.WelcomeServlet2
Сервлеты 69 Элемент дескриптора servlet-mapping элемент servlet-name url-pattern Значение welcome2 /■w«lcoms2 Рис. 2.13. Информация в дескрипторе развертывания, относящаяся к сервлету Wekom e S e rv Iet2 2.5. Обработка HTTP-запросов post HTTP-запрос post часто используется для передачи данных из HTML-формы серверному обработчику формы, который выполняет обработку данных. Например, когда вы отвечаете на вопросы Web-анкеты, информация, которую вы ввели в HTML-форме, обычно передается на Web-сервер э виде запроса post. Браузеры часто кэшируют Web-страницы, чтобы иметь возможность быстро их перезагрузить. Если последняя версия, хранящаяся в кэш, не отличается от текущей версии в Web, время, затрачиваемое на отображение страницы, существенно сокращается. Браузер сначала запрашивает у сервера, изменился ли документ, или истекло ли определенное установленное время после кэширования файла. Если нет, браузер загружает документ из кэша. Таким образом, браузер минимизирует количество данных, которые должны быть загружены, чтобы можно было просмотреть Web-страницу. Браузеры обычно не кэшируют ответ сервера на запрос post, поскольку следующий запрос post может не вернуть тот нее самый результат. Например, при проведении опроса многие пользователи могут посетить одну и ту же Web-страницу и ответить на вопрос анкеты. Результаты опроса могут затем быть представлены пользователю. Каждый новый ответ изменяет общий итог опроса. Когда вы пользуетесь поисковым сервером в Web, браузер обычно предоставляет поисковому серверу информацию, которую вы задаете в HTML-форме, с помощью запроса get. Поисковый сервер выполняет поиск, а затем возвращает вам результаты в виде Web-страницы. Такие страницы часто кэшируются браузером на случай, если вы снова будете выполнять тот же поиск. Как и запросы post, запросы get могут предоставлять Web-серверу параметры как часть запроса. Сервлет WelcomeServtet3, представленный на рис. 2.14, идентичен сервлету на рис. 2.11, за исключением того, что он определяет метод doPost (строка 12) для реакции на запросы post, вместо метода doGet. В соответствии с настройкой по умолчанию, метод do Post вызывает ошибку *Method not allowed» («Недопустимый метод»). Мы замещаем этот метод, чтобы обеспечить собственную обработку запроса post. Метод doPost принимает те же два параметра, что и метод doGet: объект, который реализует интерфейс HttpSeivletRequest для представления клиентского запроса, и объект, который реализует интерфейс HttpServletResponse для представления ответа сервера. Как и метод doGet, метод doPost возбуждает исключение ServletException, если он не способен обработать клиентский запрос, и исключение IOException, если при обработке потока возникает проблема. 1 // Рис. 2.14. WelcomeServlet3.Java 2 // Обработка запросов post, содержащих данные. 3 package com.deitel.advjhtpl,servlets; 4 5 import javax.servlet.*; 6 import javax.servlet.http.*; 7 import java.io.*;
70 Глава 2 в 9 public class WelcomeServlet3 extends HttpServlet { 10 11 // обработка клиентского запроса post 12 protected void doPost( HttpServletRequest request, 13 HttpServletResponse response ) 14 throws ServletException, IOException 15 1 16 String firstName = request.getParameter{ "firstname" ); 17 18 response.setContentType( "text/html" ); 19 PrintWriter out = response.getWriter(); 20 21 // отправка XHTML-страницы клиенту 22 23 // начало XHTML-документа 24 out.println( "<?xml version = \"1.0\"?>" ); 25 2 6 out.println( "<!DOCTYPE html PUBLIC V-//W3C//DTD " + 27 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 28 "/TR/xhtmll/DTD/xhtmll-strict.dtd\">" ) : 29 30 out. priii tin { 31 "<html xmlns = \"http://www.w3.org/1999/xhtml\">" ); 32 33 // заголовок документа 34 out.println( "<head>" ); 35 out.println( 36 "<title>Processing post requests with data</title>" ); 37 out.println( "</head>" ); 38 39 // тело документа 40 out.println( "<body>" ); 41 out.println( "<hl>Hello " + firstName + ",<br />" ) ; 42 out.println( "Welcome to Servlets!</hl>" ); 43 out.println [ "</body>" ) ,- 44 45 // конец XHTML-документа 46 out.println{ "</html>" ); 47 out. closed; // закрытие потока и конец страницы 48 } 49 } Рис. 2.14. Сервлет WekomeServlet3 отвечает на запрос post, содержащий данные Документ WeIcomeServlet3.html (рис. 2.15) предоставляет форму (элемент form, строки 13-21), в которую пользователь может ввести имя firstname в качестве запрашиваемого элементом input (строка 17) текста, а затем щелкнуть на кнопке Submit, чтобы вызвать сервлет WelcomeServlstS. Когда пользователь нажимает кнопку Submit, значения элементов input посылаются на сервер как часть запроса. Однако имейте в виду, что значения при этом не добавляются в строку URL запроса. Обратите внимание, что в качестве метода в этом примере указано post. Имейте в виду также, что запрос post не может быть введен в поле Address или Location браузера, и пользователи не могут помещать запросы post в список избранных запросов в своих браузерах.
Сервлеты 71 1 2 3 4 5 6 7 8 9 10 11 12 13 Id 15 16 17 18 19 20 21 22 23 <?xml version = "1.0"?> <!DOCTTTPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> <! Рис. 2.15: WelcomeServlet3.html —> <html xmlns = "bttp://www.w3.org/1999/xhtoil"> <head> <title>Handling an HTTP Post Request with Data</title> </head> <body> <fonn action = "/advjhtpl/welcome3" method = "post"> <pXlabel> Type your first name and press the Submit button <br /Xinput type = "text" name = "firstname" /> <input type = "submit" value = "Submit" /> </labelX/p> </form> </body> </html> 1 Handling an i-г I и1 ftrtt Redur«t itflh Oate - МнтфиА '. '"ffiw* . * ivwwja.^ •• '■agi :V •'' 1цй^'. 4<м» • jfe; Swnfr - -.' t't^^ft^'-^^ «••',;/ Type your first name and press the Submit button [Paul .:*:. L 7f7?gjR!yaiCS«4»te^ft^ J Processmy THJ*t request* withddL^ - MHfOtoft Int«14$ Jjbj-; ЕЛ 5; irtw*. '.Fevarito* _T«fcii is^Jtei ■■■■1J»~- 1Яр ;(/1ка1мя lOBO/jdvMlil/""1:"™ 3 zJ ^1 Hello Paul, Welcome to Servlets! d -г^г^р1^щ1й^^^^.~^ Рис. 2.15. HTML-документ, в котором в качестве действия action для формы form предусмотрен вызов сере, пет a Wekome5eivlet3 через псевдоним welcome3, заданный в документе web.xml Мы используем корень контекста advjhtpl для демонстрации работы сервлета, представленного на рис. 2.14. Поместите документ WelcomeServlet3.html в каталог servlets, созданный в разделе 2.3.2. Поместите файл WelcomeServlet3 в подкаталог classes каталога WEB_INF в корне контекста advjhtpl. Затем отредактируйте дескриптор развертывания web.xml в каталоге WEB_INF, чтобы включить
72 Глава 2 в него информацию, приведенную в таблице на рис. 2.L6. Перезапустите Tomcat и введите следующий URL в вашем браузере: http://localhost:8080/advihtpl/servlets/WelcomeServlet3.html Введите ваше имя в текстовом поле на Web-странице, а затем щелкните на кнопке Submit, чтобы выполнить сервлет. Элемент дескриптора servlet элемент servlet-name description servlet-class servlet-mapping элемент servlet-name url-pattern Значение welcome3 Handling HTTP post requests with data com.deitel.advjhtpl.servlets.WelcomeServlet3 we1come3 /welcome3 Рис. 2.16. Информация в дескрипторе развертывания, относящаяся к сервлету WeicomeServtet3 2.6. Переадресация запросов Иногда полезно переадресовать запрос другому ресурсу. Например, сервлет может определить тип клиентского браузера и переадресовать запрос на Web-страницу, которая была специально разработана для этого браузера. Сервлет Redirect- Servlet, представленный на рнс. 2.17, принимает параметр страницы как часть запроса get, а затем использует этот параметр для переадресации запроса на другой ресурс. 1// Рис. 2.17. RedirectServlet.java 2 // Переадресация пользователя к другой Web-странице. 3 package com.deitel.advjhtpl.servlets; 4 5 import javax.servlet.*; 6 import javax.servlet.http.*;м 7 import java.io.*; 8 9 public class RedirectServlet extends HttpServlet { 10 // обработка клиентского Запроса get protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, lOException 11 12 13 14 15 16 17 IB 19 20 21 22 23 24 t String location = request.getParameter( "page" ); if ( location != null ) if ( location.equals( "deitel" } ) response.sendRedirect( "http://www.deitel.com" ) else if ( location.equals( "welcome!" ) ) response.sendRedirect( "welcomel" );
Сервлеты 73 25 2 6 // код, который выполняется только в тон случае, если этот 27 // сервлет не переадресовывает пользователя к другой странице 28 29 response.setContentType( "text/html" ); 30 PrintWriter out = response,getWriterО; 31 32 // начало XHTML-документа 33 out.println( "<?xrol version = \"1.0\"?>" ); 34 35 out.println( "<!DOCTYPE html PUBLIC V-//W3C//DTD " + 36 "XHTML 1.0 Strict//EN\" \"http://www.w3.org" + 37 "/TR/xhtmll/DTD/xhtmll-strict.dtd\">" ); 38 39 out.printlnf 40 "<html xmlns = V'http://www.w3.org/1999/xhtml\">" ); 41 42 // заголовок документа 43 out.println( "<head>" ); 44 out.println( "<title>Invalid page</title>" ); 45 out.println( "</head>" ); 46 47 // тело документа 48 out.println( "<body>" ); 49 out.println( "<hl>Invalid page requested</hl>" ); 50 out.println( "<pXa href — " + 51 "\"servlets/RedirectServlet.html\">" ); 52 out,println( "Click here to choose again</aX/p>" ); 53 out.println( "</body>" ); 54 55 // конец XHTML-документа 56 out.println( "</html>" ); 57 out,close О; // закрытие потока и конец страницы 58 ) 59} Рис. 2.17. Переадресация запросов другим ресурсам В строке 16 параметр page извлекается из запроса. Если возвращаемое значение не равно null, структура if /else в строках 20-24 определяет, соответствует ли значение "deitel" или "welcorael". Если значением является "deitel", метод setid- Redirect объекта response (строка 21) переадресовывает запрос на www.deitel.coni, Если значением является "welcomel", в строка 24 запрос переадресовывается сервлету, представленному на рис, 2.5. Обратите внимание, что в строке 24 корень контекста advjhtpl для нашего Web-приложения не задан явно. Когда сервлет использует относительный путь для ссылки на другой статический или динамический ресурс, сервлет предполагает использование того же базового URL и корня контекста, как и те, которые использовались при вызове сервлета, если только для ресурса не был задан абсолютный URL. С учетом этого в строке 24 в действительности запрашивается ресурс, расположенный по адресу http://localhost:80B0/adv3htpl/welcomel Аналогично, в строке 51 в действительности запрашивается ресурс, расположенный по адресу http://localhost:8080/advjhtpl/servlets/RedirectServlet.html
74 Глава 2 Общая методическая рекомендация 2.6 Использование относительных путей для ссылки на ресурсы, размещенные в этом же корне контекста, делает Web-приложение более гибким. Например, еы можете изменить корень контекста, не внося изменений в статические и динамические ресурсы в приложении. После выполнения метода sendRedirect обработка запроса сервлетом RedirectServlet завершается. Если метод sendRedirect не был вызван, обработчик внештатных ситуаций метода doPost выводит Web-страницу, указывающую, что был сделан некорректный запрос. Страница дает возможность пользователю повторить попытку, возвращая XHTML-документ, представленный на рис. 2.18. Обратите внимание, что одна переадресация осуществляется на статическую Web-страницу, а другая — на сервлвт. Документ RedirectServIet.html (рис. 2.18) предоставляет две гиперссылки (строки 15-16 и 17-18), которые дают возможность пользователю вызывать серв- лет RedirectServlet. Обратите внимание, что для каждой гиперссылки параметр page задан как часть URL. Чтобы продемонстрировать передачу в качестве параметра некорректной страницы, вы можете ввести в браузере URL без указания значения для параметра page. Мы используем наш корень контекста advjhtpl для демонстрации работы серв- лета, представленного на рис. 2.17. Поместите файл RedirectServlet.html в каталог servlets, созданный в разделе 2.3.2. Поместите файл RedirectServlet.class в подкаталог classes каталога WEB_INF в корне контекста advjhtpl. Затем отредактируйте дескриптор развертывания web.xml в каталоге WEB_INF, чтобы включить в него информацию, приведенную в таблице на рис. 2.19. Перезапустите Tomcat и введите следующий URL в вашем браузере: http://localhost:8080/advjhtpl/servlets/RedirectServlet.html Щелкните на гиперссылке на Web-странице, чтобы вызвать сервлет. 1 <?xml version = "l.Q"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<'-- Рис. 2.18. RedirectServlet.html —> 6 7 <html xmlns = "http://www.w3.org/1999/xhtml"> 8 <head> 9 <title>Redirecting a Request to Another Site</title> 10 </head> 11 12 <body> 13 <p>Click a link to be redirected to the appropriate page</p> 14 <p> 15 <a href = "/advjhtpl/redirect?page=deitel"> 16 www.deitel.com</aXbr /> 17 <a href = "/advjhtpl/redirect?page=welcomel"> 18 Welcome servlet</a> 19 </p> 20 </body> 21 </html> Рис. 2.18. Документ RedirectServlet.html, демонстрирующий переадресацию запросов другим ресурсам (часть 1)
Сервлеты 75 'Ь,!^'^^^»ИД)Ш№1ИРИ! , .^^^l^t^^f^atio^BCaO/advjNipl.'sgvlats/Rj^MtStrvlet.htiri'' " ' jfjljgie^fe СНск a link to be redirected to the appropriate page www dedtl com Weicomafftrvlet в ..а ; Utal intranet "^ ■a A Simple *er»lrt Eiijtnif's - Microiofl Internet Ьц&иг Fto" Edt'wewf'Fworlter те* Нф '-'"'■--I V i »i -т"-;Г.*Г:; '.9 -.a"-:--Ja-.'i ©. ." ш . . . '"* "?** IM'J^A.-1*^*»*^ ;.±-jt^. * :.jr T_ .. *■■*-■* *t* '■■■"4- J-*-* -J ■■■. т х-— ' ~* *■ "V*~* * "^ jfrt^ffibttpifflKa»og:30Wfad*№i;wefcofrMl _ jJT'y&t j Welcome to Servlets! mpvwmmm^- tsmmm Рис. 2.18. Документ RedirectServlet.html, демонстрирующий переадресацию запросов другим ресурсам (часть 2) Элемент дескриптора servlet элемент serv1еt-name description servlet-class servlet-mapping элемент servle t-name url-pattern Значение 1 Redirect i Redirecting to static Web-pages and other servlets com.deitel.advjhtpl.servlets.RedirectServlet Redirect /redirect Рис. 2.19. Информация в дескрипторе развертывания, относящаяся к сервлету RedirectServlet При переадресации запросов параметры исходного запроса передаются новому запросу. Могут также передаваться дополнительные параметры запроса. Например, URL, передаваемый методу sendRedirect, может содержать пары имя/значение. К имеющимся параметрам добавляются любые новые параметры. Если новый параметр имеет то лее имя, что и существующий параметр, новое значение параметра имеет приоритет над исходным значением. Однико передаются по-прежнему все значения. В подобном случае полный набор значений для параметра с заданным именем может быть получен путем вызова метода getParameterValue интерфейса HttpSorvletRequest. Этот метод принимает в качестве параметра имя параметра и возвращает массив строк, содержащих значения параметра, в порядке от самого последнего к самому первому.
76 Глава 2 2.7. Отслеживание состояния сеанса Многие компании, занимающиеся электронным бизнесом, используют индивидуальную настройку (персонализацию) Web-страниц в соответствии с личными предпочтениями пользователей и предоставляют пользователям возможность получать только нужное им содержимое. Это достигается путем слежения за перемещениями потребителя со Internet и использования этих данных в совокупности с информацией, предоставленной потребителем, такой, как сведения о сделанных покупках, интересы, увлечения и т.д. Персонализация облегчает для многим людям процесс «серфинга» по Internet, делает его более интересным и позволяет быстрее находить то, что им нужно. Потребители и компании, ведущие свой бизнес через Internet, могут воспользоваться преимуществами, которые дает персонали- зация, уникальным образом интерпретируя свой данные для определенного пользователя. Предоставляя содержимое в соответствии с интересами посетителя, можно установить с ним определенные отношения, которые можно воссоздавать каждый раз, когда этот посетитель возвращается на сайт. Индивидуальная работа с потребителями путем персональных предложений, направленной рекламы, скидок и оказания персональных услуг может повысить степень расположения потребителя — многим людям нравится, когда им оказывают особое, персональное внимание. Первоначально в Internet не было такой индивидуальной работы с потребителем, которая характерна для частных магазинов. Усовершенствованные технологии дают возможность многим Web-сайтам индивидуально работать со своими посетителями. Например, такие Web-сайты, как MSN.com и CNN.com позволяют вам настраивать свои основные страницы в соответствии с вашими нуждами. Сайты виртуальных магазинов часто настраивают свои Web-страницы под отдельных покупателей. Такие сайты должны уметь различать клиентов, чтобы предлагать соответствующие товары и цены для каждого клиента. Персонализация важна для маркетинговой деятельности в Internet и для установления доверительных отношений с потребителями, чтобы добиться их расположения. Однако при всех тех плюсах, которые дает переонализация, она порождает проблему, связанную с вторжением в частную жизнь. Что, если компания, занимающаяся электронным бизнесом, которой вы сообщили ваши личные данные, продаст или передаст эти данные другой организации без вашего ведома? Что, если вы не хотите, чтобы ваши перемещения по Internet отслеживались неизвестно кем? Что, если не имеющая на то полномочий сторона получит доступ к вашим конфиденциальным данным, таким как номера кредитных карт или история болезни? Это лишь некоторые из многих вопросов, с которыми должны считаться потребители, компании, ведущие бизнес в Internet, и законодатели. Как уже говорилось, механизм запрос/ответ основан на протоколе HTTP. К сожалению, HTTP представляет собой протокол без сохранения состояния — он не поддерживает сохранение информации, которая помогает Web-серверу определить, какой запрос поступил от какого клиента. С точки зрения Web-сервера, каждый из запросов мог поступить как от одного и того же клиента, так и от разных клиентов. В этой связи сайты, подобные MSN.com и CNN.com, нуждаются в механизме для идентификации отдельных клиентов. Чтобы помочь серверу различать клиентов, каждый клиент должен идентифицировать себя перед сервером. Для того чтобы различать клиентов, используется ряд приемов. Мы познакомимся с двумя методами отслеживания клиентов: cookies (раздел 2.7.1) и отслеживание сеансов (раздел 2.7.2). Двумя другими методами, которые не будут рассматриваться в этой главе, являются использование скрытых полей форм и перезапись VRL. При использовании скрытых полей формы сервлет может записывать данные о сеансе в форму на Web-странице, которую сервлет вернул клиенту при обработке его предшествующего запроса. Когда пользователь отправляет форму в новой Web-
Сервлеты 77 странице, все данные формы, включая скрытые поля, посылаются обработчику формы на сервере. При перезаписи URL сервлет встраивает информацию о сеансе в качестве параметров запроса get в URL гкперссылок, на которых пользователь может щелкнуть мышью, чтобы отправить следующий запрос Web-серверу. 2.7.1. Cookies Одним из популярных способов индивидуальной настройки Web-страниц является применение cookies. Браузеры могут хранить cookies на компьютере пользователя для последующего извлечения в этом же сеансе или в последующих сеансах. Например, cookies могут использоваться в приложениях Internet-магазинов, чтобы хранить уникальные идентификаторы пользователей. Когда пользователи добавляют элементы в свои магазинные тележки или выполняют другие задачи, результатом которых является выдача запроса Web-серверу, сервер принимает файлы cookies, содержащие уникальные идентификаторы пользователей. Сервер затем использует уникальный идентификатор для нахождения магазинной тележки и выполнения необходимой обработки. Cookies могут также использоваться для идентификации клиентских предпочтений. При взаимодействии с клиентом сервлет может просмотреть cookie (или несколько cookies), которые он отправил клиенту при предыдущем взаимодействии, идентифицировать предпочтения клиента и немедленно отобразить интересующие клиента товары. Cookies представляют собой текстовые данные, которые посылаются сервлета- ми (или другими серверными технологиями) как часть ответов клиентам. Каждое взаимодействие на базе HTTP между клиентом и сервером включает в себя заголовок, содержащий информацию о запросе (при направлении взаимодействия от клиента к серверу) или информацию об ответе (при направлении взаимодействия от сервера к клиенту). Когда объект HttpServlet принимает запрос, заголовок содержит информацию, такую как тип запроса (например, get или post) и cookies, которые были отправлены сервером для сохранения их на компьютере клиента. Когда сервер подготавливает свой ответ, заголовок может включать любые cookies, которые сервер хочет сохранить на компьютере клиента, а также другую информацию, например, MIME-тип ответа. j^*. Совет по тестированию и отладке 2.5 \Wy Некоторые клиенты не принимают cookies. Если клиент отвергает cookies, Web-сайт или браузер может проинформировать пользователя, что сайт, возможно, не будет функционировать надлежащим образом при запрещенных cookies. В зависимости от максимального возраста cookie, Web-браузер либо хранит cookie в течение сеанса просмотра (т.е. до тех пор, пока пользователь не закроет Web-браузер), либо сохраняет cookie на компьютере клиента для последующего использования. Когда браузер запрашивает ресурс с сервера, cookies, ранее отправленные клиенту этим сервером, возвращаются сервлету как часть запроса, выданного браузером. Cookie автоматически удаляются по истечении их срока хранения (т.е. по достижении ими максимального возраста). Пример на рис. 2.20 демонстрирует применение cookies. В примере пользователь может выбрать любимый язык программирования и отправить (методом post) свой выбор на сервер. Ответом является Web-страница, в которой пользователь может выбрать другой любимый язык или щелкнуть на ссылке, чтобы просмотреть список рекомендуемых книг по данному языку программирования. Когда пользователь выбирает список рекомендуемых книг, на сервер посылается запрос get. Cookies, ранее сохраненные на клиенте, прочитываются сервлетом и используются для формирования Web-страницы, содержащей рекомендуемые книги.
Глава 2 1 // Рис. 2.20. CookieServlet.java 2 // Использование cookies для хранения данных на компьютере клиента. 3 package com.deitel.advjhtpl.servlets; 4 5 import javax.servlet.*; 6 import javax.servlet.http.*; 7 import java.io.*; 8 import java.util.*; 9 10 public class CookieServlet extends HttpServlet { 11 private final Кар books = new HashMapO; 12 13 // инициализация таблицы books 14 public void init() 15 { 16 books.put( "С", "0130895725" ); 17 books.put{ "C++", "0130895717" ); 18 books.put( "Java", "0130125075" ); 19 books.put( "VB6", "0134569555" ); 20 J 21 22 // получение выбранного языка и отправка клиенту cookie, 23 // содержащего рекомендуемую книгу 24 protected void doPost( HttpServletRequest request, 25 HttpServletHesponse response ) 26 throws ServletException, lOException 27 { 28 String language = request.getParameter( "language" ); 29 String isbn = books.get( language ) .toStringO ; 30 Cookie cookie = new Cookie( language, isbn ); 31 32 response .addCookie ( cookie ) ; // должно предшествовать getHriter 33 response.setContentType( "text/html" ); 34 PrintWriter out = response.getWriter(); 35 36 // отправка XHTML-страницы клиенту 37 38 // начало XHTML-документа 39 out.println( "<?xml version = \"1,0V'?>" ); 40 41 out.println{ "<!DOCTYPE html PUBLIC \"-//W3C//DTD " + 42 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 43 M/,ni/xhtmll/DTD/xhtmll-strict.dtdV,>" ); 44 45 out.println{ 46 "<html xmlns = \"httpr//www.w3.org/1999/xhtml\">" ); 47 48 // заголовок документа 49 out .print-in f "<head>" ); 50 out.println( "<title>Welcome to Cookies</title>" ); 51 out.println( "</head>" ) ; 52 53 // тело документа 54 out.println( "<body>" ); 55 out.println( "<p>Welcome to Cookies' You selected " + 56 language + "</p>" ),-
Сервлеты 79 57 58 out.printlnt "<pXa href = " + 59 "\"/advjhtpl/servlete/CookieSelectLanguage.html\">" + 60 "Click here to choose another language</aX/p>" ) ; 61 62 out.printlnt "<pxa href = \"/advjhtpl/cookies\">" + 63 "Click here to get book recommendations</aX/p>" ) ; 64 out.printlnt "</body>" ); 65 66 // конец XHTML=документа 67 out.printlnt "</html>" ); 68 out.closed; // Закрытие потока 69 } 70 71 // чтение cookie с клиента и создание XHTML-документа, 72 // содержащего список рекомендуемых книг 73 protected void doGet( HttpServletRequest request, 74 HttpServletResponse response ) 75 throws ServletException, XOException 76 { 77 Cookie cookies[] = request.getCookies(); // получение cookies 78 79 response.setContentType( "text/html" ); 80 PrintWriter out = response.getWriter(); ei 82 // начало XHTML-документа 83 out.println( "<?xml version = \"1.0\"?>" ); 84 85 out.printlnt "<!DOCTYPE html PUBLIC \"-//W3C//DTD " + 86 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 87 "/TR/xhtmll/DTD/xhtmll-strict.dtd\">" ) ,- 88 89 out.printlnt 90 "<html xmlne = V'http://www.w3.org/1999/xhtml\">" ); 91 92 // заголовок документа 93 out.printlnt "<head>" ); 94 out.printlnt "<title>Recommendations</title>" ); 95 out.printlnt "</head>" ); 96 97 // тело документа 98 out.printlnt "<body>" ); 99 100 // если имеются cookies, рекомендовать книги для изучения 101 if t cookies != null && cookies.length != 0 ) { 102 out.printlnt "<hl>Recommendations</hl>" ); 103 out.printlnt "<p>" ); 104 105 // получение имени каждого из cookies 106 for ( int i = 0; i < cookies.length; i++ ) 107 out.printlnt cookies[ i ] .getHante (> + 108 " How to Program. ISBN#: " + 109 cookies! i ].getValue() + "<br />" ); 110 111 out.printlnt "</p>" ); 112 }
80 Глава 2 113 else { // cookies отсутствуют 114 out.printIn( "<hl>No Recommendations</hl>" ); 115 out.println( "<p>You did not select a language.</p>" ); 116 } 117 118 out.println( "</body>" ); 119 120 // конец XHTML-документа 121 out.printlnt "</htral>" } ; 122 out.close(); // закрытие потока 123 } 124 } Рис. 2.20. Хранение данных пользователя на компьютере клиента с помощью cookies Сервлет CookieServlet (рис. 2.20) обрабатывает как запросы get, так и запросы post. Документ CookieSelectLangnage.html, представленный на рис. 2.21, содержит четыре переключателя (С, C++, Java и VB 6), а также кнопку Submit. Когда пользователь нажимает кнопку Submit, вызывается сервлет CookieServlet с использованием метода post. Сервлет добавляет cookie, содержащий выбранный язык, в заголовок ответа и отправляет клиенту ХНТМЪ-документ. Каждый раз, когда пользователь щелкает на кнопке Submit, cookie отправляется клиенту. В строке 11 определяется карта соответствий books как объект типа HashMap (пакет java.util), в котором мы храним пары ключ/значение. В качестве ключа используется название языка программирования, а в качестве значения — ISBN-код рекомендуемой книги. Метод init класса CookieServlet (строки 14-20) заполняет четыре пары ключ/значение названиями книг. Метод doPost (строки 24-69) вызывается в ответ на запрос post от XHTML-документа, представленного'на рис. 2.21. В строке 28 используется метод getParameter для получения выбранного пользователем языка language. В строка 29 из хэш books извлекается ISBN-код для выбранного языка. В строке 30 создается новый объект Cookie (пакет javax.servlet.http) с использованием значений language и isbn в качестве имени cookie и значения cookie, соответственно. Имя cookie идентифицирует cookie; значением cookie является информация, ассоциированная с cookie. Браузеры, поддерживающие cookies, должны иметь возможность хранить до 20 cookies на один Web-сайт и до 300 cookies на пользователя. Браузеры могут ограничивать размер cookie 4 Кб (4096 байт). Каждый cookie, хранящийся на машине клиента, включает в себя имя домена. Браузер посылает cookie только тому домену, имя которого сохранено в cookie. 1 <:?xml version = "1.0"?> 2 <!D0CTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<M-- Рис. 2.21. CookieSelectLanguage.html —> 6 7 <±Ltml xmlns = "http://www.w3.org/1999/xhtml"> 8 <head> 9 <title>Using Cookies</title> 10 </head> 11 12 <body> 13 <form action = "/advjhtpl/cookies" method = "post"> 14 15 <p>Select a programming language:</p>
Сервлеты 81 16 <р> П <input type = "radio" name = "language" 18 value = "C" />C <br /> 19 20 <input type = "radio" name = "language" 21 value = "C++" />C++ <br /> 22 23 <\— эта кнопка, выбранная по умолчанию —> 24 <input type = "radio" name = "language" 25 value = "Java" checked = "checked" />Java<t»r /> 26 27 <input type = "radio" name = "language" 28 value = "VB6" />VB 6 29 </p> 30 31 <pXinput type = "submit" value = "Submit" /x/p> 32 33 </form> 34 </body> 25 </tvtml> Рис. 2.21. Документ CookieSetectLanguage.html для выбора языка программирования и передачи данных сервлету CookieServlet {часть 1)
82 Глава 2 ■J using Cookies - *fctro*olt internet ^прилег ._, __._. , __ _. _ Л^ЕЩ ,i а&-а»*а аь?аь -^|^[ЙЬод:;^^^;Д)зд;^у)Ьф1^у|^|^ооИд5Ы^апди^.И(яа ^ Select a programming language: <~C (ГС++ ■*" Java <~VB6 рруг-'-'': 'Г^Р^Щи^мцр» ■+3 Welcome to Cookie - Mirtosnlt Interne* ЁкрЗогеГ .I-.JU^a.-.nm Ж--,*„■■■■„■■ Л\ _^ ■ J *■■ ..rb:tffi№. №-: j£j "1 http:/fl«*oaL8060/»(W*pl(™*j« Д'".^?" Welcome to Cockiest Y«u selected C++ Click: here to choose another language Cbck hen togetbookjrecommendarioiis И (ШЩьЩшёШщщш^ ~nm ■a ЦЩ$)|}Й httpi I flxdha*: S08Cfadyjh>P ЦсаоКн 5 Recommendatory Miotiwft: Internet Explorer &• ~ #™к)У^г .-Jsttp '•' Я*"* i**" 15'«$» Recommendations lava How to Program ISBN#: 0130125СГ75 C++ How to Program ISBN£ 0130895717 тш"> тш1*шт> a1 ..;'--.'**. Рис. 2.21. Документ CookieSelectLsnguage.html для выбора языка программирования и передачи данных сервлету CookieServlet (часть 2) Общая методическая рекомендация 2.7 Пользователи браузеров могут запрещать cookies, поэтому Web-приложе- ния> использующие cookies, могут работать неправильно для клиентов с запрещенными cookies.
Сервлеты 83 Общая методическая рекомендация 2.8 По умолчанию cookies существуют только в течение текущего сеанса просмотра (до мех пор, пока пользователь не закроет браузер). Чтобы cookies существовали и после завершения текущего сеанса, вызовите метод set Max Age класса Cookie для указания количество секунд до истечения срока хранения cookie. В строке 32 осуществляется добавление cookie в объект response с помощью метода addCookie интерфейса HttpServlet Response, Cookie посылаются клиенту как часть HTTP-заголовка. Информация заголовка всегда первой предоставляется клиенту, поэтому cookies должны быть добавлены в ответ (объект response) методом addCookie до того, как данные будут записаны в состав запроса. После добавления cookies сервлет отправляет клиенту XHTML-документ (см. вторую копию экрана на рис. 2.21). Типичная ошибка программирования 2.3 Запись данных в ответ клиенту до вызова метода addCookie с целью добавления cookies в ответ является логической ошибкой. Первыми делом следует добавить cookies в заголовок. XHTML-документ, посылаемый клиенту в ответ на запрос post, содержит гиперссылку, которая приводит к вызову метода doGet (строки 73—123). Метод прочитывает любые Cookies, которые были записаны на клиенте методом doPost. Для каждого записанного Cookie сервлет рекомендует книгу Deitel по данной теме. На Web-странице, создаваемой сервлетом, может быть отображено до четырех книг. В строке 77 cookie извлекается с клиента с помощью метода getCookies интерфейса HttpScrvletRequcst, который возвращает массив объектов Cookies. При выполнении для вызова сервлета операций get или post, cookies, ассоциированные с доменом этого сервера, автоматически отправляются сервлету. Если метод getCookie ке возвращает null (что имеет место, если cookies не были обнаружены), в строках 106-109 извлекается имя каждого Cookie с использованием метода getName класса Cookie, значение каждого Cookie извлекается с использованием метода getValue класса Cookie, и эти извлеченные данные записываются клиенту в виде строки, содержащей название рекомендуемой книги и ее ISBN-код. Общая методическая рекомендация 2.9 Обычно каждый класс сервлета обрабатывает только один тип запроса (например, get или post, но не оба). На рис. 2.21 представлен XHTML-документ, который пользователь загружает для выбора языка. Когда пользователь нажимает кнопку Submit, значение текущего выбранного переключателя посылается на сервер как часть запроса post сервлету CookieServlet, для которого в нашем примере мы используем ссылку cookies. Мы используем корень контекста advjhtpl для демонстрации работы сервлета, представленного на рис. 2.20. Поместите файл CookieSelectLanguage.html в каталог serviets, созданный ранее. Поместите файл CookieServiet.ciass в подкаталог classes каталога WEB_INF в корне контекста advjhtpl. Затем отредактируйте дескриптор развертывания web.xml в каталоге "\VEB_INF, чтобы включить в него информацию, указанную в таблице на рнс. 2.22. Перезапустите Tomcat и введите следующий URL в вашем браузере: http://localhost:8080/advjhtpl/servlets/ CooJcieSelectLanguage.html Выберите язык программирования и нажмите кнопку Submit на Web-странице, чтобы вызвать сервлет.
84 Глава 2 Имеются различные методы класса Cookie для работы с его членами. Некоторые из них представлены в таблице на рис. 2.23. [ Элемент дескриптора servlet элемент |servlet-name description servlet-class servlet-mapping: элемент servlet-name url-pattern Значение | Cookies | Using cookie to maintain state information j com.deitel .advjhtpl. servlets .CookieServlet || Cookies 1 /cookies | Рис. 2.22. Информация в дескриггторе развертывания, относящаяся к серелету CookieServlet Метод getConuaentO getOomain() getMaxAge() getName() getPath() getSourceO Описание Возвращает строку, описывающую назначение cookie (null, если не было установлено каких-либо комментариев с помощью метода setComment). Возвращает строку, содержащую имя домена cookie. Тем самым определяется, какие серверы могут получить cookie. По умолчанию cookie посылаются серверу, который отправил cookie клиенту. Возвращает целое число, представляющее максимальный возраст cookie в секундак. Возвращает строку, содержащую имя cookie, как оно бьло установлено конструктором. Возвращает строку, содержащую префикс URL для cookie. Cookie могут быть «нацелены» на определенные URL, которые вкпючают , каталоги Web-сервера. По умолчанию cookie возвращается сервисам, работающим в том ж каталоге, что и сервис, который отправил cookie, или в подкаталоге этого каталога. Возвращает булево значение, указывающее, допжен ли cookie передаваться с использованием протокола, обеспечивающего безопасность (true). getvaiue{) Возвращает строку, содержащую значение cookie, как оно было установлено методом set Value или конструктором. getVersion() Возвращает целое число, содержащее номер версии протокола cookies, используемого для создания cookie. Значение, равное 0 (по умолчанию), указывает на изначальный протокол cookies, определенный Netscape. Значение, равное 1, указывает на текущую версию, основанную на документе RFC 2109. setCoiranent( String ) Комментарии, описывающий назначение cookie, которое .предоставляется пользователю браузером. (Некоторые браузеры .дают возможг-iocib пользователю принимать каждый cookie i индивидуально.) setDomain ( string ) Определяет, какие серверы могут получать cookie. По умолчанию cookie отпоавляются серверу, который послап cookie клиенту. Домен задается в виде ".deitel.com", что указывает на , возможность получения этого cookie всеми серверами, имена |доменоз которые оканчивалиil» ив .deite3.com.
Сервлеты 85 Метод setMaxAge( int ) setPath( String ) setSecure( boolean ) setValue( String ) sefcVersion( int ) Описание Устанавливает максимальный возраст cookie в секундах. Устанавливает префикс «целевого» URL, указывающий каталоги на сервере, которые соответствуют сервисам, способным принимать этот cookie. Значение true указывает, что cookie должен посылаться только с использованием протокола, обеспечивающего безопасность. Задает значение cookie. Задает протокол cookies для этого cookie. Рис 2.23. Основные методы класса Cookie 2.7.2. Отслеживание сеанса с помощью интерфейса HttpSession Java предоставляет расширенную поддержку отслеживания сеансов с помощью интерфейса HttpSession API сервлетов. Чтобы продемонстрировать основные приемы отслеживания сеансов, мы модифицировали сервлет, представленный на рис. 2.20, чтобы использовать объекты HttpSession (рис. 2.24). Этот сервлет, опять таки, обслуживает и запросы get, и запросы post. Документ Session- SelectLanguage.html, представленный на рис. 2.25, содержит четыре переключателя (С, C++, Java и VB 6) и кнопку Submit, Когда пользователь нажимает кнопку Submit, вызывается сервлет SessionServlet с использованием метода post, Сервлет отвечает созданием объекта типа HttpSession для клиента (или использует существующий для этого клиента сеанс) и добавляет выбранный язык и ISBN- код для рекомендуемой книги в объект сеанса HttpSession. Затем сервлет отправляет клиенту XHTML-страницу. Каждый раз, когда пользователь щелкает на кнопке Submit, в объект HttpSession добавляется пара язык /ISBN-код. Общая методическая рекомендация 2.10 Сервлет не должен использовать переменные экземпляра для хранения информации о состоянии клиента, поскольку клиенты, осуществляющие доступ к этому сервлету параллельно, могут переписать совместно используемые переменные экземпляров, Сервлеты должны хранить информацию о состоянии клиента в объектах HttpSession. 1 // Рис. 2.24. SessionServlet.java 2 // Использование объекта HttpSession для хранения информации о состоянии клиента. 3 package com.deitel.advjhtpl.servlets; 4 5 import javax.servlet.*; 6 import javax.servlet.http.*; 7 import j ava.io,*; 8 import java.util.*; 9 10 public class SessionServlet extends KttpServlet { 11 private final Map books = new HaghKap(); 12 13 // инициализация таблицы books 14 public void init(} lb { 16 books.put( "C", "0130895725" );
86 Глава 2 17 books.put( "C++", "0130895717" ); 18 books.put( "Java", "0130125075" ); 19 books.putt "VB6", "0134569555" ); 20 } 21 22 // получение выбранного языка и создание объекта 23 // HttpSession, содержащего рекомендуемые клиенту книги 24 protected void doPost( HttpServletRequest request, 25 BttpServletResponse response ) 26 throws ServletException, IOException 27 { 28 String languege = request.getParameter( "language" }; 29 30 // Получение объекта сеанса session для пользователя. 31 // Создать сеанса (true), если он не существует. 32 HttpSession session = request.getSession< true ); 33 34 // добавление значения для выбора пользователя в объект session 35 session.setAttrxbute( language, books.get( language ) ); 36 37 response.setContentType( "text/html" ); 38 PrintWriter out = response.getWriter(); 39 40 // отправка XHTML-страницы клиенту 41 42 // начало XHTML-докукента 43 out.printlnf "<?xml version = \"1.0\"?>" ); 44 45 out.println( "<!DOCTYPE html РОБЫС \"-//W3C//DTD " + 46 "XHTML 1.0 St*ict//EN\" \"fcttp://www.w3.org" + 47 "/TR/xhtmll/DTD/xhtmll-strict.dtd\">" ); 46 49 out.printlnf 50 "<html xmlns = \"http://www.w3.org/1999/xhtml\,,>^, ); 51 52 // заголовок документа 53 out.printlnf "<head>" ); 54 out.println( "<title>Welcome to Sessions</title>" ); 55 out.println( "</head>" ); 56 57 // тело документа 58 out.printlnf "<body>" ) ,- 59 out.printlnf "<p>Welcome to Sessions! You selected " + 60 language + ",</p>" ); 61 62 // отображение информации о сеансе 63 out.println( "<p>Your unique session ID is: " + 64 session.getld() + "<br />" }; 65 66 out.println( 67 "This " + ( session.isNew() ? "is" : "is not" ) + 68 " a new session<br />" ); 69 70 out.printlnf "The session was created at: " + 71 new Date( session.getCreafcionTime() ) + "<br />" } ,■ 72 73 out.printlnf "You last accessed the session at: " +
74 new Date( session.getLastAccessedTime() ) + "<br />" ) 75 76 out.printing "The maximum inactive interval i.s: " + 77 session.getMaxInactivelnterval{) + " seconds</p>" ) ,- 78 79 out.println( u<pXa href = " + 80 "\"servlets/SessionSelectLanguage.html\">" + SI "Click here to choose another language</aX/p>" J ; 82 83 out.println( "<pXa href = \"sessions\">" + 84 "Click here to get book recommendations</aX/p>" ) ; 85 out.printlfi( "</body>" }; 86 87 // конец XHTML-документа 88 out.println( "</htrel>M ); 89 out.close(); // закрытие потока 90 1 91 92 // чтение атрибутов сеанса и создание XHTML-документа, 93 // содержащего рекомендуемые книги 94 protected void doGetf HttpServletRequest request, 95 HttpServletResponse response ) 96 throws ServletException, IOException 97 { 98 // Получение объекта сеанса session для пользователя. 99 // Не создавать сеанс (false), если он не существует. 100 HttpSession session = request.getSession( false ); 101 102 // получение имен значений для объекта session 103 Enumeration valueNames; 104 105 if ( session != null ) 106 valueNames = session.getAtfcributeNames(); 107 else 108 valueNames = null; 109 110 FrintWriter out = response.getWriter(); 111 response.setContentType( "text/html" ); 112 113 // начало XHTML-документа 114 out.println( "<?xml version = \"1.0\"?>" ); 115 116 out.println( "<!D0CTYPE html PUBLIC \"-//W3C//DTD " + 117 "XHTML 1.0 Strict//EN\" V'http://www.w3.org" + 118 "/TR/xhtmll/DTD/xhtmll-strict.dtd\">" ); 119 120 out.printlrt ( 121 "<html xralns = \"http://www.w3.org/1999/xhtml\M>" ); 122 123 // раздел заголовка документа 124 out.println( "<head>" ); 125 out.println( "<title>Recomraendatiotis</title>" ); 126 out.println( "</head>" ); 127 128 // раздел тела документа 129 out.println< "<body?" );
88 Глава 2 131 if { valueNames != null fiS 132 valueNames.hasMoreElementsO ) { 133 out.println( "<nl>Reeommendations</hl>" ); 134 out.println( "<p>" ); 135 136 String name, value; 137 13B // получение значения для каждого имени в списке valueNames 139 while { valueNames.hasMoreElements() ) { 140 паше = valueNames.nextElement().toStringO; 141 value = session.getAttribute( name ).toString{); 142 143 out.println( name + " How to Program. " + 144 "ISBN#: " + value + "<br />" ); 145 > 14 6 147 out.println( 4/p>" ) ; 148 } 149 else { 150 out.println( "<hl>No Reoommendations</hl>" ); 151 out.println( "<p>You did not select a language.</p>" ); 152 } 153 154 out.println( "</body>" ); 155 156 // конец XHTML-документа 157 out.println( "</html>" ); 158 out.close(); // закрытие потока 159 ) 160 > Рис. 2.24. Сохранение информации о состоянии с помощью объекта HttpSession Большая часть класса SessionServlet идентична классу CookieServIet (рис. 2.20), поэтому мы сосредоточим внимание только на его новых особенностях. Когда пользователь выбирает язык на странице SessionServletLanguage.html (рис. 2.25) и нажимает кнопку Submit, вызывается метод doPost (строки 24-90). В строке 28 извлекается выбранный пользователем язык language. Затем в строке 32 используется метод getSession интерфейса HttpServletRequest для получения объекта HttpSession для клиента. Если сервер уже имеет объект HttpSession для клиента из предыдущего запроса, метод getSession возвращает этот объект HttpSession. В противном случае параметр true, передаваемый методу getSession, указывает, что сервлет должен создать новый уникальный объект HttpSession для клиента, В случае укялания параметра false метод getSession возвращает null, если объект HttpScssion для клиента пока не существует. Применение параметра false может помочь определить, обращался ли уже клиент к Web-приложению. Подобно cookies, объект HttpSession может хранить пары имя/значение. Применительно к сеансу эти пары называются атрибутами и помещаются в объект HttpSession с помощью метода set Attribute. Б строке 35 используется метод setAttribute для помещения названия языка программирования и соответствующего -ISBN.кода рекомендуемой книги в объект HttpSession. Одним иа главных преимуществ применения объектов HttpSession по сравнению с cookies является то, что объекты HttpSession могут хранить в качестве значения атрибута любой объект (не только строки String). Это предоставляет программистам на Java гибкость при выборе типа информации о состоянии, которую они хотят сохранять для
Сервлеты 89 клиентов своих Web-приложений. Если атрибут с определенным именем уже существует при вызове метода set Attribute, объект, ассоциированный с этим атрибутом, замещается. Общая методическая рекомендация 2.11 Пары имя/значение, добавленные в объект HttpSession с помощью метода set Attribute, остаются доступными до тех пор, пока не закончится текущий сеанс просмотра клиента, или пока сеанс не будет явным образом завершен вызовом метода invalidate объекта HttpSession. Эти атрибуты также могут быть потеряны в случае перезапуска контейнера сервлетов. После добавления значений в объект HttpSession сервлет отправляет клиенту XHTML-доку мент (см. вторую копию экрана на рис. 2.25). В этом примере документ содержит различную информацию об объекте HttpSession для текущего клиента. В строке 64 используется метод getID объекта HttpSession для получения уникального идентификатора сеанса. В строке 67 определяется, существует ли уже сеанс, для чего вызывается метод isNew, который возвращает true или false. В строке 7 с помощью метода getCreatiouTime извлекается время создания сеанса. В строке 74 с помощью метода getLastAccessedTime извлекается время последнего обращения к сеансу. В строке 77 используется метод getMaxInactivelnterval для получения максимального значения времени, в течение которого объект HttpSession может оставаться неактивным, прежде чем он будет аннулирован контейнером сервлетов. XHTML-документ, посылаемый клиенту в ответ на запрос post, включает гиперссылку, которая приводит к вызову метода doGet (строки 94-159). Метод получает объект HttpSession для клиента с помощью метода getSession (строка 100). Мы не хотим выдавать калие-либо рекомендации, если клиент не имеет объекта HttpSession. В связи с этим при этом вызове метода getSession используется параметр false. Таким образом, метод getSession возвращает объект HttpSession только в том случае, если он уже существует для данного клиента. Если метод getSession не возвращает null, в строке 106 используется метод getAttributedames объекта HttpSession для извлечения списка (объект типа Enumeration) имен атрибутов (т.е. имен, используемых в качестве первого параметра метода setAttribute объекта HttpSession). Каждое имя передается в качестве параметра методу getAttribute (строка 141) для извлечения ISBN-кода книги из объекта HttpSession. Метод getAttribute принимает имя и возвращает ссылку типа Object на соответствующее значение. Далее, в ответ клиенту записывается строка, содержащая название и код ISBN рекомендуемой книги. На рис. 2.25 представлен XHTML-документ, который пользователь загружает для выбора языка программирования. Когда пользователь нажимает кнопку Submit, значение выбранного в данный момент переключателя посылается на сервер как часть запроса post сервлету SessionServlet, которому в нашем примере мы присвоили ссылку sessions. Мы используем корень контекста advjhtpl для демонстрации работы сервлета, представленного на рис. 2.24. Поместите файл Sessian.SelectLanguage.h.tml в каталог servlets, созданный ранее. Поместите файл SessionServlet.class в подкаталог classes каталога WEB-INF в корне контекста advjhtpl. Затем отредактируйте дескриптор развертывания web.xml в каталоге WEB-INF, чтобы включить в него информацию, указанную в таблице на рис. 2.26. Перезапустите Tomcat и введите следующий URL в вашем браузере: http://loealhost:6080/advjhtpl/servlets/ SessionSelectLanguage .tttml Выберите язык и нажмите кнопку Submit на Web-странице, чтобы вызвать сервлет.
90 Глава 2 1 <?зап1 version = "1.0"?> 2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Рис. 2.25. SessionSelectLanguage.html —> 6 7 <html xmlns = "http://www.w3.org/1999/xhtml"> 8 <head> 9 <title>Using Sessions</title> 10 </head> 11 12 <body> 13 <form action = "/advjhtpl/sessions" method = "post"> 14 15 <p>Select a programming language:</p> 16 <p> 17 <input type = "radio" name = "language" 18 value = "C" />C <br /> 19 20 <input type = "radio" name = "language" 21 value = "C+-1-" />C++ <br /> 22 23 <!-- атот переключатель выбирается по умолчания --> 24 <input type = "radio" name = "language" 25 value = "Java" checked = "checked" />Java<br /> 26 27 <input type = "radio" name = "language" 28 value = "VB6" />VB 6 2? </P> 30 31 <pXinput type = "submit" value = "Submit" /X/p> 32 33 </£orm> 34 </body> 35 </html> Рис. 2.25. Документ SessionSelectLangjage.html для выбора языка программирования и передачи данных сервлету SessionServlet (часть 1)
[fciiMiij.jimmj.iM.iHHUJwiwiJHw^^Mg-jt? | j*. eet'jJBw* FavbAJ»' то* Hit Г Badt. .--''jaMSfJ ■ ' 'Stop ЦвмК^у-йм» . Sw«h fWta* h|tp:/Jlin:atiKt:Sa80;*itvlhtolfiKsiaH ~ "дПР' Welcome to Sessions I You selected Java Ycur unique session ID is: tvjn6158hl Ths is a new sessioii The session was created at: Tue Feb 20 10:17:17 EST 2001 Ycu last accessed Ifae session at Tbe Feb 20 10-17:17 EST 2001 The maiamum inactive mtetval is. 1230 seconds Click here to chaose another language Cbck here to fiet book recommendations Ш!^^^.^^^,!^!"^^!,™. ^•; Sf-.V <э ■■■.ал Ф-Y <*■-■ &\ 5eenA FAVOritef ДМ|Йи [Й Wtp:JJloalhiist:aO30;advihlpl(Mr><ttiBei5m5^eftLangLH[>g.)itml «J- j>' VBaA" '. F$№.W v* Step Sttfresh'-Л'йв» Select a programming language: <"C Г C++ f* Java ЩаЙГ • ИИ.;; ■ ""^^ШШШШЩЬ^т*.-~!Щ 2И* ш:аа,уагшшж^;- ^М*1^*.ЦЁЗ hfc^VfjtPcj^eiBJD^^jl^pi/swaoro jjjjJlJjjife Welcome to Sessions! You selected Java. Your unique session ф is: tvjn6l53hl This is a ucw Ktsiflft Tte siission was created ar Tue Feb 20 10:17:17 EST 2001 You last accessed the session at Tue Feb 20 10:17 17 EST 2001 The maximum inactive interval is: 1300 seconds Click here ю choose another language Сйдк here to get bwk if Lonniendations 'ffl;gtWta«t^eWa*v»ifrl<»vWsfiri£^^ Рис. 2.25. Д<жуьлент SessionSelectLanguage.html для выбора языка пр и передачи данных сервлету SessionServlet (часть 2)
92 Глава 2 -4 thing SrMnons - Mrtrosoft Internet EKpkwer iVn.n.rrthJ^I.... .j,Jfcf,^ft--.WMl"rp«™*= Togli Яф ' * *!л?,,:. '■ - .1 V* ; |й11|^1аьир:^^Ь«^ЗП№/а^^|Ьф1^^1^^5Н1Х^ЫкйдЧ1»ае.ЬЫ^ ^><& ^ Stop, RqFrftrf) Hart» ' 3 Select a programming language. <~C Г-С++ f~ Java | Stibmrt iteip»»" "X^rr-jg](3S2tS^r jWcltome: to Sessions -MJciosoft internet Ев*йч*с1 HKatffc: '■ ■ ' forward. ■ -- 5top ; Jfe™b .Jg*..„ ' »■*-■-^wtitw i'.fW'.-E* -Viet Г*Ю*>» 10* Htlp. 12»Ы5И ;«d«B |© htto:ffloc*ostie08C/4aY»*P lessons 31-«*e "Welcome to Sessions! You selected VB6 Your unique session ID is: Evjn^lSShl This is not anew session The session was created at:TueEeb 20 "0 17:17 EST 2001 You last ас с e s sed the ses sion at: Tue Feb 20 10:17:17 EST 2001 The maximum inactive interval is: 1800 seconds Click here to choose another language Click here to eet boolLfecommendabon? fe hta/flKah^^e^J/sessb^',". T" I BJSriti****. ^i ВЛИЯНИЯМИ ■<:---tDtxl ■JMJMWItjfe] http:/Jlixjtiost:SiB0f3M)tpi;sesgmi jjj ^4». i №* ■ Bdt.' ¥e« FawiirtES Toot U .a Recommendations Java How to Program. ISBN#: 0130125075 VB6 How to Program. ISBNS: 0134565555 Рис. 2.25. Документ SessionSelectLanguage.html для выбора языка программирования и передачи данных сервлету SessionServlet (часть 3)
Сервлеты 93 Элемент дескриптор а servlet элемент servlet-name Description servlet-clas s servlet-mapping элемент servlet-name url-pattern Значение | Sessions Using sessions to maintain state information com.deitel.advjhtpl.servlets.SessionServlet Sessions /sessions Рис. 2.26. Информация в дескрипторе развертывания, относящаяся к сервлету Wei com s Se rvi et 2 2.8. Многоуровневые приложения: использование средств JDBC из сервлета Сервлеты могут взаимодействовать с базами данных через интерфейс JDBC (Java Database Connectivity). JPBC предоставляет для Java-программ единообразный способ соединения с различными базами данных вне зависимости от особенностей систем управления базами данных. Многие современные приложения представляют собой трехуровневые распределенные приложения, состоящие из интерфейса пользователя, бизнес-лотки и средств доступа к базам данных. Интерфейс пользователя в таком приложении часто создается с использованием HTML, XHTML (как в этой главе) или Dynamic HTML. В некоторых случаях на этом уровне также используются алдлеты Java. HTML и XHTML являются наиболее предпочтительными средствами для интерфейса пользователя в системах, где важное значение имеет переносимость. Поскольку HTML поддерживается всеми браузерами, разработка интерфейса пользователя, доступ к которому осуществляется через Web-браузер, гарантирует переносимость между всеми платформами, на которых имеются Web-браузеры. Используя сетевые возможности, предоставляемые браузером, интерфейс пользователя может взаимодействовать с бизнес-логикой среднего уровня. Средний уровень может затем осуществлять доступ к базе данных для манипулирования данными. Три уровня могут физически размещаться на отдельных компьютерах, которые связаны через сеть. В многоуровневой архитектуре средний уровень часто представлен Web-сервером. На них функционирует бизнес-логика, которая манипулирует данными, хранящимися в базах данных, и взаимодействует с клиентскими Web-браузерами. Сервлеты через JDBC могут взаимодействовать с системами управления базами данных. Разработчикам не нужно знать о специфике каждой из систем управления базами данных. Вместо этого разработчики используют SQL-запросы, а драйвер JDBC берет иа себя специфику взаимодействия с каждой из систем управления базами данных.. Сервлет SurveyServlet, представленный на рис. 2.27, и документ, представленный на рис. 2.28, демонстрируют трехуровневое распределенное приложение, которое отображает интерфейс пользователя в браузере с помощью XHTML. Средним уровнем является сервлет Java, который обрабатывает запросы браузера клиента и предоставляет доступ к третьему уровню — базе данных Cloudscape, доступной через JDBC. Сервлет в этом примере используется для проведения Web-опроса, который дает возможность пользователям проголосовать за свое любимое животное.
94 Глава 2 Принимая запрос post от документа Survey.himl, сервлет обновляет общее число голосов в базе данных, отданных за это животное, и возвращает клиенту динамически генерируемый XHTML-документ, содержащий результаты опроса. 1 // Рис. 2.27. SurveyServlet.Java 2 // Web-опрос, в котором средства JDBC используются из сервлета. 3 package com.deitel.advjhtpl.servlets■ 4 5 import java.io.*; 6 import j ava.text.*; 7 import j ava.sql.*; 8 impost javax.servlet.*; 9 import javax.servlet.http.*; 10 11 public class SurveyServlet extends HttpServlet { 12 private Connection connection; 13 private PreparedStatement updateVotes, totalVotes, results; 14 15 // настройка соединения с базой данных и подготовка операторов SQL 16 public void init( ServletConfig config ) 17 throws ServletException 18 { 19 // попытка соединения с базой данных и создания операторов PreparedStatement 20 try {' 21 Class.forName( "COM.cloudscape.core.RmiJdbcDriver" ); 22 connection = DriverManager.getConnection{ 23 "jdbc:rmi:jdbc:cloudscape:animalsurvey" ); 24 25 // оператор PreparedStatement для добавления 26 // голоса, поданного sa определенное животное 27 updateVotes = 2 8 connection.prepareStatement( 29 "UPDATE surveyresulta SET votes = votes + 1 " + 30 "WHERE id = ?" 31 ),- 32 33 // оператор PreparedStatement для суммирования голосов 34 totalVotes = 35 connection.prepareStatement( 36 "SELECT sum( votes ) FROM survfcyresults" 37 ); 3B 39 // оператор PreparedStatement для получения данных из таблицы параметров опроса 40 results = 41 connection.prepareStatement( 42 "SELECT surveyoption, votes, id " + 43 "FROM surveyresults ORDER BY id" 44 ) ; 45 ) 46 47 // для любого исключения возбуждать исключение CTnavailableException 48 // для указания, что сервлет s данный момент не доступен 4 9 catch ( Exception exception ) { 50 exception .printstaclcTrace () ;
Сервлеты 95 51 throw new UnavailableException( exception,gatMessage() ); 52 } 53 54 } // конец метода init 55 56 // обработка ответа 57 protected void doPost{ HttpServletRequest request, 53 HttpServletResponse response ) 59 throws ServletException, IOSxception 60 { 61 // настройка ответа., отправляемого клиенту 62 response.setContentType( "text/html" ); 63 PrintWriter out = response.getWriter()• 64 DecimalFormat twoDigita = new DecimalFormat( "0.00" ); 65 66 // начало XHTML-документа 67 out.println( "<?xml version = 4"1,0\"?>" ); 68 69 out.println( "<!DOCTYPE html PUBIIC \"-//W3C//DTD " + 70 "XHTML 1.0 Strict//EK\" V'http://www.w3.org" + 71 "/TR/xhtaill/DTD/xlitmll-strict.dtdV'>" ); 72 73 out.println( 74 "<html xmlns = V'http://www.w3.org/1999/xhtml\">" ); 75 76 // заголовок документа 77 out.printlnt "<head>" ) ,- 7B 79 // чтение текущего ответа на опрос SO int value = 81 Integer.parselnt( request.getfarameter( "animal" ) ); 82 83 // попытка обработать голос и выдать текущий результат опроса 84 try { 85 86 // обновление суммы голосов для текущего ответа на опрос 87 updateVotes.setInt{ 1, value ); 88 updateVotes.executeUpdate(); 89 90 // получение сумны всех ответов на опрос 91 ResultSet totalRS = totalVotes.executeQuery(); 92 totalRS.nextO : 93 int total = totalRS.getlnt( 1 ); 94 95 // получение результатов 96 ResultSet jresultsRS = results.executeQuery(); 97 out.println( "<title>Thank you»</title>" ); 98 out.println( "</nead>" ); 99 100 out.println( "<body>" ); 101 out.printlnt "<p>Thank you for participating." );■ 102 out.printlnt "<br />Results:</pXpre>" ); 103 104 // обработка результатов 105 iiit votes; 106
96 Глава 2 107 while ( resultsRS.next() ) { 108 out.print( resultsRS.. getString( 1 ) ); 109 out.print( ": " ); 120 votes = resultsRS.getint( 2 ) ; 111 out.print( twoDigits.format( 112 ( double ) votes / total * 100 ) ); 113 out.print( "% responses: " ); 114 out.println( votes ); 115 } 116 117 resultsRS.close {) ," 118 119 out.print( "Total responses: " ); 120 out.print( total ); 121 122 // конец XHTML-документа 123 Out.println( "</preX/body></html>" ); 124 out.close(); 125 ) 126 127 // если возникает исключение при обращении к баэе данных. Возвратить страницу, указывающую на ошибку 128 catch { SQLException sqlException ) { 129 sqlException.printStackTracef); 130 Out.println( "<title>Error</title>" ); 131 Out.println( "</head>" ); 132 out.println{ "<bodyXp>Database error occurred, " ); 133 out.println! "Try again later. </px/bodyX/html>" ); 134 out.close() ; 135 ] 136 137 } // конец метода doPost 138 139 // закрытие операторов SQL и базы данных по завершении работы серилета 140 public void destroy() 141 { 142 // попытка закрыть операторы и соединение с базой данных 143 try { 144 updateVotes.close(); 145 totalVotes.close{); 146 results.close(); 147 connection.close(}; 148 } 149 150 // обработка исключения при работе с базой данных, выдача сообщения об ошибке клиенту 151 catch( SQLException sqlException ) { 152 sqlException.printStackTrace',) ; 153 } 154 } // конец метода destroy 155 } Рис. 2.27. Многоуровневое Web-приложение, использующее XHTML, сервлеты и JDBC
Сервлеты 97 В этом примере используется программный продукт Cloudscape. Полную информацию по системе управления базами данных Cloudscape можно найти на сайте www.cloudscape.com. Следуйте предоставленным инструкциям, чтобы установить сервер Cloudscape. Чтобы запустить сервер Cloudscape, откройте командное окно. Перейдите к каталогу, в котором установлен Cloudscape (Cloudscape_3.6 по умолчанию). В этом каталоге имеется подкаталог frameworks. В Cloudscape имеется два режима выполнения: embedded и RmiJdbc. Режим embedded дает возможность выполнять Cloudscape как часть приложения Java. Режим RmiJdbc дает возможность выполнять Cloudscape как автономный сервер баз данных. Именно этот режим будет использоваться в данном случае, В каталоге каждого режима выполнения имеется подкаталог bin, содержащий командные файлы (Windows) и командные сценарии (Linux/Unix) для установки переменных окружения и выполнения Cloudscape. Перейдите в подкаталог bin в режиме RmiJdbc. Выполните командный файл или сценарий, начинающийся с setServerCloudscapeCP, чтобы установить переменные окружения, необходимые серверу. Затем выполните командный файл или сценарий, начинающийся с startCS, чтобы запустить сервер баз данных Cloudscape. Сервер можно остановить, выполнив сценарий stopCS из другого командного окна. В каталоге примеров для этой главы содержится SQL-сценарий animalsur- vey.sql, который вы можете использовать для создания базы данных animal- survey, применяемой в этом примере. Чтобы создать базу данных animalsurvey, сначала убедитесь, что сервер Cloudscape запущен. Откройте новое окно командной строки, а затем перейдите к каталогу frameworks/RmiJdbc/Ьш Cloudscape. В этом каталоге выполните командный файл или сценарий, начинающийся с setClientCloudscapeCP, для установки переменных окружения, необходимых сценарию создания базы данных createDatabase. Далее, перейдите к каталогу на вашем компьютере, в который содержатся файлы примеров для этой главы, и введите createDatabase animalsurvey.sql чтобы выполнить SQL-сценарий. База данных будет создана. [Замечание. Мы написали этот сценарий, чтобы вы могли в любое время снова его выполнить и восстановить исходное содержимое базы данных.] В строках 12 и 13 объявляется ссылка Connection для управления соединением с базой данных и три ссылки PreparedStatement для обновления числа голосов, поданных за животное, суммирования всех голосов и получения окончательных результатов опроса. Сервлеты инициализируются замещением метода init (строки 16-54). Метод init вызывается только один раз в течение срока жизни сервлета перед принятием каких-либо клиентских запросов. Метод init принимает параметр servletConfig и возбуждает исключение ServletException. Параметр предоставляет сервлету информацию о параметрах инициализации (т.е. параметрах, не ассоциированных с запросом, но передаваемых сервлету для инициализации переменных сервлета). Эти параметры задаются в дескрипторе развертывания web.xml как часть элемента servlet. Каждый из параметров присутствует в элементе init-param, имеющем следующий вид: <init-param> <param-name> здесь указывается имя параметра </param-name> <param-value> здесь указывается значение параметра </param-valu6> </ini t-param>
98 Глава 2 Сервлеты могут получать значения параметров инициализации, вызывая метод getlnitParameter класса ServletConfig, который принимает строку, представляющую собой имя параметра. В этом примере метод init сервлета (строки 16-54) осуществляет соединение с базой данных Cloudscape. В строке 21 загружается драйвер (COM.cloudscape.co- re.RmiJdbcDriver). В строках 22-23 осуществляется попытка открыть соединение с базой данных animalsuxvey. База данных содержит одну таблицу (surveyresults), которая состоит из трех полей: уникального целого числа с именем id, идентифицирующего каждую запись; строки с именем surveyoption, представляющей собой выбранный вариант ответа; и целого числа с именем votes, представляющего собой количество голосов, отданных за данный вариант ответа. В строках 27-44 создаются подготовленные операторы (объекты типа Prepared- Statements) с именами updateVotcs, total Votes и results. Оператор updateVotes добавляет единицу к значению votes записи базы данных с заданным идентификатором. Оператор totalVotes использует встроенную функцию sum SQL для суммирования всех голосов votes в таблице surveyresults. Оператор results возвращает все данные из таблицы surveyresnlts. Когда пользователь отправляет ответ на опрос, метод doPost (строки 57-137) обрабатывает запрос. В сроках 80-81 осуществляется получение ответа на опрос, после чего блок try (строки 84-125) пытается обработать ответ. В строках 87-88 ответ на опрос задается в качестве первого параметра подготовленного оператора (объект типа PreparedStatement) updateVotes, а затем выполняется обновление базы данных. В строках 91-93 выполняется подготовленный оператор totalVotes для извлечения общего количества полученных голосов. Далее, в строках 96-123 выполняется подготовленный оператор results и обрабатывается результирующее множество Resultset для создания страницы с итогами опроса, отправляемой клиенту. Когда контейнер сервлетов завершает выполнение сервлета, метод destroy (строки 140—154) закрывает каждый из подготовленных операторов PreparedStatement, а затем закрывает соединение с базой данных. На рис. 2.28 представлен документ survey.html, который вызывает сервлет SurveyServlet через псевдоним animalSurvey, когда пользователь отправляет заполненную им форму опроса. 1 <?xml version = "1.0"?> 2<!D0CTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtnai/DTD/xhtmll-strict.(itd"> 4 5<!-- Рис. 2.28. Survey.html --> 6 1 <html xmlns = "http://www.w3.org/1999/xhtrol"> 8 <head> 9 <title>Survey</title> 10 </head> 11 12 <body> 13 <form method = "post" action = "/advjhtpl/aninialsurvey"> 14 15 <p>What is your favorite pet?</p> 16 17 <p> IB <input type = "radio" name = "animal" 19 value = "1" />Dog<br /> 20 <input type = "radio" name = "animal" 21 value = "2" />cat<br /> 22 <inp\it type = "radio" name = "animal"
Сервлеты 99 23 24 25 26 27 28 29 30 31 32 </fonn> 33 </body> 34 </html> value = "3" />Bird<br /> <input type = "radio" name = "animal" value = "4" />Snake<br /> <input type = "radio" name = "animal" value = "5" checked = "checked" />None </p> <pXinput type = "submit" value = "Submit" /X/p> шжеевзя . r^fajffijftj «Э, imp.- .'St: .t3> i $ НИЩА kWJVX<lfrtt5t:B08W»№pWi«<^sfojy<;y.btri 3.^^ ■\Ubat is your favorite pet' ffDog I" Cat <"Bjrd С Snake <* None g^-'lf»'. wis si SF**1 Г5Щ5Г „|ИИ|Я|,.,,Я, jtddim |f j -ittp:>ftoca*№t:aoaojjdvjnpijarirrMfejfffy ^ШШ 'ttiank you for participating Results: Dog: 57,394 responses: 22 Слс: 23-Ё84 responses: 9 Bxcd: Ш.53% responses: 4 SnaKe: £.2 5* responsesi 2 None: 2.63% responses: 1 Total responses: 3S ©Dor?7 "Г^Г"Ш.Км( «ten*" 11 Рис. 2.28. Документ Survey.html, который дает возможность пользователям отправлять ответы на Web-опрос сервлету SurveyServlet Мы используем наш корень контекста advjhtpl для демонстрации сервлета, представленного на рис, 2,27. Поместите файл Survey.html в каталог servlets, созданный ранее. Поместите файл SurveyServlet.class в подкаталог classes каталога WEB-INF в корне контекста advjhtpl. Затем отредактируйте дескриптор развертывания web.xml в каталоге WEB-INF, чтобы включить в него информацию, указанную в таблице ка рис. 2.29. Кроме того, эта программа не сможет быть выпол-
100 Глава 2 нена в Tomcat, если Web-приложение не будет осведомлено о местоположении JAR-файлов cloudscape.jar и RmiJdbc.jar, которые содержат драйвер базы данных Cloudscape и его файлы поддержки. Файл cloudscape.jar расположен в каталоге lib Cloudscape. Файл RmiJdbc.jar расположен в каталоге frameworks\RmiJdbc\clas- ses Cloudscape. Поместите копии этих файлов в подкаталог lib каталога WEB-INF, чтобы сделать их доступными для Web-приложения. Элемент дескриптора ssrvlet элемент servlet-name description servlet-class servlet-mapping элемент secvlet-name url-pattern Значение Animalsurvey Connecting to a database from a servlet com.deitel.advjhtpl.servlets.SurveyServlet animalsurvey /animalsurvey Рис. 2.29. Информация в дескрипторе развертывания, относящаяся к сервлету SurveyServlet Копии этих файлов должна быть помещена в подкаталог lib каталога WEB-INF корня контекста advjhtpl. Скопировав эти файлы, перезапустите Tomcat и введите следующий URL в Web-браузере: http://localhost:8080/advjhtpl/servlets/Survey.html Выберите вариант ответа и нажмите кнопку Submit на Web-странице, чтобы вызвать сервлет. 2.9. Класс HttpUtils Класс HttpUtils предоставляет три статических метода для упрощения программирования сервлетов. Эти методы описаны в таблице на рис. 2.30. Метод Ge tReque s tURL ParsePostData ParseQueryString Описание Этот метод принимает в качестве параметра объект HttpServletRequest и возвращает буфер StringBuffer, содержащий URL ресурса, инициировавшего запрос. Этот метод принимает в качестве параметра целое число и объект ServletlnputStream. Целое число представляет количество байт в потоке ввода ServletlnputStream. Объект Servlet In put Stream содержит пары ключ/значение, передаваемые сервлету (с помощью метода post) из формы (элемент form). Метод возвращает хэш Hashtable. содержащий пары ключ/значение. Этот метод принимает в качестве параметра строку (String), содержащую строку запроса в запросе get, и возвращает хэш Hashtable, содержащий пары ключ/значение в строке ззпроса. Значением каждого ключа является строковый массив. Сiрока запроса может быть получена с помощью метода getQueryString интерфейса HttpServletRequest Рис. 2.30. Методы класса HttpUtils
Сервлеты 101 2.10. Ресурсы в Internet и во Всемирной паутине В этом разделе приведено множество ресурсов в Internet, имеющих отношение к сервлетам, и дано краткое описание каждого из них. Java.sun,com/products/servlet/index.html Страница сервлетов Web-сайта Java корпорации Sun Microsystems, Inc. предоставляет последнюю информацию, относящуюся к сервлетам, и список ресурсов по сервлетам. Jakarta.apache.org Это осноапая страница проекта Apache Project, посвященная Jakarta Project Tomcat — эталонной реализации сервлетов и JavaServer Pages — одному из многих подчиненных проектов проекта Jakarta Project, j akarta.apache.org/tomcat/index.html Основная страница эталонной реализации сервлетов Tomcat и серверных страниц JavaServer Pages, j 3v3.apache.org Это основная страница проекта Apache Project для всех технологий, связанных с Java. Этот сайт предоставляет доступ ко многим пакетам Java, полезным для разработчиков сервлетов и JSP. www.servlets.com Это Web-сайт книги Java Servlet Programming (Программирование сервлетов Java) издательства Q'Reilly. Книга предоставляет множество разнообразных ресурсов. Она является прекрасным источником информации для программистов, изучающих технологию сервлетов. theserverside.com Сайт TheServerSide.com содержит информацию и ресурсы по Enterprise Java. www-servletSource.com Сайт ServletSource.com — ресурс по сервлетам, содержащий примеры кода, советы, учебный материал и ссылки на многие другие Web-сайты с информацией по сервлетам. www■cookiecentral.com Хороший всеобъемлющий ресурс по cookies. developer.netscape.com/docs/manuals/communicator/jsguide4/cookies.htm Описание технологии cookies, предоставленное Netscape. www.j avacorporate.com Основная страница для инфраструктуры с открытым кодом ExpressoFramework, которая содержит библиотеку расширяемых компонентов сервлетов, способствующих ускорению разработки Web-приложений, www.servlet.com/srvdev.jhtml Форум Servlet Developers Forum предоставляет ресурсы разработчикам серверных приложений на Java и информацию о Web-серверах, поддерживающих технологии сервлетов. www.servletforura.com Сайт ServletForum.com — это группа новостей, в которой можно задавать вопросы и получать ответы на них от других участников группы. www.coolservlets.com Этот сайт бесплатно предоставляет сервлеты Java с открытым исходным кодом. www.cetus-links.org/oo_java_servleta.html Предоставляет список ссылок на ресурсы по сервлетам и другим технологиям. www.javaskyline.com Java Skyline — это сетевой журнал для разработчиков сервлетов.
102 Глава 2 www.гfc-editor.org Сайт RFC Editor предоставляет поисковый сервер для нахождения документов RFC). Многие из RFC предоставляют подробное описание технологий Web, Интерес для разработчиков сервлетов могут представлять следующие документы RFC: URIs (RFC 1630), URLs (RFC 1738), Relative URLs (RFC 1808), HTTP/1.0 (RFC 1945), MIME (RFC 2045-2049), HTTP State Management Mechanism (RFC 2109), Use and Interpretation of HTTP Version Numbers (RFC 2145), Hypertext Coffee Pot Control Protocol (RFC 2324), HTTP/1.1 (RFC 2615) и HTTP Authentication: Basic and Digest Authentication (RFC 2617). www.irvine.com/-mime Сайт часто задаваемых вопросов (FAQ) no Multipurpose Internet Mail Extensions предоставляет информацию о стандарте MIME, список зарегистрированных MIME-типов, а также ссылки на другие ресурсы по МШЕ. Резюме • Классы и интерфейсы, используемые в серзлетах, содержатся в пакетах javax.servlet и javax.servlet.bttp. • В Internet испольяуетг.я множество протоколов. Протокол HTTP (Hypertext Transfer Protocol), который составляет основу Всемирной паутины, использует унифицированные идентификаторы ресурсов URI (Uniform Resource Identifiers) для определения местоположения ресурсов в Internet. • URL как правило указывает на файлы или каталоги, но могут являться указателями на приложения, решающие сложные задачи, например, поиск в базах данных и в Internet. • Технология JavaServer Pages является расширением технологии сервлетов. • Сервлеты обычно выполняются под управлением Web-сервера (также называемого контейнером сервлетов). • Сервлеты и серверные страницы JavaServer Pages стали настолько популярными, что в настоящее время поддерживаются большинством Web-серверов и серверов приложений. ■ Все сервлеты должны реалиэовывать интерфейс Servlet. Методы интерфейса Servlet автоматически вызываются контейнером сервлетов. • Жизненный цикл сервлета начинается, когда контейнер сервлетов загружает сервлет в память — обычно в ответ на первый запрос к этому сервлету. Перед тем, как сервлет сможет обработать первый запрос, контейнер сервлетов вызывает метод init сервлета. По завершении выполнения метода init сервлет может ответить на первый поступивший запрос. Все запросы обрабатываются методом service сервлета, который может многократно вызываться в течение жизненного цикла сервлета. Когда контейнер сервлета завершает выполнение сервлета, вызывается метод destroy для освобождения ресурсов, используемых сервлетом. • Пакеты сервлетов определяют два абстрактных класса, которые реализуют интерфейс Servlet: класс GenericServlet и класс HttpServlet. Большинство сервлетов расширяют один их этих классов и замещают некоторые, или все их методы, чтобы получить соответствующее поведение. • Ключевым методом каждого сервлета является метод service, который принимает объект Servlet Request и объект ServletResponse. Эти объекты предоставляют доступ к потокам ввода и вывода, которые дают возможность сервлету прочитывать данные с клиента и отправлять данные клиенту. • Сервлеты для использования в Web обычно расширяют класс HttpServlet. Класс HttpServlet замещает метод service, чтобы иметь возможность различать типы запросов, получаемых от клиентского Web-браузера. К двум наиболее типичным HTTP-запросам (их также называют методами запроса) относятся get и post. • Класс HttpServlet определяет методы doGet и doPost для реакции, соответственно, на запросы get и post, поступающие от клиента. Вызов этих методов осуществляется методом service класса HttpServlet, который, в свою очередь, вызываются при поступлении запроса на сервер. • Методы doGet и doPost принимают в качестве параметров объект ilttpServletRequest и объект HttpServletResponse, которые позволяют осуществлять взаимодействие между клиентом и сервером.
Сервлеты 103 • Ответ посылается клиенту с помощью объекта PrintWriter, возвращаемого методом getWriter объекта HttpServletResponse. • Метод setContentType объекта HttpServletResponse задает MIME-тип ответа, посылаемого клиенту. Это дает возможность браузеру пользователя распознавать и должным образом обрабатывать содержимое. • Имя localhost сервера (IP-адрес 127.0.0.1) представляет собой общеизвестное имя локального компьютера. Это имя может быть использовано для тестирования прилоя{ешш TCP/IP на локальном компьютере. • Сервер Tomcat ожидает запросы от клиентов на порту 8080. Этот номер порта должен быть задан как часть URL при запросе сервлета, выполняющегося на Tomcat. • Клиент может осуществлять доступ к сервлету только в том случае, если сервлет установлен на сервере, который реагирует на запросы, обращенные к сервлетам. Для Web-серверов, поддерживающих сервлеты, обычно предусматривается процедура установки сералетоа. • Tomcat представляет собой полнофункциональиую реализацию стандартов JSP и сервле- тов. В состав Tomcat входит Web-сервер, поэтому его можно использовать как автономный контейнер для тестирования JSP-страниц и сервлетов. • Tomcat может быть принят в качестве обработчика запросов на JSP-страницы и еералеты, принимаемых популярными Web-серверами, такими как Apache и IIS. Tomcat также интегрирован в эталонную реализацию Java 2 Enterprise Edition от Sun Microsystems. • JSP-страницы, сервлеты и их файлы поддержки развертываются как часть Web-приложений. В Tomcat Web-приложения развертываются в подкаталоги webapps Tomcat. • Web-приложение представляет собой структуру каталогов, в которой размещаются все файлы, относящиеся к приложению. Эта структура каталогов может быть задана администратором сервера Tomcat в каталоге webapps, или же вся структура каталогов может быть заархивирована в файл архива Web-приложения. Такой архив известен как WAR- файл и имеет расширение .war. • Если WAR-файл помещен в каталог vfebapps, сервер Tomcat при запуске извлекает содержимое WAR-файла в соответствующую структуру подкаталогов webapps. • Структура каталогов Web-приложения состоит из корня контекста — каталога верхнего уровня для всего Web-приложения — и нескольких подкаталогов. Корень контекста является корневым каталогом для Web-приложения. Все JSP-страницы, HTML-документы, сервлеты и вспомогательные файлы, такие как изображения и файлы классов, размещаются а этом каталоге или в его подкаталогах. Каталог WEB-INF содержит дескриптор развертывания Web-приложения (web.xml). Каталог WЕВ-INF/classes содержит файлы классов сервлета и другие файлы классов поддержки, используемые а Web-приложении. Каталог WEB-INF/ИЬ содержит файлы архивов Java (JAR), которые могут включать файлы классов сервлета и другие файлы классов поддержки, используемые в Web-приложении. • Прежде, чем осуществить развертывание Web-приложения, контейнер сервлетов должен определить корень контекста Web-приложения. В Tomcat для этого достаточно просто поместить каталог в подкаталог webapps. Tomcat использует имя каталога в качестве имени контекста. • Развертывание Web-приложения требует создания дескриптора развертывания (web.xml). • HTTP-запросы get могут непосредственно вводиться в поле Address или Location браузера. • Параметры передаются в запросе get в виде пар имя/значение. Знак ? отделяет URL от данных, передаваемых как часть запроса get. В передаваемых парах имя/значение имя отделено от значения символом =. Если имеется несколько пар имя/значение, каждая пара отделяется от другой символом &. • Метод getParameter интерфейса HttpServletRequest принимает в качестве параметра имя параметра и возвращает соответствующее строковое значение или null, если параметр не является частью запроса. • НТТР-эатгрос post часто используется для передачи данных из формы на Web-странице серверному обработчику формы, который обрабатывает данные. • Браузеры часто кэшируют (сохраняют на диске) Web-страницы, чтобы иметь возможность быстро перезагружать страницы. Браузеры не кэшируют ответ сервера на запрос post. • Метод doPost принимает те же даа параметра, что и метод doGet, — объект, который реализует интерфейс HttpServletRequest для представления клиентского запроса и объект, который реализует интерфейс HttpServletResponse для представления ответа сервера. • Метод setRedirect интерфейса HttpServletResponse переадресовывает запрос ресурсу с указанным URL.
104 Глава 2 • Если сервлет использует относительный путь для ссылки на другой статический или динамический ресурс, сервлет предполагает использование этого же корневого каталога контекста, если только для ресурса не задан абсолютный URL. • После выполнения метода sendRedirect обработка запроса сервлетом, который вызвал метод sendRedirect, завершается. • При переадресации запросов параметры из первоначального запроса передаются в качестве параметров новому запросу. Могут также передаваться дополнительные параметры запроса, • Новые параметры добавляются к имеющимся параметрам запроса. Если новый параметр имеет то же имя, что и существующий параметр, значение нового параметра имеет приоритет над первоначальным значением. Тем не менее, передаются все значения. • Полный набор значений для параметра запроса с заданным именем может быть получен путем вызова метода getParameterValnes интерфейса HttpServletRequest, который принимает а качестве аргумента имя параметра и возвращает массив строк, содержащий значения параметра в порядке, начиная от последнего добавленного значения для этого параметра к первому добавленному значению. • Многие современные Web-сайты предоставляют настраиваемые Web-страницы и/или различные функциональные возможности для различных клиентов. • HTTP является протоколом без сохранения состонния — он не поддерживает сохранение информации, которая может помочь Web-серверу определить, какой запрос поступил от какого клиента. • Cookies могут хранить информацию на компьютере пользователя для последующего ее извлечения в этом или в будущих сеансах просмотра. • Cookies представляют собой текстовые дачные, посылаемые еервлетами (или другими серверными технологиями) как часть ответов клиентам. • Каждое HTTP-взаимодействие между клиентом и сервером содержит заголозок, включающий информацию о запросе (при направлении взаимодействия от клиента к серверу) или информацию об ответе (при направлении взаимодействия от сервера к клиенту). • Когда сервер получает запрос, заголовок содержит информацию, такую, как метод запроса (например, get или post) или cookies, сохраненную на клиентском компьютере сервером. • Когда сервер формирует свой ответ, заголовок содержит любые cookies, которые сервер хотел бы сохранить на компьютере клиента, а также другую информацию, например, МШЕ-тип ответа. • В занисимости от максимального возраста cookie, Web-браузер либо хранит cookie в течение сеанса просмотра, либо сохраняет cookie на компьютере клиента для последующего использования. Когда браузер запрашивает ресурс с сервера, cookies, ранее отправленные клиенту этим сервером, возвращаются серверу как часть запроса, сформированного браузером. Cookies автоматически удаляются по истечении их срока хранения. • По умолчанию cookies существуют только в течение текущего сеанса просмотра (до тех пор, пока пользователь не закроет браузер). Чтобы cookie сохранялся и после текущего сеанса, следует вызвать метод setMaxAge класса Cookie и указать количество секунд до завершения срока существования cookie. • Метод getCookies интерфейса HttpServletRequest возвращает массив объектов Cookie. Метод getCookies возвращает nnll, если cookie в запросе отсутствует, • Метод getName класса Cookie изалекает имя cookie. Метод getValne класса Cookie извлекает значение cookie. • Java предоставляет альтернативу применению cookie для отслеживания информации о сеансе. Эта возможность реализуется с помощью интерфейса HttpSession и позволяет избежать проблем, связанных с запрещением cookies пользователями, делая механизм отслеживания сеанса прозрачным для программиста. • Метод getSession интерфейса HttpServletRequest получает объект HttpSession для клиента. • Подобно cookies, объект HttpSession может хранить пары имя/значение. Применительно к сеансам они называются атрибутами, сохраняются методом setAttribnte и извлекаются методом getAttribute. • Пары имя/значение, добавленные в объект HttpSession с помощью метода setAttribute, остаются доступными до завершения текущего сеанса просмотра или до тех пор, пока сеанс не будет явным образом уничтожен путем: вызова метода invalidate объекта HttpSession. • Метод getID класса HttpSession получает уникальный идентификатор сеанса.
Сервлеты 105 • Метод isNew класса HttpSession определяет, является ли сеанс новым, или же он уже существует. Метод getCreationTime получает время создания сеанса. • Метод getLastAccessedTime получает время последнего обращения к сеансу. • Метод getMaxInactivelnterval класса HttpSession получает максимальную длительность неактивности объекта HttpSession до того, как контейнер сервлета его уничтожит. • Многие современные приложения являются трехуровневыми и состоят из интерфейса пользователя, бизнес-логики и средств доступа к базам данных. • В многоуровневых архитектурах средний уровень часто функционирует на Web-серверах. Они обеспечивают бизнес-логик у, которая манипулирует данными, хранящимися в базах данных, и взаимодействует с клиентскими Web-браузерами. • Метод init интерфейса Servlet принимает яргумент ServletConfig и возбуждает исключение ServletException. Аргумент предоставляет сервлету информацию о параметрах инициализации, которые задаются н элементе servlet дескриптора развертывания. Каждый параметр инициализации присутствует в элементе init-param с дочерними элементами рагаш-name и рагаш-value. Терминология addCookie, метод интерфейса HttpServletResponse Apache Tomcat, сервер cache a Web-page — кэширование Web-страницы commit a response — завершение ответа context root — корневой каталог контекста Cookie, класс delete, метод запроса deploy a Web application — развертывание Web-приложения deployment descriptor — дескриптор развертывания destroy, метод интерфейса Servlet doGet, метод интерфейса HttpServlet doPost, метод интерфейса HttpServlet GenericServIet, класс из пакета javax.servlet get," метод запроса getAttribute, метод интерфейса HttpSession getAttribnteNames, метод интерфейса HttpSession getCookies, метод интерфейса HttpServletRequest getCreationTime, метод интерфейса HttpSession getlD, метод интерфейса HttpSession getLastAccessedTime, метод интерфейса HttpSession getMaxInactivelnterval, метод интерфейса HttpSession getName, метод класса Cookie getOutputStream, метод интерфейса HttpServletKesponse getParameter, метод интерфейса HttpServletReqnest getParameterNames, метод интерфейса HttpServletReqnest getParameterValues, метод интерфейса HttpServletRequest getServletConfig, метод интерфейса Servlet getServIetlnfo, метод интерфейса Servlet get Session, метод интерфейса HttpServletRequest get Value, метод класса Gookie get Writer, метод интерфейса HttpServletResponse host name — имя хоста HTTP (Hypertext Transfer Protocol), протокол HTTP header — заголовок HTTP HTTP request — НТТР-запрос HttpServlet, интерфейс HttpServletRequest, интерфейс HttpServletResponse, интерфейс HttpSession, интерфейс init, метод интерфейса Servlet initialization parameter — параметр инициализации invalidate, метод интерфейса HttpSession isNew, метод интерфейса HttpSession Jakarta, проект JAVA_HQME, аеременкая окружения javax.servlet, пакет javax.servlet.http, пакет Jigsaw, Web-сервер localhost (127.0.0.1) maximum age of cookie — максимальный возраст cookie MIME type — MIME-тип options, тип запроса, path, атрибут port — порт post, запроса put, метод запроса redirect a request — переадресация запроса request method — метод запроса request parameter — параметр запроса sendRedireet, метод интерфейса Ht t p S ervletRespo use service, метод интерфейса Servlet servlet — сервлет servlet container — контейнер сервлетов
106 Глава 2 Servlet, интерфейс thin client — тонкий клиент servlet life cycle — жизневвый цикл сервлета ТОМСАТ_НОМЕ, геремеиная окружения servlet mapping —карта соответствий для trace, запрос сервлета URL pattern — шаблон URL ServletConfig, интерфейс WAR (Web application archive) file — файл ServletContext, интерфейс архива Web-приложения ServletException, класс Web application — Web-приложение ServletOutpntStream, класс Web application deployment descriptor ServletRequest, интерфейс (web.xml) — дескриптор развертывания ServletResponse, интерфейс Web-приложения session tracking — отслеживание сеанса webapps, каталог setAttribate, метод интерфейса HttpSession W~EB_INF, каталог setContentType, метод интерфейса WEB_INF/classes, каталог HttpServletResponse WEB_INF/lib, каталог shopping cart — магазинная тележка well-known port number — общеизвестный text/html, MIME-тип номер порта Упражнения для самоконтроля 2.1. Заполните пропуски в следующих высказываниях: а) Классы HttpServlet и GenericServlet реализуют интерфейс ■ b) Класс HttpServlet определяет методы и для реакции на запросы get и post, поступающие от клиента. c) Метод интерфейса HttpServletResponse получает символьный поток вывода, который дает возможность отправлять текстовые данные клиенту. d) Атрибут __ элемента form определяет серверный обработчик формы, т.е. программу, которая обрабатывает запрос. e) представляет собой имя, которое относится к локальному компьютеру. f) Метод класса Cookie возвращает строку, содержащую имя cookie, как оно было установлено конструктором . g) Метод getSession интерфейса HttpServletRequest возвращает клиенту объект 2.2. Ответьте, является ли каждое из следующих высказываний истиняым или ложным. Если высказывание ложно, объясните, почему. a) Сервлеты обычно используются в сетевом приложении на стороне клиента. b) Методы сервлета выполняются автоматически. c) Двумя основными HTTP-запросами являются запросы get и put. <i) Общеизвестным номером порта для Web-запросов является 55. e) Cookies имеют неограниченное время действия. f) Срок действия объектов HttpSession закапчивается только при завершении сеанса просмотра или при вызове метода invalidate. g) Метод getAttribute интерфейса HttpSession возвращает объект, ассоциированный с определенным именем. Ответь/ на упражнения для самоконтроля 2.1. a) Servlet. b) doGet, doPost. с) getWriter. d) action, e) localhost f) getName, setName. g) HttpSession. 2.2. а) Ложно. Сервлеты обычно используются на стороне сервера. b) Истинно. c) Ложно. Двумя основными типами HTTP-запросов являются запросы get и post. d) Ложно. Общеизвестным номером порта для Web-запросов является 80. e) Ложно- Срок действия Cookies истекает только по достижении ими максимального возраста. f) Истинно. g) Истинно.
Сервлеты 107 Упражнения 2.3. Модифицируйте пример Cookie, представленный на рис. 2.20, чтобы для каждой рекомендованной книги указывалась ее цена. Помимо этого, дайте пользователю возможность выбирать некоторые или все рекомендованные книги и заказывать их. Разверните ваше Web-приложение на сервере Tomcat. 2.4. Модифицируйте нример HttpSessktn, представленный на рис, 2.24, чтобы для каждой рекомендованной книги указывалась ее цена. Помимо этого, дайте пользователю возможность выбирать некоторые или все рекомендованные книги и заказывать их. Разверните ваше Web-приложен не на сервере Tomcat. 2.5. Создайте Web-приложение для реализации динамических FAQ (часто задаваемых вопросов). Приложение должно получать информацию для создания динамических Web-стравиц FAQ из базы данных, которая состоит из таблиц Topics и FAQ. Таблица Topics должна иметь два поля: уникальный целочисленный идентификатор для каждой темы (topicED) и название темы (topicName). Таблица FAQ должна иметь три поля: topi с ID (внешний ключ), строку, представляющую вопрос (question), и Ответ на вопрос (answer). При вызове сервлета он должен прочитать данные из базы данных и возвращать динамически создаваемую Web-страницу, содержащую каждый вопрос и ответ, отсортированные по темам. 2.6. Модифицируйте Web-приложение из Упражнения 2.5, чтобы начальный запрос к серв- лету возвращал Web-страницу с темами FAQ из базы данных. После этого пользователь сможет переходить по гиперссылке к другому сервлегу, который возвращает только часто задаваемые вопросы по определенной теме. 2.7. Модифицируйте Web-приложение, представленное на рис. 2.27, чтобы дать возможность пользователю видеть результаты опроса, не отвечал ыа вопрос. 2.8. Web-приложение, представленное на рис. 2.27, дает возможность пользователям голосовать любое количество раз путем многократного возврата к Web-страниде опроса и последующего голосования. Модифицируйте решение Упражнения 2.7, использовав cookie со сроком хранения один день, чтобы не позволять пользователю голосовать более одного раза в день. Когда пользователь возвращается на Web-сайт, cookie, ранее сохраненный в его системе, отправляется на сервер. Сервлет должен проверять наличие cookie и, если он существует, указывать клиенту, что оп уже участвовал в голосовании. Сервлет также должен возвращать текущие результаты опроса. 2.9. Модифицируйте Web-приложение, представленное на рис. 2,27, чтобы обобщить его для любого опроса. Используйте параметры сервлета (см, раздел 2.8), чтобы задавать опции опроса. Когда пользователь запрашивает участие в опросе, динамически генерируется форма, содержащая опции опроса. Осуществите развертывание этого Web-приложения с использованием различных корней контекста. Замечание. Возможно, вам придется модифицировать базу данных, используемую в этом примере, чтобы иметь возможность хранить в ней данные сразу для нескольких опросов, 2.10. Напишите Web-приложение, которое состоит из сервлета (DirectoryServlet) и нескольких Web-документов. Первым документом, который увидит пользователь, должен быть документ index.html. В этом документе должен содержаться ряд гиперссылок на другие Web-страницы вашего сайта. При щелчке мышью на гиперссылке должен вызываться серзлет с помощью запроса get, который содержит параметр page. Сервлет должен получать параметр page и переадресовывать запрос к соответствующему документу. 2.11. Модифицируйте Web-приложение, представленное на рис. 2.10, чтобы первый документ, который пользователь видит в браузере, динамически генерировался сервлетом HomePageServlet на основе параметров иницийлизяпии (см. раздел 2.8). Для каждой страницы Web-сайта должны быть предусмотрены свои параметры инициализации. Сервлет HomePageServlet прочитывает имя и значение каждого параметра и создает хэш Hash Map пар имя/значение параметров. Эта информация должна использоваться для динамического создания начальной страницы. Хэш (объект Hash Map) также должен быть помещен в контейнер ServletContext с помощью метода set At tribute, чтобы ее мог использовать сервлет DirectoryServlet для определения, куда направлять каждый из запросов. Динамическая основная страница должна содержать гиперссылки на каждый документ Web-сайта. Как и в Упражкепии 2.10, когда пользователь щелкает на ссылке, должен вызываться сервлет DirectoryServlet с передачей ему параметра страницы. Затем сервлет DirectoryServlet должен получить хэш HashMap из контейнера ServletContext, найти соответствующий документ и переадресовать пользователя к этому документу.
8 JavaServer Pages (JSP) Цели • Научиться создавать и развертывать JSP-страницы. • Научиться применять неявные объекты JSP и Java для создания динамических Web-страниц. • Научиться задавать глобальную для JSP-страницы информацию с помощью директив. • Научиться использовать действия для манипулирования компонентами JavaBeans в JSP-странице, чтобы осуществлять динамическое включение ресурсов и переадресацию запросов к другим JSP-страницам. • Научиться создавать собственные библиотеки нестандартных тегов, инкапсулирующие в новые теги сложные функциональные возможности, которые могут быть многократно использованы программистами JSP-страниц и дизайнерами Web-страниц. Мы думаем, что помидор кс способен общаться с другим помидором. Возможно, мы ошибаемся. Густав Экштейн Осел — это лошадь в переводе на голландский. Георг Кристоф Лихтенберг Вопрос таланта — это вопрос количества. Талант состоит не в том, чтобы написать одну страницу, а в том, чтобы написать их триста. Жюль Ренар
110 Глава 3 3.1. Введение Наше обсуждение сетевого взаимодействия между клиентом и сервером в этой главе мы продолжим рассмотрением серверных страниц Java (JavaServer Pages — JSP). JavaServer Pages представляет собой расширение технологии сервле- тов для упрощения доставки динамического Web-содержимого. Страницы Java- Server Pages дают возможность программистам Web-приложения создавать динамическое содержимое за счет многократного использования ранее определенных компонентов и за счет взаимодействия с компонентами путем написания сценариев, выполняющихся на стороне сервера. Программисты JavaServer Pages могут многократно использовать компоненты JavaBeans и создавать собственные библиотеки нестандартных тегов, которые инкапсулируют сложные динамические функциональные средства. Библиотеки нестандартных тегов также дают возможность дизайнерам Web-страниц, не знакомым с языком Java, усовершенствовать Web-страницы, добавляя средства динамического отображения содержимое и новые возможности по обработке. В дополнение к классам и интерфейсам для программирования сервлетов (из пакетов javax.servlet и javax.servlet.http), в пакетах javax.servlet.jsp и javax.ser- vlet.jsp.target содержатся классы и интерфейсы, относящиеся к программированию JavaServer Pages. В ходе главы мы обсудим многие из этих классов и интер-
JavaServer Pages (JSP) 111 фейсов по мере знакомства с основами технологии JavaServer Pages. Полное описание технологии JavaServer Pages можно найти в спецификации JavaServer Pages 1.1 по адресу java.sun.com/products/jsp/download.html1. В разделе 3.9 приводятся и другие ресурсы, имеющие отношение к технологии JSP. [Замечание. Исходный код и изображения для всех рассматриваемых в этой главе примеров можно найти па Web-сайте компании Deitel www.deitel.com, а также на Web-сайте издательства «Бином» www.biiiom-prcss.ru/books/adv_java2.litiii.] 3.2. Обзор технологии JavaServer Pages В JSP имеются четыре ключевых компонента: директивы, действия, скриптле- ты и библиотеки тегов. Директивы представляют собой сообщения для контейнера JSP, которые дают возможность программисту задавать параметры страницы, включать содержимое из других ресурсов и задавать собственные библиотеки нестандартных тегов для использования их в JSP-странице. Действия инкапсулируют функциональные возможности в предопределенных тегах, которые программисты могут встраивать в JSP-страницу. Действия часто выполняются на основе информации, посылаемой на сервер в составе запроса от определенного клиента. Действия также могут создавать объекты Java для использования их в скриптлетах JSP. Скриптлеты (scriptlets), или элементы сценария, дают возможность программистам вставлять код Java, который взаимодействует с компонентами JSP-страницы (и, возможно, с другими компонентами Web приложения) для обработки запроса. Биб л истеки тегов являются составной частью механизма расширения тегов, который дает возможность программистам создавать собственные теги. Такие теги позволяют программистам манипулировать содержимым JSP. Эти типы компонентов JSP подробно рассматриваются в последующих разделах. Во многом страницы JavaServer Pages выглядят как стандартные XHTML- или XML-документы. В действительности JSP-страницы обычно содержат разметку XHTML или XML. Такая разметка носит название данных с неизменной структурой (fixed template data) или текста с неизменной структурой. Наличие данных с неизменной структурой часто помогают программисту принять решение, какую технологию следует использовать: сервлеты или JSP. Программисты предпочитают использовать JSP, если большая часть посылаемого клиенту содержимого представляет собой данные с неизменной структурой, и лишь небольшая часть содержимого генерируется динамически с помощью кода Java. Программисты используют сервлеты, если только небольшая часть содержимого, посылаемого клиенту, представляет собой данные с неизменной структурой. На самом деле некоторые сервлеты не генерируют какого-либо содержимого. Вместо этого они выполняют определенную задачу в интересах клиента, а затем вызывают другие сервлеты или JSP-страницы, чтобы выдать ответ. Заметим, что в большинстве случаев сервлеты и JSP являются взаимозаменяемыми. Подобно сервлетам, JSP-страницы обычно выполняются на Web-сервере. Сервер при этом часто называют контейнером JSP. S Общая методическая рекомендация 3.1 Символьный текст, содержащийся в JSP-странице. в сервлете превращается в строковые литералы, которые представляют преобразованную J SP-страницу. Когда сервер, способный поддерживать технологию JSP, принимает первый запрос на JSP-страницу, контейнер JSP транслирует эту JSP-страницу в сервлет Java, который обслуживает текущий запрос и все последующие запросы к этой 1 На момент подготовки к изданию перевода книги актуальной была версия 1.2. спецификации JavaServer Pages. — Прим. ред.
112 Глава 3 JSP-странице. Если при компиляции нового сервлета возникают ошибки, эти ошибки приводят к ошибкам на этапе трансляции. Контейнер JSP на этапе трансляции помещает операторы Java, которые реализуют ответ JSP-страницы, в метод _jspSeroice. Если сервлет компилируется без ошибок, контейнер JSP вызывает метод _jspService для обработки запроса. JSP-страница может обработать запрос непосредственно или же вызвать другие компоненты Web-приложения, чтобы содействовать обработке запроса. Любые ошибки, которые имеют место в процессе обработки запроса, называются ошибки на этапе запроса. —з Совет по повышению эффективности 3.1 "^^ Некоторые контейнеры JSP транслируют JSP-страницы в серелеты на этапе установки. Это позволяет избежать потерь в производительности для первого клиента, запрашивающего JSP-страницу. В целом механизм запрос/ответ и жизненный цикл для JSP-страниц и для серв- летов одинаков. JSP-страницы могут определять методы jspln.it и jspDestroy (схожие с методами init и destroy для сервлетов), которые вызываются, когда JSP- страница, соответственно, инициализируется и завершает свое действие. Программисты JSP-страниц могут определять эти методы с помощью объявлений JSP — составной части механизма создания сценариев JSP. 3.3. Первый пример JSP-страницы Мы начнем наше знакомство с технологией JavaServer Pages с простого примера (рис. 3.1), в котором в Web-страницу с помощью выражения JSP вставляются текущие дата и время. Как можно видеть, страница clock.jsp содержит XHTML-разметку. В случаях, подобных этому, проще реализовать JSP-страницу, чем сервлет. В сервлете, который выполняет ту же задачу, что и данная JSP-страница, каждая строка XHTML- разметки обычно представляет собой отдельный оператор Java, который помещает строку, содержащую разметку, в ответ клиенту. Написание кода для вывода разметки часто сопровонедается ошибками. Большинство средств редактирования JSP-страниц предоставляют возможности выделения цветом различных элементов синтаксиса, что способствует соблюдению программистами надлежащего синтаксиса разметки. 1 <?xml version = "1.0"?> 2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtjnll/DTD/xhtmll-sti:ict.dtd"> 4 5<!— Рис. З.1. clock.jsp —> 6 7<html xmlna = "http://www.w3.org/1999/xlitiia"> 8 9 <head> 10 <mefca http-equiv = "refresh" content = "60" /> 11 12 <title>A Simple JSP Example</title> 13 14 <style type = "text/css"> 15 .big { font-family: helvetica, arial, sans-serif; 16 font-weight: bold; 17 font-size: 2em,- } 18 </style>
JavaServer Pages (JSP) 113 19 20 21 22 23 2d 25 26 27 28 29 30 31 32 33 34 35 36 37 38 </head> <body> <p class = "big">Simple JSP Example</p> <table style = "border: 6px outset;"> <tr> <td style = "background-color: black;"> <p class = "big" style = "color: cyan;"> <!— выражение JSP для вставки даты/времени <%= new java.util.Date() %> </p> </td> </tr> </table> </body> </html> Simple JSP Example Tue Sep 19 14:07:32 EDT 2000 В5Г "! Пйй«^" feW [И l«»:/)bt«I«ist<llC0№iJBg4 J4 Simple JSP Example Tue Sep 19 14:07:37 2000 Ms ШШ. 1ШПШШШ&+ ~шш№* Л Рис. 3.1. Использование выражения JSP для вставки даты и времени на Web-страницу Общая методическая рекомендация 3.2 Страницы JavaServer Pages проще реализовывлть, чем сервлеты. если ответ на клиентский запрос состоит главным образом из разметки, которая в промежутке между различными запросами остается неизменной.
114 Глава 3 JSP-етраница, представленная на рис. 3.1, генерирует XHTML-докумеыт, который отображает текущие дату и время. Основной строкой в этой JSP-странице (строка 30) является выражение <%= new Java.util.Date( ) %> Выражения JSP заключаются в ограничители <%= и %>. В данном случае выражение создает новый экземпляр класса Date из пакета java.util. Когда клиент запрашивает эту JSP- страницу, данное выражение вставляет а ответ клиенту строку, представляющую текущие дату и время. [Замечание. Чтобы обеспечить надлежащую локализацию, эта JSP-страница должна возвращать клиенту дату в региональном (принятом в данной местности) формате. В этом примере представление даты Date определяется регионом местонахождения сервера. JSP-страница clock2.jsp, представленная на рис. 3.9, демонстрирует, как определить регион местонахождения (местность) клиента. Страница использует объект DateFormat (пакет Java.text) для форматирования даты в соответсгвии с правилами, сложившимися для данной местности.] Ш Общая методическая рекомендация 3.3 Контейнер JSP преобразовывает результат выполнения каждого выражения JSP в строку, которая выводится как часть ответа клиенту. Обратите внимание на использование элемента XHTML meta в строке 10 для установки интервала обновления документа, равного 60 секундам. Это застав ляет браузер запрашивать страницу clock.jsp каждые 60 секунд. При каждом запросе страницы clock.jsp контейнер JSP заново вычисляет выражение в строке 30, создавая новый объект Data с новыми текущими значениями даты и времени сервера. Как и в главе 2, для тестирования наших JSP-страниц в ранее созданном Web-приложении advjhtpl мы используем сервер Apache Tomcat. Подробности, связанные с созданием и настройкой Web-приложения, вы можете найти в разделах 2.3.1 и 2.3.2. Чтобы протестировать страницу clock.jsp, создайте новый каталог с именем jsp в подкаталоге advjhtpl каталога web&pps Tomcat. Далее, скопируйте файл clock.jsp в каталог jsp. Откройте ваш Web-браузер и введите в строке адреса следующий URL для тестирования страницы clock.jsp: http://localhost:8080/advjJitpl/jsp/cloek.jap При первом вызове JSP-страницы обратите внимание на задержку, вызванную тем, что Tomcat транслирует JSP-страницу в сервлет и вызывает сервлет в ответ на ваш запрос. [Замечание. Каталог с именем jsp в Web-приложении создавать не обязательно. В данном случае этот каталог используется для того, чтобы отделить примеры этой главы от примеров сервлетов в главе 2.] 3.4. Неявные объекты Неявные объекты предоставляют программистам доступ ко многим функциональным возможностям сервлетов в контексте JavaServer Page. Неявные объекты имеют четыре области видимости: приложения, страницы, запроса и сеанса. Приложение контейнера JSP и сервлетов владеет объектами с областью видимости в пределах приложения. Такими объектами может манипулировать любой сервлет или JSP-страница. Объекты с областью видимости в пределах страницы существуют только на той странице, на которой они определены. Каждая страница имеет свои собственные экземпляры неявных объектов со страничной областью видимости. Объекты с областью видимости в пределах запроса существуют в течение запроса. Например, JSP-страница кожет частично обработать страницу, а затем переслать запрос другому сервлету или JSP-странице для дальнейшей обработки.
JavaServer Pages (JSP) 115 Объекты с областью видимости в пределах запроса теряют свое действие по завершении обработки запроса выдачей ответа клиенту. Объекты с областью видимости в пределах сеанса существуют в течение всего клиентского сеанса просмотра, В таблице на рис. 3.2 описаны неявные объекты JSP и их области видимости. В этой главе будет продемонстрировано применение некоторых из этих объектов. Неявный объект Описание Область видимости в пределах приложения application Этот объект javax.servlet.ServletContext представляет собой контейнер, в котором исполняется JSP-страница, Область видимости в пределах страницы config exception out page pageContext response Этот объект javax.servlet.ServletConfig содержит параметры конфигурации. Как и для сералет™, параметры конфигурации могут быть заданы в дескригторе Web-приложения. Этот объект java.lang.Throwable представляет исключение, которое передается странице сообщения об ошибках JSP. Объект доступен только на странице сообщения об ошибках JSP. Этот объект javax.servlet.jsp.JspWriter записывает текст как часть ответа на запрос. Этот объект неявно используется выражениями JSP и действиями, которые вставляют строку содержимого е ответ, Этот объект java.lang.Object представляет ссылку this для текущего экземпляра JSP-страницы. Этот объект javax.servlet.jsp.PageContext скрывает детали реализации базового контейнера JSP и сервлетов и предоставляет программистам JSP-страниц доступ к неявным объектам, описанным в этой таблице. Этот объект представляет ответ клиенту Объект обычно представляет I собой экземпляр класса, который реализует интерфейс HttpServletResponse (пакет javax.servlet.http). Если используется протокол, отличный от HTTP, этот объект является экземпляром класса, который реализует интерфейс javax.servlet.5ervletResponse. Область видимости в пределах запроса request Этот объект представляет клиентский запрос. Объект обычно является экземпляром класса, который реализует интерфейс HttpServletRequest (пакет javax.servlet.http). Если используется протокол, отличный от HTTP, этот объект является экземпляром подкласса javax.servlet.ServletRcquest. \ Область видимости в пределах сеанса | session Этот объек-javax.servlet.http.Http5ession представляет информацию о клиентском сеансе, если такой сеанс был создан. Этот объект доступен только на страницах, которые участвуют е сеансе, Рис. 3.2. Неявные объекты J5P Обратите внимание, что многие из неявных объектов расширяют классы или реализуют интерфейсы, которые обсуждались в главе 2. Таким образом, JSP-страницы могут использовать для взаимодействия с такими объектами те же методы, которые используют сервлеты (см. главу 2). В большинстве примеров в этой главе используется один или несколько из представленных в таблице на рис. 3.2 неявных объектов.
116 Глава 3 3.5. Сценарии Страницы JavaServer Pages часто формируют динамически генерируемое содержимое как часть XHTML-доку мента, отправляемого клиенту в ответ на запрос. В некоторых случаях содержимое является статическим, но выводится только при соблюдении определенных условий, указанных в запросе (например, при предоставлении значений в форме, которая осуществляет отправку запроса). Программисты JSP-страниц могут вставлять код Java и логику управления в JSP-страницу путем написания соответствующего сценария. Общая методическая рекомендация 3.4 В настоящее время страницы JavaServer Pages поддерживают сценарии, написанные только на Java. Последующие версии JSP, возможно, будут поддерживать и другие языки сценариев. 3.5.1. Компоненты сценария К компонентам сценария JSP относятся скриптлеты, комментарии, выражения, объявления и управляющие последовательности (escape-последовательности). В этом разделе описывается каждый из этих компонентов сценария. Многие из них демонстрируются в примере на рис. 3.4 в конце раздела 3.5.2. Скриптлеты представляют собой фрагменты кода, ограниченные символами <% и %>. Они содержат операторы Java, которые контейнер помещает в метод _jspServlet на этапе трансляции. JSP-страницы поддерживают три типа комментариев: комментарии JSP, комментарии XHTML и комментарии языка сценариев. Комментарии JSP заключаются в ограничители <%— и —%>. Такие комментарии могут быть размещены в любом месте JSP-страницы, но не внутри скриптлетов. Комментарии XHTML заключаются в ограничители <!— и —>. Эти комментарии могут быть размещены в любом месте JSP-страницы, но не внутри скриптлетов. Комментарии языка сценариев в настоящий момент представляют собой комментарии языка Java, поскольку Java сейчас является единственным возможным языком написания сценариев JSP. В скриптлетах могут использоваться как однострочные комментарии Java (ограничиваемые символами / и /), так и многострочные комментарии (ограничиваемые символами /* и */). Типичная ошибка программирования 3.1 Помещение комментария JSP или комментария XHTML внутрь скрипт- лета является синтаксической ошибкой, выявляемой на этапе трансляции, которая препятствует корректной трансляции JSP-страницы. Комментарии JSP и комментарии языка сценариев игнорируются и не присутствуют в ответе клиенту. Когда клиент просматривает исходный код ответа, отправленного JSP-страницей, он видит только комментарии XHTML. Иметь различные типы комментариев полезно, чтобы отделять комментарии, которые являются видимыми для пользователя, от комментариев, которые обрабатываются на сервере. Выражение JSP, заключенное в ограничители <%= и %>, содержит выражение Java, которое вычисляется, когда клиент запрашивает JSP-страницу, содержащую это выражение. Контейнер преобразовывает результат JSP-выражения в строковый объект (String), а затем помещает эту строку в ответ клиенту. Объ явления (заключенные в ограничители <%/ и %>) дают возможность программисту JSP определять переменные и методы. Переменные становятся пере-
Java Serve r Pages (JSP) 117 менными экземпляра класса сервлета, который представляет транслированную JSP-страницу. Аналогично, методы становятся членами класса, который представляет транслированную JSP-страницу. В объявлениях переменных и методов на JSP-странице используется синтаксис Java. Таким образом, объявление переменной должно заканчиваться точкой с запятой, например <%! int counter =0; %> Типичная ошибка программирования 3.2 Объявление переменной без использования завершающего символа точки с запятой является синтаксической ошибкой. Общая методическая рекомендация 3.5 Переменные и методы, декларированные в объявлениях JSP, инициализируются при инициализации JSP-страницы и доступны, для использования во всех скриптлетах и выражениях на этой JSP-странице. Переменные, объявленные подобным образом, становятся переменными экземпляра класса сервлета, который представляет транслированную JSP-страницу. Общая методическая рекомендация 3.6 Как и сервлеты, JSP-страницы не должны сохранять информацию о состоянии клиента в переменных экземпляров. Для этого JSP-страницы должны использовать неявный объект JSP session. Специальные символы или последовательности символов, которые контейнер JSP обычно использует в качестве ограничителей JSP-кода, могут включаться на JSP-страницу как буквенные (литеральные) символы в элементах сценария, в данных с неизменной структурой и в значениях атрибутов с помощью управляющих escape-последовательностей. В таблице на рис. 3.3 представлены буквенные символы и соответствующие escape-последовательности, а также поясняется, где следует применять escape-последовательности. 3.5.2. Пример сценария JSP-страница, представленная на рис. 3.4, демонстрирует основные возможности сценария при обработке запросов get. JSP-страница позволяет пользователю ввести имя, а затем возвращает это имя в ответе. Используя скриптлег, JSP-страница определяет, был ли параметр first Name передан JSP-странице в составе запроса; если нет, JSP-страница возвращает XHTML-доку мент, содержащий форму, в которой пользователь может ввести имя. В ином случае JSP-страница получает значение firstName и использует его как часть XHTML-документа, который содержит приветственное сообщение, выдаваемое пользователю. Литерал <* %> Escape-последовательность <\* %\> Описание Символьная последовательность <% обычно указывает на начало скриптлета, Escape-последовательность <\% помещает буквенные символы <% в ответ клиенту. Символьная последовэтегьность %> обычно указывает на конец скриптлета. Escape-последовательность %\> помещает буквенные символы %> в ответ клиенту,
118 Глава 3 Литерал ■ ■т \ Escape-после- довател ьн ость V V Описание Как и строковые литералы а программе Java, escape-последовательности для символов ', " и \ дают возможность использовать эти символы в значениях атрибутов. Напомним, что символьный текст на JSP-странице в серелете превращается в строковые литералы, которые представляют транслированную JSP-страницу, | Рис. 3.3. Escape-последовательности JSP 1 2 3 4 5 б 1 В 9 10 11 12 13 14 15 16 17 18 19 2D 21 22 23 24 25 26 27 28 2» 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version = "1.0"?> -ODOCTYPE html POBLIC "-//W3C//DTD XHTML 1.0 Strict//EH" "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> <!— Рис. 3.4. welcome.jsp —> <\— JSP-страница, обрабатывающая запрос get, содержащий данные. —> <html xmlns = "http://www.w3.ocg/1999/xhtml"> <!-' раздел заголовка документа —> <head> <title>Processing "get" requests with data</title> </head> <!-- раздел тела документа —> <body> <% // начало скриявдета String паше = request.getParameter( "firstHame" ); if ( name != null ) { %> <%— закрытие скришлета для вставки данных с неизменной структурой --%> <Ы> Hello <%= name %>, <br /> Welcome to JavaServeг Pages! </hl> <% // продолжение скриптлета } // конец блока if else { %> <%-- закрмтие скриптлета для вставки данных с неизменной структурой --% > <£orm action = "welcome.jap" method = "gef"> <p> Type your first name and press Submit</p> <pXinput type = "text" name ~ "firgtSame" /> <input type = "submit" value = "Submit" />
JavaServer Pages (JSP) «9 42 </p> 43 </form> 44 45 <% // продолжение скриптлета 46 47 ) // конец блока else 48 49 *> <i— конец слгриптлета —i> 50 </body> 51 52 </html> <!-- конец XHTML-документа —> ■ A****- ЬРЧЕ Type your (Paul «ЙЩ^ Wew Favatei bob rtdjF ,.jfMvil g-; -Sup *:',jK IT N(p:fl1«al»5t.saaa/advjhtpl/M>>relcome.|!p first name and press Submit r"s"bnaft.A •«lar -, -r^ ■■ ш в& Home ;. 1 ДИ№ .1 ■ *a 'Ш ~ 1 '^i* '51 "ik**H * j £1 t'*K^«!«!*' ■'.:£$ http:Jteat.Ht;B0S0)advihl:pl);alf».ak:are,l5p;Sratome=Paul -SJJjfe0 Hello Paul, Welcome to JaVa Server Pages! I & .. zi worn: :Ш»:Г~Щ;;, Ч. ччр*: -;1Р%ГШШ^5ЩГ~5 Рис. 3.4. JSP-стрзнииз wekome.jsp, содержащая сценарий Обратите внимание, что большая часть кода, представленного в листинге на рис. 3.4, является XHTML-разметкой (т.е. данными с неизменной структурой). В составе элемента body имеется несколько екрилтлетов (строки 17-23, 30-35 и 45-49) а также выражение JSP (строка 26). Как можно видеть, в этой JSP-стра- нице используются три типа комментариев. Скриптлеты содержат структуру if/else, которая определяет, получила ли JSP-страпица в составе запроса значение для имени. В строке 19 используется метод getParameter неявного объекта JSP request (объект класса HttpServIetRequest) для получения значения параметра firstName, лоиле чего результат присваивается переменной name. В строке 21 проверяется, что значение паше не есть null (т.е. что имя было передано JSP-странице в составе запроса). Если условие истинно (true), скриптлет временно закрывается для вывода данных с неизменной структурой в строках 25-28. Выражение JSP в строке 26 осуществляет вывод значения переменной name (т.е. имени, переданного JSP-странице как параметр запроса). Скриптлет возобновляется в строках 30-35 с закрывающей фи-
120 Глава 3 гурной скобки структуры if, после которой начиняется блок else структуры if/else. Если условие в строке 21 ложно (имеет значение false), вывод в строках 25-28 не производится. Вместо этого в строках 37-43 осуществляется вывод формы. Пользователь может ввести имя в форме и нажать кнопку Submit для повторного запроса JSP-страницы и выполнения тела структуры if (строки 25-28). Общая методическая рекомендация 3.7 Скриптлеты, выражения и данные с неизменной структурой совместно используются на JSP-странице, чтобы формировать различные варианты ответа в зависимости от информации* содержащейся в запросе к JSP-странице. Совет по тестированию и отладке 3.1 Иногда, при отладке JSP-страницы бывает довольно сложно обнаружить ошибки, поскольку номера строк, сообщаемые контейнером JSP, относятся К номерам строк с ере лет а, представляющего транслированную JSP-страницу, а не к изначальным номерам строк кода JSP-страницы. Интегрированные среды разработки программ, такие как Forte for Java Community Edition компании Sun Microsystems, Inc., дают возможность компилировать JSP-страницы в собственном окружении, чтобы иметь возможность наблюдать сообщения о синтаксических ошибках. Эти сообщения включают в себя описание оператора сервлета. который представляет транслированную JSP-страницу, что может оказаться полезным при выявлении ошибок. Совет по тестированию и отладке 3.2 Многие контейнеры JSP сохраняют серелеты, представляющие транслированные JSP-страницы. Например, в каталоге Tomcat имеется подкаталог work, в котором можно найти исходный код для сервлетов, транслированных Tomcat. Чтобы протестировать в Tomcat JSP-страницу, представленную на рис. 3.4, скопируйте файл welcome.jsp в каталог jsp, созданный в разделе 3.3. Откройте Web-браузер и введите следующий URL, чтобы протестировать страницу welcome.jsp: http;//localhost:8080/advjlitpl/isp/welcome.jsp При первом выполнении ЛЯР-страницы отображается форма, в которую вы можете ввести ваше имя, поскольку параметр firstName JSP-странице через URL не передавался. После того как вы отправите ваше имя, окно браузера должно иметь вид, представленный ка второй копии экрана на рис. 3.4. Замечание. Как и для сервлетов, параметры запроса get можно передавать через URL. Следующий URL передает параметр firstName JSP-странице welcome.jsp г http://l0carhost:8O8O/advjhtpl/jsp/welcome.jsp?firstName=Paul 3.6. Стандартные действия В продолжение обсуждения технологии JSP рассмотрим стандартные действия JSP 1рис. 3.5). Эти действия предоставляют реализации JSP возможность доступа к нескольким наиболее типичным задачам, выполняемым на JSP-странице, таким как включение содержимого из других ресурсов, переадресация и взаимодействие с компонентами JavaBeans. Контейнеры JSP обрабатывают действия на этапе запроса. Описания действий ограничиваются тегами <jsp:3eucmeue> и </)$р:действие>, где действие — это имя стандартного действия. В случаях, ко-
JavaServer Pages (JSP) 121 гда между начальным и конечным тегами ничего нет, может быть использован синтаксис XML для пустых элементов <}$р:действие/>. Стандартные действия JSP приведены в таблице на рис. 3.5. Действие <jsp:include> <jsp:forward> <jsp:plugin> < j sp: parajn> Описание Динамически включает другой ресурс в JbP-страницу. При выполнении JSP-стрзницы этот ресурс включается и обрабатывается. Переадресовывает обработку запроса другой JSP-странице, сервлету или статической аранице. Это действие завершает выполнение текущей JSP-сфаницы. Дает возможность добавления е страницу подключаемого компонента в виде специфичного для браузера HTML-элемента object или embed. /Для апппета Java это действие дает возможность загружать и устанавливать подключаемые модули Java Plug in, если они еще не установлены на компьютере клиента. Используется совместно с действиями include, forward и plugin для задания дополнительных пар имя/значение для данных, используемых этими действиями. Манипулирование компонентами JavaBeans <jsp:useBean> <j sp:setProperty> <j sp:getProperty> Указывает, что JSP-страница использует экземпляр компонента JavaBeans. Это действие задает область видимости компонента и присваивает ему идентификатор, который компоненты сценария могут использовать для манипулирования компонентом. Задает свойство указанного экземпляра компонента JavaBeans, Особенностью этого действия является автоматическое отождествление параметров запроса со свойствами компонента, носящими эти же имена. Получает свойство для заданного экземпляра компонента JavaBeans и преобразовывает результат в строку для вывода ее а составе ответа. Рис. 3.5. Стандартные действия JSP 3.6-1. Действие <jsp:include> В JavaServer Pages поддерживается два механизма включений: действие <jsp: inclu.de> и директива include. Действие <jsp:include> дает возможность включать динамическое содержимое в страницу JavaServer Page. Если включаемый ресурс в промежутке между запросами изменился, следующий запрос к JSP-страпице, содержащей действие <jsp:inelude>, будет осуществлять включение нового содержимого ресурса. В свою очередь, директива include копирует содержимое на JSP- страниду один pas, на этапе трансляции. Если включенный ресурс изменился, новое содержимое не будет отражено в JSP-странице, которая использует директиву include, если только JSP-страница не будет перекомпилирована. В таблице на рис. 3.6 описаны атрибуты действия <jsp:include>. S Общая методическая рекомендация 3.8 Согласно спецификации JavaServer Pages 1.1, контейнеру JSP предоставляется возможность определять, был ли изменен ресурс, включенный с помощью директивы include. Если да, контейнер может перекомпилировать JSP-страницу, которая включает ресурс. Однако спецификация не предоставляет какой-либо механизм, который может быть использован для указания контейнеру на изменение включенного ресурса.
122 Глава 3 Совет по повышению эффективности 3.2 Действие <jsp:include> обладает большей гибкостью, чем директива include, но требует больших непроизводительных затрат в случае частого изменения содержимого страницы. Используйте действие <jsp: included только в том случае, если необходимо иметь динамически меняющееся содержимое. Типичная ошибка программирования 3.3 Установка для атрибута flush действия <JKp:include> значения false является ошибкой, выявляемой на этапе трансляции. На сегодняшний день для атрибута flush поддерживается только значение true. Атрибут page flush Описание Задает относительный UR1 к включаемому ресурсу. Ресурс должен быть частью этого же Web-приложения. Задает, следует ли очищать буфер после выполнения директивы include. Согласно спецификации JSP 1.1, этот атрибут должен иметь значение true. Рис. 3.6. Атрибуты действия <jsp:include> Типичная ошибка программирования 3.4 Если для действия <jsp:include> не указать атрибут flush, на этапе трансляции будет зафиксирована ошибка. Задание этого атрибута является обязательным. Типичная ошибка программирования 3.5 Указание в действии <jsp:include> страницы, которая не является частью этого же Web-приложения, порождает ошибку на этапе запроса. В этом случае действие <jsp:include> не будет осуществлять включение какого-либо содержимого. Следующий пример демонстрирует действие <jsp:include>, использующее четыре ресурса XHTML и JSP, которые представляют как статическое, так и динамическое содержимое. JSP-страница include.jsp (рис. 3.10) осуществляет включение трех внешних ресурсов: banncr.html (рис. 3.7), toc.html (рис. 3.8) и clock2.jsp (рис. 3.9). JSP-страница include .jsp создает XHTML-документ, содержащий таблицу table, в которой ресурс banner.html занимает два столбца в первой строке, ресурс toc.html — левый столбец во второй строке, а ресурс cIock2 jsp (упрощенная версия страницы, представленной на рис. 3.1) — правый столбец во второй строке. Страница, представленная на рис. 3.10, использует три действия <jsp:include> (строки 38 39, 48 и 55 56) в качестве содержимого элементов td в таблице table. С помощью XHTML-документов и JSP-страницы, представленной на рис. 3.10, демонстрируется, каким образом в JSP-страницы может включаться статическое и динамическое содержимое. На копиях экрана иа рис. 3.10. показаны результаты выполнения двух отдельных запросов к странице include.jsp. Страница, представленная на рис. 3.9 (clock2.jsp), демонстрирует, как определить местность клиента (класс Locale из пакета java.util), и использует этот объект Locale для форматирования даты Date в соответствии с форматом DateFormat (пакет java.text). В строке 14 вызывается метод getLocale объекта request, который возвращает местность клиента (объект Locale). В строках 17-20 вызывается статический метод getDateTimelnstance класса DateFonnat для получения объек-
JavaServer Pages (JSP) 123 та DateFormat. Первые два параметра указывают, что дата и время должны быть представлены в формате LONG (другими возможными форматами являются FULL, MEDIUM, SHORT и DEFAULT). Третий параметр задает местность, для которой объект DateFormat должен осуществить форматирование даты. В сроке 25 вызывается метод format объекта DateFormat для формирования строки, представляющей дату. Объект DateFormat форматирует эту строку для местности, указанной в строках 17-20. [Замечание. Этот пример работает для западных языков, в которых используется набор символов ISO-8859-1. Для языков, которые не поддерживают этот набор символов, JSP-страница должна предоставить соответствующий набор символов с помощью директивы JSP page (раздел 3.7.1). На сайте java.s un.com/j 2se/1.3/docs/guide/intl /encoding/doc.html представлен перечень кодировок символов. Тип содержимого ответа определяет набор символов, который будет использоваться в ответе. Тип содержимого имеет вид: "mime-mun; charset^rcodupoerca" (например, "text/html;charset=ISO-8859-1"),] Чтобы протестировать в Tomcat страницу, представленную на рис, 3.10, скопируйте файлы banner.html, toc.html, clock2.jsp, include.jsp и каталог images в каталог jsp, созданный в разделе 3.3, Откройте Web-браузер и введите следующий URL, чтобы протестировать страницу welcome.jsp: http://localhost:8080/advjhtpl/jsp/include.jsp 1<!-- Рис. 3.7. banner.html —> 2<»-- "Шапка", включаемая в другой документ. —> 3 <div style = "width: 5B0px"> 4 <p> 5 Java(TM), С, C++, Visual Basic(R), 6 Object Technology, and <br /> Internet and 7 World Wide Web Programming Training6nbsp;<br /> 8 On-Site Seminars Delivered Worldwide 9 </p> 10 11 <p> 12 <a href = "mailto:deitel@deitel.com"> 13 deitel@deitel.conK/aXbr /> 14 15 978.579.9911<br /> 16 4 90B Boston Post Road, Suite 200, 17 Sudbury, MA 01776 18 </p> 19 </div> Рис. З.7. «Шапка» (banner.html), которая включается в начало XHTML-доку мента, создаваемого JSP-стрэницей, представлен ной на рис. 3.10 1 <Г— Рис. 3.8. toc.html —> 2 <!-- Содержимое, включаемое в другой документ —> 3 4 <рХа href = "http://www.deitel.com/books/index.htnil"> 5 publications/BoolcStore 6 </аХ/р> 7 8 <рХа href = "http://www.deitel.com/whatsnew.html"> 9 What's New 10 </aX/p>
124 Глава 3 11 12 <рХа href = "http: //www.deitel .com/books/downloads.html"> 13 Downloads/Resources 14 </ax/p> 15 16 <pXa href = "http://www.deitel.com/faq/index.htBil"> 17 FAQ (Frequently Asked Questions) 18 </ax/p> 19 20 <pXa href = "http://www.deitel.com/intro.html"> 21 Who we are 22 </ax/p> 23 24 <pxa href = "http://www.deitel.eoin/index.htiiil"> 25 Home Page 26 </aX/p> 27 28 <p>Send questions or comments about this site to 29 <a href = "mailto:deitel@deitel.coni"> 30 deitel@deitel.com 31 </aXbr /> 32 Copyright 1995-2002 by Deitel tamp; Associates, Inc. 33 All Rights Reserved. 34 </p> Рис. З.8. Содержимое (toc.html), которое включается в левую часть XHTML-документа, создаваемого JSP-страницей, представленной на рис. 3.10 1 <Г— Рис. 3.9. clock2.jsp —> 2 <!— Дата и время, включаемые в другой документ. —> 3 4 <table> 5 <tr> 6 <td style = "background-color: black;"> 7 <p class = "big" style = "color: cyan; font-size: 3em; 8 font-weight: bold;"> 9 10 <%-- сценарий для определения местности клиента --%> 11 <%— и формата даты --%> 12 <% 13 // получение местности клиента 14 java.util.Locale locale = request.getLocale(); 15 16 // получение формата данкьгх DateFormat для местности клиента 17 java-text.DateFormat dateFormat = 18 Java. text. DateFormat. getDateTimelnstance. ( 19 java.text.DateFormat.LONG, 20 java.text.DateFormat.LONG, locale ) ; 21 22 %> <%— конец сценария --%> 23 24 <%— вывод даты —%> 25 <%^ dateFormat.format( new java.util.Date О ) %> 26 </p>
JavaServer Pages (JSP) 125 27 </td> 28 </tr> 29 </tabJ-e> Рис. З.9. JSP-страница clock2.jsp, которая включается в виде основного содержимое в XHTML-доку мент, создаваемый JSP-страницей, представленной на рис. 3.10 1 <?хи»1 version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhcnul/DTD/xhtmll-strict.dtd"> 4 5<!-- Рие, 3.10. include.jsp —> 6 1 <html xmlns = "http://www.w3.org/1999/xhtna"> 8 9 <head> 10 <title>Os±ng jsp:include</title> 11 12 <style type = "text/css"> 13 body { 14 font-family: tahoma, helvetica, arial, sans-serif; 15 } 16 17 table, tr, td { 18 font-size: . 9em; 19 border: 3px groove; 20 padding: 5px; 21 background-color: tdddddd; 22 } 23 </style> 24 </head> 25 26 <body> 27 <table> 28 <tr> 29 <td style = "width: 160px; text-align: center"> 30 <img src = "images/logotiny,png" 31 width = "140" height = "93" 32 alt = "Deitel S Associates, Inc. Logo" /> 33 </td> 34 35 <td> 36 37 <%— включение документа banner,html в эту JSP-страницу --%> 38 <jsp:include page = "banner.html" 39 flush = "true" /> 40 41 </td> 42 </tr> 43 44 <tr> 45 <td style = "width: 160px"> 46 47 <%-- включение документа toe.html в эту JSP-страницу --%> 48 <jsp:include page я "toe.html" flush = "true" />
126 Глава 3 49 50 51 52 53 54 55 56 57 58 59 60 61 <%- </td> <td style = "vertical-align: top"> - включение документа clock2.jsp в эту jSP-страницу --%> <3sp: include page = "cloelt2 . jsp" flush = "true" /> </td> </tr> </tat>le> </body> 62 </html> ЩН ■--: + й s 3nten« and v^drtJVsteWtfiPraoaimig Т:**ч ' ovata Earnf Meed waUMde aaAsiagEijMfaf''' ЙЙ M?< тргг July 31, 2001 9:46:42 ДМ EDTi Ж I шшшвшт ЩШЙШ! "ЯЯШТГ шё"№^;ащащ Рис. 3.10. JSP-страница indude.jsp, огущесгнляющая включение ресурсен г помощью действия <jsp:include> 3.6,2. Действие <jsp:forward> Действие <jsp:forward> дает возможность JSP-странице переадресовывать обработку запроса. Обработка запроса исходной JSP-страницей завершается, как только JSP-страница переадресовывает запрос. Действие <jsp:forward> имеет единственный атрибут page, задающий относительный URL ресурса (в этом же Web-приложеннн), к которому должен быть переадресован запрос. Общая методическая рекомендация 3.9 При использовании действия <}sp:forward> ресурс, к которому будет переадресован запрос, должен располагаться в том же контексте (Web-приложении), что и JSP-страница, изначально принявшая запрос.
iavaServer Pages (iSP) 127 JSP-страница forwardl.jsp (рис. 3.11) представляет собой модифицированную версию страницы welcome.jsp (рис. 3.4). Главное различие состоит в строках 22—25, в которых JSP-страница forwardl.jsp переадресовывает запрос JSP-странице for- ward2.jsp (рис. 3.12). Обратите внимание на действие <jsp:param> в строках 23-24. Это действие добавляет параметр запроса, представляющий дату и время получения изначального запроса, который переадресовывается странице forward2.jsp. Действие <jsp:param> задает пары имя/значение для данных, которые передаются действиям <jsp:mclude>, <jsp:forward> и <jsp:plugin>. Каждое действие <jsp:param> имеет два обязательных атрибута: name и value. Если действие <jsp:param> задает параметр, который уже присутствует в запросе, новое значение для параметра имеет приоритет над первоначальным значением. Все имеющиеся значения для этого параметра могут быть получены е помощью метода get- ParamcterValue неявного объекта JSP request, который возвращает массив строк. JSP-страница forward2.jsp использует имя пате, заданное в действии <jsp: param> ("date"), для получения даты и времени. Она также использует параметр firstName, изначально переданный странице forwardl.jsp, чтобы получить имя пользователя. Выражение JSP из листинга на рис. 3.12 (строки 23 и 24) осуществляет вставку значения параметров запроса в ответ клиенту. На копии экрана на рис. 3.11 показано начало взаимодействия с клиентом. На копии экрана на рис. 3.12 показаны результаты, возвращаемые клиенту после переадресации запроса к странице forward2.jsp. Чтобы протестировать в Tomcat JSP-страницы, представленные на рис. 3.11 и 3.12, скопируйте файлы forwardl.jsp и forward2.jsp в каталог jsp, созданный в разделе 3.3. Откройте ваш Web-браузер и введите следующий URL, чтобы протестировать страницу welcome.jsp: . http://localhost:8080/advjhtpl/jsp/forwardl.jsp 1 <?xml version = "1.0"?> 2 «C'DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EH" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<!-- Рис. 3.11. forwardl.jsp —> 6 7 <html xmlns = "http://www.w3.org/1999/xhtml"> a 9 <head> 10 <title>Forward request to another JSP</title> 11 </head> 12 13 <body> 14 <* // начало скрипилета IS 16 String name = request.getPararoeter( "firstName" ); 17 18 if ( name ! = null ) { 19 20 %> <%— закрытие скриптлета для вставки данных с неизменной структурой --%> 21 22 <jsp:forward page - "£orward2.jsp"> 23 <jsp:param паше = "date" 24 value = "<%= new javs.util.DateO %>" /> 25 </jsp:forward> 26
128 Глава 3 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 4В 49 <% // продолжение скриптлета } // конец блока if else { %> <%-- закрытие скриптлета для вставки данных с неизменной структурой —%> <forjn action = "forwardl.jsp" method = "get"> <p>Type your first name and press Submit</p> <pXinput type = "text" name = "firstName" /> <input type = "submit" value = "Submit" /> </p> </form> <% // продолжение скрилтлета } // конец блоха else %> <%— конец скриптлета --%> </body> </html> <?-- конец XHTML-документа —> ШЩШШЗШ!! Л Forward requ Ш&.*ягЩФ№М ттштштм ^Pffi***108* :atlSuJadvtitpW]Spff<»'''»ril 'Й>. .ijjjjj -в ТУре your Erst name and press Submit Pod Submit! Рис. 3.11. JSP-страница forwardtjsp принимает параметр firstName, добавляет дату к параметрам запроса и переадресовывает запрос странице forward2.jsp для дальнейшей обработки 1 <?xml version = "1.0"?> 2 <ID0CTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<!-- forward2.3sp —> 6 7 <atml xmlns = "http://www.w3.org/1999/xhtml"> 8 9 <head> 10 <title>Processing a forwarded request</title> 11 12 <style type = "text/ess"> 13 .big { 14 font-family: tahoma, helvetica, arial, sans-serif:
JavaServer Pages (JSP) 129 15 font-weight: bold; 16 font-size: 2em; 17 } 18 </style> 19 </head> 20 21 <body> 22 <p class = "big"> 23 Hello <%= request,getParameter( "firstName" ) %>, <br /> 24 Your request was received <br /> and forwarded at 25 </p> 26 27 <table style = "border: 6px outset;"> 28 <tr> 29 <td 3tyle = "background-color: black;"> 30 <p class = "big" style = "color: cyan;"> 31 <%= request.getParameter( "date" ) %> 32 </p> 33 </td> 34 </tr> 35 </table> 36 </body> 37 38 </html> J:** "*.^..^*** 1°* * : . :, -Р...ШШЛ- у*— ■ _-- - fe^ ~.*t-?9. Ф ^Jjft^^.JS.^gg^CHjf^'Jlft lilies Hello Paul, Your request was received and forwarded at Sun Jul 22 17:18:43 EDT 2001 1ёоЯИШШВЭТЩ; Г ' .jjfuiajwwwiiS Рис. 3.12. JSP-страница forward2.jsp принимает запрос (в данном примере - от страницы forwardl.jsp) и использует параметры запроса при выдаче ответа клиенту 3.6.3. Действие <jsp:plugin> Действие <jsp:plugin> добавляет апплет или компонент JavaBeans в Web-страницу в виде специфичного для браузера XHTML-элемента object или embed. Это действие также дает возможность клиенту загружать и устанавливать подключаемый модуль Java Plug-in, если он еще не был установлен. В таблице на рис. 3.13 описаны атрибуты действия <jsp:plugin>. Атрибут type code Описание Тип компонента: компонент JavaBeans или апплет. Класс, который гредставляет компонент.
130 Глава 3 Атрибут codebase align archive height hspace jreversion name vspace title width nsplugimirl iepluginurl Описание Местоположение класса, задаваемого атрибутом code, и архивов, задаваемых атрибутам archive. Способ выравнивания компонента. Список архивных файлов, разделенных пробелами, которые содержат ресурсы, используемые компонентом. Такой архив может включат^ класс, задаваемый атрибутом code. Высота компонента на странице, заданная в пикселах или в процентах. Пространство слева и справа от компонента, выраженное в ликсегах. Версия окружения выполнения Java Runtime Environment и подключаемого модуля, необходимого для выполнения компонента. Значением по умолчанию является 1.1. Имя компонента. Пространство над и под компонентом, выраженное в пикселах. Текст, который описывает компонент Ширина компонента на странице, выраженная в пикселах или в процентах. Адрес для загрузки подключаемого модуля Java Plug-in для Netscape Navigator. Адрес для загрузки подключаемого модуля Java Plug-in для Internet Explorer. Рис. 3.13. Атрибуты действия <jsp:plugin> На рис. 3.14 представлен апплет, который формирует изображение с помощью средств API Java2D, Апплет имеет три параметра, которые дают возможность программисту JSP-страницы задавать фоновый цвет для рисунка. Параметры представляют составляющие красного (red), зеленого (green) и синего (blue) цвета значениями палитры RGB в диапазоне 0-255. Апплет получает значения параметров в строках 21-23. Если в процессе обработки параметров возникают какие-либо исключения, они перехватываются в строке 32 и игнорируются, оставляя для ап- плета белый цвет фона, используемый по умолчанию. 1// Рис. 3.14. ShapesApplet.Java 2 // Апплет, демонстрирунщий рисование произвольной траектории в Java2D. 3 package com.deite1.advjhtpl.jsp.applet; 4 5 // Набор базовых пакетов Java 6 import Java.applet.*; 7 import Java.awt.event.*; 8 import Java.awt.*; 9 import Java.awt.geom.*; 10 11 // Пакет» расширений Java 12 import javax,swing.*; 13 14 public class ShapesApplet extends JApplet { 15 16 // инициализация апплета 17 public void init() 16 i 19 // получение параметров цвета из XHTML-файла 20 try { 21 int red = Integer.paraelnt( getParameter( "red" ) );
JavaServer Pages (JSP) 131 22 int green = Integer.parselnt{ getParameter( "green" ) ) 23 int blue = Integer.parselnt( getParameter{ "blue" ) } ; 24 25 Color backgroundColor = new Color{ red, green, blue ); 26 27 setBackground( backgroundColor ); 28 } 29 30 // если при обработке параметров цвета возникло 31 // исключение, перехватить его и игнорировать 32 catch ( Exception exception ) { 33 // ничего не предпринимать 34 } 35 } 36 37 public void paint( Graphics g ) 38 { 39 // создание массивов координат хну 40 int xPoints[] = 41 { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 }; 42 int yPoints[] = 43 { 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 },- 44 45 // получение ссылки на объект Graphics2D 46 Graphics2D g2d = ( Graphics2D ) g; 47 4 8 // создание звезды из набора точек 49 GeneralPath star = new GeneralPathf) ,- 50 51 // задание начальной координаты траектории GeneralPath 52 star.moveTo( xPoints[ 0 ], yPoints[ 0 ] ); 53 54 // создание звезды -- звезда здесь не рисуется 55 for ( int к = 1; к < xPoints.length; k+* ) 56 star.lineTo( xPoints[ k ], yPoints[ k ] ); 57 58 // замыкание контура фигуры 59 star.closePath(}; 60 61 // перемещение начала в точку {200, 200) 62 g2d.translate{ 200, 200 ); 63 64 // вращение вокруг начала и рисование звезд случайно выбранными цветами 65 for ( int j = 1; j <= 20; j++ ) { 66 g2d.rotate( Math.PI / 10.0 ); 67 68 g2d.setColor( 69 new Color( ( int ) { Math.random() * 256 ), 70 { int ) { Math.random() * 256 ), 71 ( int ) ( Math.random() * 256 ) ) ); 72 73 g2d.fill( star ); // рисование закрашенной звезды 74 } 75 } 7€ } Рис. 3.14. Апплет. демонстрирующий действие <jsp:plugin>
132 Глава 3 Большинство используемых на сегодняшний день Web-бра узе ров не поддерживают апплетов, написанных для платформы Java 2. Для выполнения таких аппле- тов в большинстве современных браузеров требуется подключаемый модуль Java Plug-in. JSP-страница, представленная на рис. 3.15, использует действие <jsp: plagin> (строки 10-22) для подключения модуля Java Plug-in. В строке 11 указывается имя пакета и имя класса для класса апплета. В строке 12 указывается местоположение класса апплета. В строке 13 задается, что апплет должен иметь окно шириной в 400 пикселов, а в строке 14 задается, что высота окна апплета должна составлять 400 пикселов. В строках 16-20 задаются параметры апплета. Вы можете изменить цвет фона в апплете, изменив значения для красного, зеленого и синего цветов. Учтите, что действие <jsp:plugm> требует, чтобы любые имеющиеся действия <jsp:param> указывались в действии <jsp:params>. Чтобы протестировать действие <jsp:plugin> в Tomcat, скопируйте файлы plugin.jsp и ShapesApplet.class в каталог jsp, созданный в разделе 3.3. {Замечание. Класс ShapesApplet определен в пакете com.deit el.advjhtpl. jsp. applet. Этот пример будет работать только в том случае, если в каталоге classes определена надлежащая структура каталогов пакета.] Откройте ваш Web-браузер и введите следующий URL, чтобы протестировать страницу plugin.jsp: http://localhost:8080/advjhtpl/jsp/plugin.jsp На копиях экрана на рис. 3.15 показано выполнение апплета в Microsoft internet Explorer 5.5 и в Netscape Navigator 6.0. 1<!-- Рис. 3.15. plugin.jsp —> 2 3 <html> 4 5 <head> 6 <title>Using jsp:plugin to load an applet</title> 7 </head> S 9 <body> 10 <jsp:plugin type = "applet" 11 code = "com.deitel.advjhtpl.jsp.applet.ShapesApplet" 12 codebase = "/advjhtpl/jsp" 13 width = "J00" 14 height = "flOO1^ 15 16 <jsp :paranis> П <jsp:paiam name = "red" value = "255" /> 18 <jsp:param name = "green" value = "255" /> 19 <jsp:param name = "blue" value = "0" /> 20 </jsp:params> 21 22 </3sp:plugin> 23 </body> 24 </htitll> Рис. 3.15. Использование действия <jsp:plugin> для встраивания апплета Java2 в JSP-страницу (часть 1)
JavaServer Pages (JSP) 133 |j lib .jg^'ngT; FW»ty Too* нф '."'J Vp J**t»gidKt°:fAitJica,3ll8niatitml/Hifelm>n.№ '±|'(^Ч: "в. aw «i^lfet Л "3 ^BJp^wWWiff*1'™!^* т-гвдавдшН! Рис. 3.15. Использование действия <jsp:plugin> для встраивания апплета Java2 в JSP-страницу (часть 2) 3.6.4. Действие <jsp:useBean> Действие <jsp:useBean> дает возможность JSP-странице манипулировать объектом Java. Это действие создает объект Java или находит существующий объект для его использования JSP-страницей, В таблице на рис. 3.16 представлены атрибуты действия <jsp:useBean>. Если атрибуты class и beanName не заданы, контейнер JSP пытается найти имеющийся объект с типом, задаваемым атрибутом type. Подобно неявным объектам JSP, объекты, задаваемые действием <jsp: useBean>, имеют области видимости page (страница), request (запрос), session (сеанс) или application (приложение), которые определяют, где эти объекты могут использоваться в Web-приложении. Объекты с областью видимости page (страница) доступны только для страницы, на которой они определены. В принципе, несколько JSP-страниц могут иметь доступ к объектам с другими областями видимости. Например, все JSP-страницы, которые обрабатывают один запрос, могут иметь доступ к объекту с областью видимости request (запрос). 3 Типичная ошибка программирования 3.6 Должен быть задан один или оба атрибута действия <jsp:useBean>: class и type; в противном случае возникнет ошибка на этапе трансляции.
134 Глава 3 На страницах многих современных Web-сайтов размещается меняющаяся реклама. При каждом посещении одоой из таких стрениц в Web-браузере пользователя, как правило, отображается новая реклама. Обычно щелчок мышью на изображении с рекламой переносит вас на Web-сайт компании, поместившей рекламу. Наш первый пример действия <jsp:useBean> демонстрирует простой компонент JavaBeans, который циклически отображает пять рекламных изображений. В этом примере в качестве рекламы используются рисунки обложек ряда книг по программированию. При щелчке на обложке вы переходите на Web-сайт Amazon.com, где сможете прочесть информацию о книге и, возможно, заказать ее. Компонент Rotator (рис. 3.17) определяет три метода: get Image, getLink и nextAd. Метод getlmage возвращает имя файла изображения для рисунка обложки. Метод getLink возвращает гиперссылку на информацию о книге на сайте Amazon.com. Метод nextAd обновляет объект Rotator, чтобы следующие обращения к методам getlmage и getLink возвращали информацию о другой рекламе. Методы getlmage и getLink представляют, соответственно, свойства только для чтения image и link компонента JavaBeana. Объект Rotator отслеживает текущую рекламу с помощью переменной selectedlndex, которая обновляется путем вызова метода nextAd. Атрибут id scope class beanName type Описание Имя, используемое для манипулирования объектом Java с помощью действий <jsp:setProperty> и <jsp:getProperty> Переменная с этим именем также объявляется для использования ее в элементах сценария JSP. Задаваемое здесь имя чувствительно к регистру. Область видимости, в которой доступен объект Java; page, request, session или application. Область видимости по умолчанию * раде (страница). Полное имя класса объекта Java. Имя компонента, которое используется методом instantiate клзсса java.beans.Beans для загрузки компонента JavaBeans в память. Тип компонента JavaBeans. Этот тип может совладать с типом атрибута class, быть суперклассом этого типа или интерфейсом, реализуемым этим типом. Значением по умолчанию является тип class Исключение ClassCastException возбуждается, если тип объекта Jsva не совпадает с типом, задзвземым атрибутом type. Рис. 3.16. Атрибуты действия <jsp:useBean> 1 // Рис. 3.17. Rotator.java Iff Компонент JavaBeans для создания меняющейся рекламы. 3 package com.deitel.advjhtpl.jsp.beans; 4 5 public class Rotator { 6 private String images[] = { "images/jhtp3,jp^", 7 "images/xmlhtpl.jpg", "images/ebechtpl.jpg", 8 "images/iw3htpl.jpg", "images/cpphtp3.jpg"}; 9 10 private String links[] = { 11 "http://www.amazon.com/exec/obidos/ASIN/0130125075/" + 12 "deitelassociatin", 13 "http://www.ama2on.com/exec/obidos/ASIN/01302SJ173/" + 14 "deitelassociatin", 15 "http://www.amazon.com/exec/obidos/ASIN/01302e419X/" +
JavaServer Pages (JSP) 135 16 "deitelassociatin", 17 "http://www.amazon.conL/exec/obidos/ASIN/0130161438/" + 18 "deitelassociatin", 19 "http://vfww.amazon.com/exec/obidos/ASIiJ/0130895717/" + 20 "deitelassociatin" }; 21 22 private int selectedlndex = 0; 23 24 // возврат файла изображения для текущей реклам» 25 public String getlmageO 26 { 27 return images[ selectedlndex ]; 28 ) 29 30 // возврат URL Web-сайта, разместившего рекламу 31 public String getLinM) 32 { 33 return links[ selectedlndex ]; 34 } 35 36 // обновление индекса selectedlndex, чтобы последующие 37 // обращения к getImage и getLink возвращали другую рекламу 38 public void nextAdO 39 { 40 selectedlndex = ( selectedlndex + 1 ) % images.length; 41 > «J „__ Рис. 3.17. Компонент Rotator, который работает с несколькими рекламными изображениями В строках 7- 8 JSP-страницы adrotator.jsp (рис. 3.18) извлекается ссылка на экземпляр класса Rotator. Атрибут id для компонента имеет значение rotator. JSP-страница использует это имя для манипулирования компонентом. Объект имеет сеансовую область видимости (session), чтобы каждый отдельный клиент видел одну и ту же последовательность рекламных изображений в течение сеанса просмотра. Когда JSP-страница adrotator.jsp получает запрос от нового клиента, контейнер JSP создает компонент и сохраняет его в JSP-странице в данных сеанса session для этого клиента (объект типа HttpSession). Для каждого запроса к этой JSP-странице в строке 22 используется ссылка rotator, созданная в строке 7, чтобы вызвать метод nextAd компонента Rotator. Таким образом, каждый запрос будет получать следующую рекламу, обслуживаемую компонентом Rotator. В строках 29-34 определяется гилерссылка на сайт Amazon.com для определенной книги. В строках 29-30 вводится действие <jsp:getProperty> для получения значения свойства link компонента Rotator. Действие <jsp:getProperty> имеет два атрибута — name и property — которые задают объект, подлежащий манипулированию, и свойство, значение котпрого следует получить. Если объект JavaBeans использует стандартные соглашения по именованию компонентов JavaBeans, для получения значения свойства link из компонента следует использовать метод get!.ink. Действие <jsp:getProperty> вызывает метод getLink для компонента, на который указывает ссылка rotator, преобразует возвращенное значение в строку и выводит эту строку в составе ответа клиенту. Свойство link становится значением атрибута href гиперссылки. Гиперссылка представляется на итоговой Web-странице в виде изображения обложки книги. В строках 32-33 создается элемент img и использу-
136 Глава 3 ется другое действие <jsp:getProperty> для получения значения свойства image компонента Rotator. Имейте в виду, что свойства link и image могут быть получены с помощью выражений JSP. Например, действие <jsp:getProperty> в строках 29-30 может быть заменено выражением <%=rotator.getLink( )%> Аналогично, действие <jsp:getProperty> в строках 32-33 может быть заменено выражением <%=rotator,getlmage( )%> 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!-- Рис. 3.18. adrotator.jsp --> e 7 <jsp:useBean id = "rotator" scope = "session" 8 class - "com.deitel.advjhtpl.jsp.beans.Rotator" /> 9 10 <html xmlns = "http://www.w3.org/1999/xhtml"> 11 12 <head> 13 <title>AdRotator Example</title> 14 15 <style type = "text/ess"> 16 .big { font-family: helvetica, arial, sans-serif; 17 font-weight: bold; 18 font-size: 2em; > 19 </style> 20 21 <%-- обновление рекламы —%> 22 <% rota tor. nextAdO ; %> 23 </head> 24 25 <body> 26 <p class = "big">AdHotator Example</p> 27 28 <p> 29 <a href = "<3sp:getProperty name = "rotator" 30 property = "link" />"> 31 32 <img sre = "<jsp:getProperty name = "rotator" 33 property = "image" />" alt = "advertisement" /> 34 </a> 35 </p> 36 </body> 37 </html> Рис. 3.18. JSP-страница adrotator.jsp использует компонент Rotator для отображения новой рекламы при каждом следующем запросе страница (часть 1)
JavaServer Pages (IS?) 137 №№$«**£; .7-^Vm<i ^ГШШмЩШШЩ Рис. 3.18. JSP-страница adrotator.jsp испогьзует компонент Rotator для отображений новой рекламы при каждом следующем запросе страницы {часть 2) Чтобы протестировать JSP-страницу adrotator.jsp в Tomcat, скопируйте файл adrotator.jsp в каталог jsp, созданный в разделе 3.3. Вы должны были скопировать каталог Images в каталог jsp, когда тестировали JSP-страницу, представленную на рис. 3.10. Если вы этого не сделали, скопируйте каталог images сейчас. Скопируйте файл Rotator.class в каталог WEB-INF\classes корня advjhtpl Web- приложения в Tomcat. [Замечание. Этот пример будет работать только в том случае, если в каталоге classes определена надлежащая структура каталогов для Rotator. Класс Rotator определен в пакете com.deitel.advjhtpl.jsp.beans.] Откройте ваш Web-браузер и введите следующий URL, чтобы протестировать JSP-страницу adrotator.jsp: http://localhost:8080/advjhtpl/jsp/adrotator.jap Попробуйте несколько раз перезагрузить эту JSP-страницу в вашем браузере, чтобы увидеть, как меняется реклама при каждом последующем запросе. Действие <jsp:setProperty> может задавать значения свойств компонента JavaBeans. Это действие особенно полезно для связывания значений параметров запроса со свойствами компонента JavaBeans. Параметры запроса могут быть использованы для установки свойств примитивных типов boolean, byte, char, int, long, float и double, а также типов String, Boolean, Byte, Character, Integer, Long, Float и Double из пакета java.lang. Атрибуты действия <jsp:setProperty> приведены в таблице на рис. 3.19.
13S Глава 3 Атрибут name property pa ram value Описание Идентификатор компонента JavaBeans, для которого будет задаваться свойство (или свойства). Имя задаваемого свойства. Задан/е "«" в качестве значения для этого атрибута приводит к тому, что JSP-страница отождествляет параметры запроса со свойствами компонента. Для каждого параметра запроса, который отождествляется (т.е. имя параметра запроса идентично имени свойства компонента) с соответствующим свойством компонента, этот параметр задается в качестве значения свойства. Если в качестве значения параметра задано "", значение свойства компонента остается неизменным. Если имена параметров запроса не совпадают с именами свойств компонента, этот атрибут может использоваться для указания, какой параметр запроса следует использовать для получения значения определенного свойства компонента. Этот атрибут является необязательным Если атрибут не задан, имена параметров запроса должны совпадать с именами свойств компонента. Значение, которое присваивается свойству компонента. Значение обычно является результатом выполнения выражения JSP. Этот атрибут особенно полезен для задания свойств компонента, которые не могут быть заданы с помощью параметров запроса. Этот атрибут является необязательным. Если атрибут не задан, свойство компонента JavaBeans должно иметь такой тип данных, который допускает установку через параметры запроса. | Рис. 3.19, Атрибуты действия <jsp:setProperty> Типичная ошибка программирования 3.7 Используйте атрибут value действия <J8p:setProperty> для задания типов свойств компонента JavaBeans, которые не могут быть заданы с помощью параметров запроса; в противном случае возникнет ошибка преобразования. Общая методическая рекомендация 3.10 Действие <jsp:setProperty> может использовать значения параметров запроса для задания свойств компонента JavaBeans только для следующих типов свойств: String, примитивных типов (boolean, byte, char, short, int, long, float и double) и классов-оберток для типов (Boolean, Byte, Character, Short, Integer, Long, Float и Double). В качестве следующего примера рассмотрим приложение гостевой книги, которое дает возможность пользователям помещать свое имя, фамилию и адрес e-mail в базу данных. После отправки этой информации пользователю отображается Web-страница, содержащая перечень всех пользователей, зарегистрированных в гостевой книге. Адрес e-mail каждого лица отображается в виде гиперссылки, которая позволяет пользователю отправлять этому лицу сообщения электронной почты. Пример демонстрирует применение действия <jsp:setProperty>. Кроме того, пример знакомит с директивой page JSP и страницами ошибок JSP. Пример приложения гостевой книги состоит из компонентов JavaBeans Guest- Bean {рис. 3.20) и GuestDataBean (рис, 3.21), а также JSP-страницы guestBook- ErrorPage.jsp (рис. 3.24). Образцы выходных результатов для этого примера представлены на рис. 3.25.
JavaServer Pages (JSP) 13Э Компонент JavaBeans Guest Bean (рис. 3.20) определяет три свойства, относящихся к гостю: имя firstNamc, фамилия lastName и адрес электронной почты email. Каждое из свойств является свойством для чтения/записи и предполагает применение методов set и get для манипулирования свойством. 1 // Рис. 3.20. GuestBean.Java 2 // Компонент JavaBeans для хранения данных о госте в гостевой книге. 3 package com.deitel.advjhtpl.j sp.beans; 4 5 public class GuestBean { 6 private String firstNamc, lastName, email; 7 8 // задание имени гости 9 public void setFirstName( String name ) 10 { 11 firstName = name; 12 } 13 14 // получение имени гостя 15 public String getFirstName() 16 { 17 return firstName; ia ) 19 20 // задание фамилии гостя 21 public void setLastName( String name ) 22 i 23 lastName = name; 24 ) 25 26 // получение фамилии гостя 27 publio String getLastName{) 28 { 29 return lastName; 30 ) 31 32 /У задание адреса e-mail гостя 33 public void setEmail( String address ) ■ 34 ( 35 email = address; 36 } 37 38 // получение адреса e-mail гостя 39 public String getEmailO 40 { 41 return email; 42 ) ДЗ ) ___ Рис. 3.20. Компонент Guest Book хранит информацию для одного гостя Компонент JavaBeans Guest Da taBean (рис. 3.21) осуществляет соединение с базой данных guestbook и предоставляет методы getGuestList и addGueat для манипулирования базой данных. База данных guestbook имеет одну таблицу (guests), содержащую три столбца (firstName, lastName и email). Мы предоставляем сценарий SQL (guestbook.sql) вместе с этим примером, который может быть использо-
140 Глава 3 ван для создания базы данных Cloudscape guestbook. Другие подробности, связанные с созданием базы данных с помощью Cloudscape, вы можете найти на сайте www.cloudscape.com, а также в главе 8 книги «Технологии программирования на Java 2. Книга 1». 1 // GuestDataBean.Java 2 // Класс GuestDataBean создает соединение с базой данных и 3 // поддерживает вставку и извлечение данных из базы данных. 4 package com.deitel.advjhtpl.j sp.beans; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.sql.*; 9 import Java.util.*; 10 11 public class GuestDataBean { 12 private Connection connection; 13 private PreparedStatement addRecord, getRecords; 14 15 // создание объекта TitlesBean 16 public GuestDataBean() throws Exception 17 ^ 18 // загрузка драйвера Cloudscape 19 Class.forName( "COM.cloudscape.core.RmiJdbcDriver" ); 20 21 // соединение с базой данных 22 connection = DriverManager.getCoiwiection( 23 "jdbc:rmi:jdbc:cloudscape:guestbook" ); 24 25 getRecords = 2 6 connection.prepareStatement( 27 "SELECT firstMame, lastHаде, email FROM guosts" 28 ); 29 30 addRecord = 31 connection.prepareStatement( 32 "INSERT INTO guests ( " + 33 "firstName, lastName, email ) " + 34 "VALUES ( ?, ?, ? )" 35 ); 36 } 37 38 // возврат списка объектов GuestBean 39 public List getGuestList() throws SQLException 40 { 41 List guestList = new ArrayList(); 42 43 // получение списка названий 44 KesultSet results = getRecords.executeQuery0; 45 46 // получение строки данных 47 while ( results.next{) ) { 48 GuestBean guest = new GuestBean(); 49 50 guest.setFirstName( results.getString( 1 ) ); 51 guest.setLastName{ results.getString< 2 ) );
JavaServer Pages (JSP) 141 52 guest.setEmail( results.getString( 3 ) } ; 53 54 guestList.add( guest ) ; 55 } 56 57 return guestList; 58 } 59 60 // вставка сведений о госте в базу данных гостевой книги 61 public void addGuest( GuestBean guest ) throws SQLExcepLion 62 { 63 addRecord.setString( 1, guest.getFirstName() ); 64 addRecord. setStringt 2, guest. getLastNameO ) •' 65 addRecord.setString( 3, guest.getEmail() ); 66 67 addRecord.executeUpdate{); 68 } 69 70 // закрытие операторов и завершение соединения с базой данных 71 protected void finalize(} 72 { 73 // попытка закрыть соединение с базой данных 74 try ( 75 getRecords.close(}; 76 addRecord.close(); 77 connection.close(); 78 } 79 80 // обработка исключения SQLException при операции закрытия 81 catch ( SQLException sqlException ) { 82 sqlException.prints taclcTrace () ; 83 > 84 } 85 } Рис. 3.21. Компонент GuestDataBean осуществляет доступ к базе данных от имени JSP-страницы guestBookLogin.jsp Метод getGuestList (строки 39-58) класса GuestDataBean возвращает список ArrayList объектов GuestBean, представляющих гостей в базе данных. Метод getGuestList создает объекты GuestBean из результирующего множества ResultSet, возвращенного подготовленным оператором (объект типа PreparedStatement) get- Records, определенного в строках 25—28 и выполняемого в строке 44) Метод add Guest класса GuestDataBean (строки 61-68) принимает в качестве параметра объект GuestBean и использует свойства объекта GuestBean в качестве параметров для подготовленного оператора (объект типа PreparedStatement) addRecord, определенного в строках 30-35. Этот подготовленный оператор (выполняемый в строке 67) помещает в базу данных сведения о новом госте. Обратите внимание, что методы конструк'гора GuestDataBean getGuestList и addGucst не обрабатывают потенциальные исключения. В конструкторе в строке 19 может возбуждаться исключение ClassNotFoundException, а другие операторы могут возбуждать исключения SQLException. Аналогично, исключения SQL- Exception могут возбуждаться из тела методов getGuestList и addList. В этом примере мы сознательно разрешили передачу любых возникающих исключений обратно JSP-странице, которая вызывает конструктор или методы класса Guest-
142 Глава 3 DataBean. Это дает возможность продемонстрировать применение страниц ошибок JSP. Если JSP-страница выполняет операцию, которая возбуждает исключение, JSP-страница может осуществить включение скриптлетов, перехватывающих исключение и обрабатывающих его. Исключения, которые не были перехвачены, могут быть отправлены для обработки в страницу ошибок JSP. JSP-страница guestBookLogin.jsp (рис. 3.22) представляет собой модифицированную версию JSP-страницы forwardl.jsp (рис. 3.11) и выводит форму, в которой пользователи могут ввести свое имя, фамилию и адрес e-mail. Когда пользователь отправляет форму, страница guestBookLogin.jsp запрашивается снова, чтобы убедиться, что все значения данных были введен и. Если нет, страница guestBookLogin.jsp еще раз выводит форму, чтобы пользователь мог заполнить пустое поле (поля). Если пользователь заполнил все три поля, JSP-страница guestBook- Login.jsp переадресовывает запрос к странице guestBook View.jsp, которая отображает содержимое гостевой книги. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<!-- Рис. 3.22. guestBookLogin.jsp —> 6 7 <%-- параметры страницы --%> 8 <%@ page errorPage = "guestBookErrorPage.jsp" %> 9 10 <%-- компоненты JavaBeans используемые этой JSP-страницей --%> 11 <jsp:useBean id = "guest" scope = "page" 12 class = "com.deitel.advjhtpl.jsp.beans.GuestBean" /> 13 <jsp:useBean id = "guestData" scope = "request" 14 class = "com.deitel.advjhtpl.Jsp.beans.GuestDataBean" /> 15 16 <html xmlns = "http://www.w3.org/1999/xhtral"> 17 18 <head> 19 <title>Guest Book Login</title> 20 21 <style type = "text/css"> 22 body ( 23 font-family: tahoma, helvetica, arial, sans-serif; 24 ) 25 26 table, tr, td { 27 font-size: . 9em; 2 8 border: 3px groove; 29 padding: 5px; 30 background-color: fdddddd,- 31 ) 32 </style> 33 </head> 34 35 <body> 36 <jsp:setProperty name = "guest" property = "*" /> 37 38 <% // начало скриптлета 39 40 if ( guest.getFirstHame() = null || 41 guest.getLastMame() = null ||
JavaServer Pages (JSP) 143 42 guest.getEmail() == null ) { 43 44 %><%-- закрытие скриптлета для вставки данных с неизменной структурой —%> 45 46 <form method = "post" action = "guestBooJcLogin. jsp":> 47 <p>Enter your first name, last name and email 46 address to register in our guest book.</p> 49 50 <table> 51 <tr> 52 <td>First name</td> 53 54 <td> 55 <input type ■ "text" name = "firstName" /> 56 </td> 57 </tr> 58 59 <tr> 60 <td>Last name</td> 61 62 <td> 63 <input type = "text" name = "lastName" /> 64 </td> 65 </tr> 66 67 <tr> 68 <td>Email</td> 69 70 <td> 71 <input type - "text" name = "email" /> 72 </td> 73 </tr> 74 75 <tr> 7 6 <td colspan = "2"> 77 <input type ■ "submit" 78 value ■ "Submit" /> 79 </td> 80 </tr> 81 </table> 82 </form> 83 84 <% // продолжение скриптлета 85 86 } // конец блока if 87 else { 88 guestData.addGuest( guest ); 89 90 %> <%— закрытие скриптлета для вставки действия jsp:forward —%> 91 92 <%— переход к отображению содержимого гостевой книги —%> 93 <jsp:forward page = "guestBookView.jsp" /> 94 95 <% // продолжение скриптлета 96
144 Глава 3 97 } // конец блока else 98 99 %> <%— конея скриптлета —%> 100 </body> 101 102 </html> Рис. 3.22. JSP-стрзница guestBookLogin.jsp дает возможность пользователю отправлять имя, фамилию и адрес e-mail для помещения их в гостевую книгу Строка 8 в листинге кода страницы guestBookLogin.jsp содержит директиву page, которая определяет информацию, глобально доступную на JSP-странице. Директивы заключаются в ограничители <%@ и %>. В данном случае атрибут ег- rorPage директивы page указывает на страницу guestBookErrorPage.jsp (рис. 3.24), т.е. все не перехваченные исключения пересылаются для обработки странице guestBookErrorPage.jsp. Полное описание директивы page дается в разделе 3.7. В строках 11-14 определяются два действия <jsp:useBean>. В строках 11-12 создается экземпляр класса GuestBean с именем guest. Этот компонент имеет етраничную область видимости (page): он пригоден для использования только на этой странице. В строках 11-14 создается экземпляр класса GuestDataBean с именем guestData. Этот компонент имеет область видимости в пределах запроса (request) и пригоден для использования на этой странице и на любых других страницах, которые содействуют обработке одного клиентского запроса. Таким образом, если страница guestBookLogin.jsp переадресует запрос странице guestBook- Vie-w.jsp, компонент GuestDataBean по-прежнему остается доступным для использовании на странице guestBookYiew.jsp. В строке 36 демонстрируется задание для свойств компонента GuestBean с именем guest значений, получаемых из параметров запроса. Элементы input в строках 55, 63 и 71 посят те же имена, что и свойства объекта GuestBean. В этой связи используется способность действия <jsp:SetPropcrty> отождествлять параметры запроса со свойствами путем задания значения "*" для атрибута property. Можно также индивидуально задавать свойства с помощью следующих строк: <jsp:setProperty name = "guest" property = "firstName" paiam = "firstName" /> <jsp:setProperty name = "guest" property = "lastName" param = "lastName" /> <jsp:setProperty name = "guest" property = "email" param ~ "email" /> Если параметры запроса носят имена, которые отличаются от имен свойств компонента GuestBean, атрибут param в каждом из приведенных выше действий <jsp: SetProperty> может быть заменен на имя соответствующего параметра запроса. JSP-страница guestBoofeView.jsp (рис. 3.23) формирует XHTML-документ, содержащий записи гостевой книги в табличном формате. Директивы page определены в строках 8-10. В строке 8 страница guestBookErrorPage.jsp задается в качестве страницы обработки ошибок для данной JSP страницы. В строках 9-10 мы встречаем новый для нас атрибут import директивы page. Атрибут import позволяет программистам задавать классы и пакеты Java, которые используются в контексте JSP-страницы. В строке 9 задается, что в этой JSP-странице используются классы из пакета java.util, а в строке 10 задается, что используются также классы из пакета com.deitel.advjhtpl.jsp.bean.
JavaServer Pages (JSP) 145 В строках 13-14 задается действие <jsp:useBe»n>, которое получает ссылку на объект GuestDataBean. Если объект GuestDataBean уже существует, действие возвращает ссылку на имеющийся объект. В противном случае действие создает объект GuestDataBean для использования его этой JSP-страницей. В строках 50-59 определен скриптлет, который получает список гостей из объекта GuestDataBean, и начинается цикл вывода записей. Б строках 61-70 совместно используются текст с неизменной структурой и выражения JSP для создания строк в таблице данных гостевой книги, которая будет отображаться клиенту. Скриптлет в строках 72-76 завершает цикл. 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<(— Рис. 3.23. guestBookView.jsp —> 6 7 <%-- параметры страницы —%> 8 <%@ page errorPage = "guestBookErrorPage.jsp" %> 9 <%@ page import = "ja.va.util.*" %> 10 <%@ page import = "com.deite1.advjhtpl.jsp.beans.*" %> 11 12 <%— компонент GuestDataBean для получения списка гостей --%> 13 <jsp:useBean id = "guestData" scope = "request" 14 class = "com.deitel.advjhtpl.jsp.beans.GuestDataBean" /> 15 16 <n.tiul Ktilns = "http://www.w3.org/1959/xht»l"> 17 18 <head> 19 <title>Guest List</title> 20 21 <style type = "text/css"> 22 body { 23 font-family: tahoma, helvetica, arial, sans-serif; 24 } 25 26 table, tr, td, th { 27 text-align: center; 28 font-size: . 9em; 29 border: 3px groove; 30 padding: 5px; 31 background-color: #dddddd; 32 } 33 </style> 34 </head> 35 3 6 <body> 37 <p style = "font-size: 2em;"XSuest List</p> 3B 39 <table> 40 <thead> 41 <tr> 42 <th style = "width: lOOpx;">Last name</th> 43 <th style = "width: lQQpx; ">First name</th> 44 <th style = "width: 200px; ">EroaiK/th> 45 </tr> 46 </thead> 47
146 Глава 3 48 49 50 51 52 53 54 55 56 57 56 59 60 61 62 63 64 65 66 67 6S 69 70 71 72 73 74 75 76 77 78 79 80 81 82 < <tbody> <% // начало скриптлета List guestList = guestData.getGuestList[); Iterator guestListlterator = guestList.iterator(); GuestBean guest; while ( guestListlterator.hasNext{) ) { guest = ( GuestBean ) guestListlterator.next[); %> <%— закрытие скриптлета для вставки данных с неизменной структурой —%> <tr> <tdx%= guest. getLastMameO %x/td> <tdX%= guest.getFirstName() %X/td> <td> <a href = "ntailto:<%= guest.getEmail () %>"> <%= guest.getEmailO %X/a> </td> </tr> <% // продолжение скриптлета } // конец блоха while %> <%-- конец скриптлета --%> </tbody> /table> </body> c/html> Рис. 3.23. JSP-страиица guestBookView.jsp отображает содержимое гостевой книга JSP-страница (рис. 3.24) выводит XHTML-доку мент, содержащий сообщение об ошибке, в зависимости от типа исключения, которое привело к вызову страницы ошибок. В строках 8-10 определено несколько директив pages. В строке 8 исполь- % зуется атрибут isErrorPage директивы page. Задание для этого атрибута значения true приводит к созданию страницы сообщений об ошибках JSP и разрешает доступа к неявному объекту exception JSP, который ссылается на объект исключения, указывающий на наличие проблемы. Типичная ошибка программирования 3.8 Неявный объект JSP exception может использоваться только страницами обработки ошибок. Использование этого объекта другими JSP-страни- цами приводит к возникновению ошибки на этапе трансляции. В строках 29-46 определены скриптлеты, которые выявляют тип имевшего место исключения. Здесь нее начинается вывод соответствующего сообщения об ошибке с помощью данных с неизменной структурой. Фактическое сообщение об ошибке для исключения выводится в строке 56.
JavaServer Pages (JSP) 147 1 <?xml version = "1.0"?> 2 <"DOCTYPE html PUBLIC "-//W3C//DTD XHTHL 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtnai/DTD/athtnill-strict.dtd"> 4 5 <!— Рис. Э.24. guestBookErrorPage.jsp —> 6 7 <%-- параметры страницы —%> 8 <%@ page isErrorPage = "true" %> 9 <%@ page import = "java.util.*" %> 10 <%@ page import = "java.sql.*" %> 11 12 <html xmlns = "http://www.w3.org/1999/xhtml"> 13 14 <head> 15 <title>Error!</title> 16 17 <style type = "text/css"> 18 .bigRed { 19 font-size: 2em; 20 color: red; 21 font-weight: bold; 22 } 23 </style> 24 </hea.d> 25 2 6 <body> 27 <p class = "bigRed"> 28 29 <% // схриптлет для определения типа исключения 30 //и вывода начала сообщения об ошибке 31 if ( exception instanceof SQLException ) 32 %> 33 34 An SQLException 35 36 <% 37 else if ( exception instanceof ClassHotFoundException ) 38 %> 39 40 A ClassNotFoundException 41 42 <% 43 else 44 %> 45 46 An exception 47 48 <%— закрытие скриптлета для вставки данных с неизменной структурой —%> 49 50 <%-- продолжение вывода сообщения об ошибке --%> 51 occurred while interacting with the guestbook database. 52 </p> 53 54 <p class = "bigRed"> 55 The error message was:<br /> 56 <%= exception. getMessageO %>
148 Глава 3 57 58 59 60 €1 62 </html> </р> <р class = "bigRed">Please try again later</p> </body> Рис. 3.24. JSP-страница guest BookErrorPage.jsp реагирует на исключения, возникающие на страницах guestBookLogin.jsp и guestBookView.jsp На рис. 3.25 показаны образцы взаимодействий между пользователем и JSP- страницами в приложении для гостевой книги. В первых двух случаях (два верхних ряда копий экранов) разные пользователи вводят свое имя, фамилию и адрес e-mail. Каждый раз текущее содержимое гостевой книги возвращается и отображается пользователю. В третьем случае пользователь указал адрес e-mailj который уже имеется в базе данных. Адрес e-mail является первичным ключом таблицы guest базы данных guestbook, поэтому его значения должны быть уникальными. Таким образом, база данных препятствует вставке новой записи, и возникает исключение. Исключение пересылается для обработки странице guest BookErrorPage.jsp, результат которого показан на последней копии экрана. ■AdfrBg^H^JftynlTegaMllrtvttBlJfep.'gigattMUc^Ha! ^j ^^ Enter you first name, last name end email adoress bo register In our guest bock. Ftotnmh 1 Letrome bn* Isuhmjj jPaul Deuel ; |dei[el@dertet.cbm 1 ёд&и" Ф^ВШйкй?- Ш: ■■, Т±.1ШШ^1*№1к,^..,.^ Г~ , Guest List ■Ц | Let na™ I! First rune I 1_1;Ш: Щ::"r&Wj EmsJ |- "-" .tSje«j6iBI,cora:.'- Jl ш~]щщ^ш^Е^Ш!}£~^^^^~ \&--** *» Htttoft>»: Fonerjitf " 9np \fWw\<Qm&-.flkKilbrt:9aW*drt&ilwt^*aQdij^& ^1 ^&> Enter your first name, last name and email iridess to register in our guest bock. 73 Festive*?' asrj«n»|'|SaiHfy_ j ?ean® bug ?t»ug .oom Jj *]ч Tr^SiiST d frtdiwgS |^3 http^JcxralimrBDBDfMrv^lfltpfguwtBQQliloqp )tp ^[ ^d' лзет: 'ЙЭ^^-ГйЖ* Guest List |.-w.ttii%; f Щ»уп*|?} | мм 1|--№-1 F «^"it-Jfwv-.] ...bnal j — j. sa*i«*uaaiio_nw i| IE" ^ШЗШШКЩШЙ Рис. 3.25. Образцы выходных результатов для JSP-страницы гостевой книги (часть 1)
JavaServer Pages (JSP) 149 j^^B*Jta*»toUn*i«ffl*™iBi 2i &^, i] fit Gdt 4e» fn&ijn leif J*t*^|-7-. .::Щ?&Г'*-;:'! 2! Enter vor first ГкЭте, last name 2nd emal address to register in :>jr g№« bxk 13 ffttrunw l£$tn*re Erred (jftSWii: Ji^-J, _—__—___ j |Ha>vey j i \bv> J |dei:sl@dEi'elcam j i J iQiSj^ir ~^—:. таэйщгщяа^мг-- -^ «в : £*'j; №• . .fsvtrte ■ lnds неф »ЭД а ■a' la jaJ&mj |^http:/^ahcU:308a/advrepl^p;gu№cEoobLixi jsp !3-.«!й An SQLException occurred while interacting with the «liestbook database. The error message was: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint. Please try again later ГЗ ©в "WSS^SSF Рис. 3.25. Образцы выходных результатов для JSP-страницы гостевой книги (часть 2) Чтобы протестировать гостевую книгу в Tomcat, скопируйте файлы guestBoofe- L о gin. jsp, guestBookView.jsp и guestBookErrorPage.jsp в каталог jsp, созданный в разделе 3.3. Скопируйте файлы GucstBean.class и GuestDataBcan.class в каталог WEB-INF\classes корень advjhtpl Web-приложения в Tomcat, [Замечание. Этот пример будет работать только в том случае, если в каталоге classes определена надлежащая структура каталогов для классов GuestBean и GuestDataBcan. Эти классы определены в пакете com.dcitcl.advjhtpl.jsp.beans.] Откройте ваш Web- браузер и введите следующий URL, чтобы протестировать JSP-страницу guest- BookLogin.jsp: http://localhost:80B0/advjhtpl/jsp/guestBookLogin.jsp 3.7. Директивы Директивы представляют собой сообщения контейнеру JSP, посредством которых программист может задавать параметры и настройки для страницы (например, задавать страницу обработки ошибок), включать содержимое из других ресурсов и задавать собственные библиотеки нестандартных тегов для их использования
150 Глава 3 в JSP-странице. Директивы (ограничиваемые символами <%@ и %>) обрабатываются на этапе трансляции. Таким образом, директивы не формируют выходных данных немедленно, поскольку они обрабатываются до того, как JSP-страница принимает какие-либо запросы. В таблице на рис. 3,26 описаны три типа директив. Эти директивы будут рассмотрены в следующих нескольких подразделах. 3.7.1. Директива page Директива page задает глобальные настройки для JSP-страницы в контейнере JSP. Может использоваться несколько директив page при условии, что при этом имеется только одно вхождение каждого из атрибутов. Единственным исключением из этого правила является атрибут import, который может многократно применяться для импортирования пакетов Java, используемых JSP-страницей. В таблице на рис. 3.27 представлены атрибуты директивы page. Директива раде include taglib _ Описание I Определяет параметры страницы для обработки их контейнером JSP. ; Заставляет контейнер JSP выполнять вставку содержимого другого ресурса на этапе трансляции. При трансляции JSP-страницы в сервлет и компиляции указываемый файл замещает директиву include и транслируется, как если бы он изначально являлся частью JSP-страницы. Дает возможность программистам включать свои собственные теги в виде библиотек тегов. Эти библиотеки могут использоваться для инкапсулирования функциональных возможностей и упрощения процесса создания кода для JSP-страницы. Рис. 3.26. Директивы JSP -*- Типичная ошибка программирования 3.9 Использование нескольких директив page, у которых один или более атрибутов совпадают, приводит к ошибке на этапе трансляции JSP-страницы. Типичная ошибка программирования 3.10 Использование директивы page с атрибутом или значением, которое не распознается, приводит к ошибке на этапе трансляции JSP-страницы. Атрибут language extends import _ Описание Язык сценария, используемый JSP-crpaht^e^ На данный момент единственным допустимым значением для этого атрибута является Java. Задает класс, котооому будет наследовать транслированная JSP-страница. В качестве значения этого атрибута должно задаваться полное имя пакета или класса. Задает список отделяемых запятыми имен классов и/или пакетов, которые будут использоваться в текущей JSP-cipdHHue. Если нзыком сценария является Java, список для импортирования по умолчанию имеет вид: java.lang.*, javax.servlet.*, javax.servlet.jsp.*. javax.servlet.http.* Если задано несколько свойств import, контейнер помещает имена пакетов в список.
avaServer Pages (JSP) 151 Атрибут session juffer autoFlush isThreadSafe info чггогРаде isErrorPage conatAntType Описание Задает, участвует ли страница а сеансе. Значением этого атрибута может быть true (участвует в сеансе - по умолчанию) или false (не участвует в сеансе). Если страница участвует в сеансе, неявный объект JSP session доступен для использования на странице. В противном случае объект session недоступен, и использование его в коде сцензрия приводит к ошибке на этапе трансляции. Задает размер буфера аызода, используемого неявным объектом out. Этот зтрибут может иметь значение попе при отсутствии буферизации, либо конкретное значение, например, 8 kb (размер буфера по умолчанию). Спецификация JSP указывает, что размер используемого буфера должен быть не меньше заданного размера. При установке значения true (значение по умолчанию) этот атрибут указывает, что буфер вывода, используемый с неявным объектом out. должен автоматически очищаться при заполнении буферз. При установке значения false в случае переполнения буфера возбуждается исключение. Для этого атрибута следует установить значение true, если для атрибута buffer задано значение попе. Определяет, обеспечивает ли страница безопасное выполнение потоков (thread safe). При задании значения true (по умолчанию) считается, что страница обеспечивает безопасность программных потоков и может обрабатывать несколько запросов одновременно. При задании значения false сервлет, который представляет страницу, реализует интерфейс java.iang.SingleThreadModel, ^ этой JSP-страницей может одновременно 1 обрабатываться только один запрос. Стандарт JSP допускает существование нескольких экземпляров J5P для JSP-страниц, которые не являются безопасными для выполнения потоков Это позволяет контейнеру более эффективно обрабатывать запросы Однако при этом не гарантируемся, что доступ к ресурсам, совместно используемым экземплярами J5P, будет осуществляться посредством безогасных программных потоков. Задает строку информации, описываощей страницу. Эта строка возвращается методом getServletlnfo сервлета, который представляет собой транслированную JSP-страницу. Этот метод может быть вызван посредством неявного объекта JSP page. Любые, не перехваченные на текущей странице исключения, отправляются дня обработки странице ошибок. Неявный объект exception страницы ошибок ссылается на изначально возникшее исключение. Определяет, яэляется ли текущая страница страницей обработки ошибок, которая будет вызываться в ответ на ошибку, имевшую место в другой странице. Если атрибут имеет значение true, создается неявный объект exception, который ссылается на изначально возникшее исключение. Если атрибут имеет значение false (no умолчанию), любое использование объекта exception на странице приводит к ошибке на этапе трансляции. Задает MtME-тип данных в ответе клиенту. По умолчанию используется Тип text/html. 'ис. 3.27. Атрибуты директивы page
152 Глава 3 Общая методическая рекомендация 3.11 Как указывается в разделе 2.7.1 спецификации JSP, атрибут extends «не следует использовать без тщательного учета всех факторов, поскольку он ограничивает возможность контейнера JSP предоставлять специализированные суперклассы, которые могут улучшить качество предоставляемого сервиса». Напомним, что класс Java может расширять только один другой класс. Если в JSP-странице задан явный суперкласс, контейнер JSP не сможет транслировать JSP-страницу в подкласс одного из собственных усовершенствованных классов контейнера сервлетов в приложении. ^ Типичная ошибка программирования 3.11 Использование неявного объекта JSP session в JSP-странице, для которой не задан атрибут session директивы page со значением true, приводит к ошибке на этапе трансляции, 3.7.2. Директива include Директива include включает содержимое другого ресурса один раз, на этапе трансляции JSP-страницы. Директива include имеет только один атрибут — file — который задает URL включаемой страницы. Разница между директивой include и действием <jsp:include> проявляется только в том случае, если включаемое содержимое изменяется. Например, если определение XHTML-документа изменяется после того, как оно было включено директивой include, при дальнейших вызовах JSP-страницы будет отображаться изначальное содержимое XHTML-доку мента, а не новое содержимое. В противоположность этому, действие <jsp:include> обрабатывается при каждом запросе этой JSP-странице. Следовательно, изменения включенного содержимого будут учтены при следующем запросе JSP-страницы, которая использует действие <jsp:include>. Общая методическая рекомендация 3,12 Спецификация JavaServer Pages 1,1 не предоставляет механизм для обновления текста, включенного на JSP-страницу с помощью директивы include. Версия 1.2 спецификации JSP предусматривает предоставление контейнером такого механизма, но сама спецификация напрямую его не предоставляет. JSP-страница includeDirective.jsp (рис. 3.28) повторно реализует JSP-страницу include.jsp (рис. 3.10) с помощью директив include. Чтобы протестировать JSP- страницу includeDirective.jsp в Tomcat, скопируйте файл includeDirective.jsp в каталог jsp, созданный в разделе 3.3. Откройте Web-браузер и введите следующий IJRL, чтобы протестировать JSP-страницу includeDirective.jsp: http://localhost:8080/advjhtpl/jsp/includeDirective.jsp 1 <?xiftl version = "1,0°?> 2 «C'DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3_org/TR/xhtmll/DTD/xhtroll-strict.dtd"> 4 5 <•— Рис. 3.28. includeDirective.jsp --> 6 7 <html xmlns = "http://www.w3.org/1999/xhtinl"> 8
JavaServer Pages (JSP) 153 9 <head> 10 <title>Oaing the include directive*:/title> 11 12 <style type = "text/css"> 13 body { 14 font-family: tahoma, helvetica, arial, sans-serif; 15 } 16 17 table, tr, td { 18 font-size: . 9em; 19 border: 3px groove; 20 padding: 5px; 21 background-color: #dddddd; 22 } 23 </style> 24 </head> 25 26 <body> 27 <table> 28 <tr> 29 <td style = "width: 160px; text-align: center"> 30 <img src = "imagea/logotiny.png" 31 width = "140" height = "93" 32 alt - "Deitel & Associates, Inc. Logo" /> 33 </td> 34 35 <td> 36 37 <%— включение документа banner.html в JSP-страницу --%> 38 <%@ include file = "banner.html" %> 39 40 </td> 41 </tr> 42 43 <tr> 44 <td style = "width: 160px"> 45 46 <%-- включение документа toe.html в эту JSP-страницу --%> 47 <%@ include file = "toe.html" %> 48 49 </td> 50 51 <td style = "vertical-align: top"> 52 53 <%-- включение документа clock2.jsp в эту JSP-страницу --%> 54 <%@ include file = "clock2.jsp" %> 55 56 </td> 57 </tr> 58 </table> 59 </body> 60 </html> Рис. 3.28, JSP-страница indudeDirective.jsp демонстрирует включение содержимого на этапе трансляции с помощью директивы include (часть 1)
154 Глава 3 Рис. 3.28. JSP-страница indudeDirective.jsp демонстрирует зключение содержимого на этапе трансляции с помощью директивы include (часть 2) 3.8. Библиотеки нестандартных тегов Ранее в этой главе мы выяснили, каким образом страницы JavaServer Pages облегчают доставку динамического Web-содержимого. Теперь мы перейдем к рассмотрению библиотек пользовательских (нестандартных) тегов JSP, которые предоставляют другой механизм для инкапсулирования сложных функциональных возможностей, используемых JSP-страницами. Библиотеки пользовательских тегов определяют один или несколько нестандартных тегов, которые программисты JSP-страниц могут использовать для создания динамического содержимого. Функциональные возможности этих нестандартных тегов определены в классах Java, которые реализуют интерфейс Tag (пакет javax.servlet.jsp.tagext), обычно путем расширения класса TagSupport или класса BodyTagSupport. Этот механизм позволяет программистам на Java создавать более сложные функциональные средства для дизайнеров Web-страниц, не знакомых с программированием на Java. Ранее мы познакомились с применением действия <jsp:useBean> и компонентов JavaBeans для встраивания в JSP-страницу сложных, инкапсулированных функциональных возможностей. Во многих случаях действие <jsp:useBean> и компоненты JavaBeans могут выполнять те же задачи, что и нестандартные теги. Однако применение действия <jsp:useBean> и компонентов JavaBeans имеет недостатки: компоненты JavaBeans не могут манипулировать содержимым JSP-стра- ницы, а дизайнеры Web-страниц должны обладать определенным знанием языка Java, чтобы использовать в странице компоненты JavaBeans. В случае применения нестандартных тегов дизайнерам Web-страниц не обязательно знать Java. В этом разделе будут рассмотрены три примера нестандартных тегов. Каждый из тегов является составной частью одной библиотеки тегов, которую мы назвали advjhtpl. JSP-страница осуществляет включение библиотеки нестандартных тегов с помощью директивы taglib. Атрибуты директивы taglib представлены в таблице на рис. 3.29.
JavaServer Pages (JSP) 155 Атрибут uri tagPrefix Описание Задает относительный или абсолютный URI дескриптора библиотеки тегов. Задает обязательный префикс, который отличает нестандартные (пользовательские) теги от стандартных (встроенных) тегов. Зарезервированными являются имена префиксов jsp, jspx, Java, javax. servlet, sun и sunw. j Рис. 3.29. Атрибуты директивы taglib В каждом из примеров в этом разделе используется директива taglib. Существует несколько типов нестандартных тегов, которые имеют различный уровень сложности. Мы продемонстрируем применение простых тегов, простых тегов с атрибутами и тегов, которые способны обрабатывать элементы в собственном теле. Чтобы получить полную информацию о библиотеках нестандартных тегов, обратитесь к ресурсам, приведенным в разделе 3.9. 3.8.1. Простой нестандартный тег Рассмотрим первый пример, который реализует простой нестандартный тег, вставляющий в JSP-страницу строку "Welcome to JSP Tag Libraries". При реализации нестандартных тегов необходимо определить класс обработчика тега для каждого из тегов, который реализует функциональные возможности тега; дескриптор библиотеки тега, который предоставляет контейнеру JSP информацию о библиотеке тегов и содержащихся в ней нестандартных тегах; а также JSP-страницу, которая использует нестандартный тег. Первый пример использования нестандартного тега представлен на рис. 3.30 (customTag Welcome .jsp). В конце этого раздела будет рассказано, как настроить этот пример для тестирования его в Tomcat. 1 <?xml version = "1.0"?> 2 «C'DOCTYPE html POBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5 <!— Рис. 3.30. customTagWelcome.jsp --> 6 <!— JSP-страниыа, которая использует нестандартный тег для вывода содержимого. --> 7 8 <%— директива taglib —%> 9 <%@ taglib uri = "advjhtpl-taglib.tld" prefix = "advjhtpl" %> 10 11 <html xmlns = "http://www.w3.org/1999/xhtna"> 12 13 <head> 14 <title>Simple Custom Tag Example</title> 15 </head> 16 17 <body> 18 <p>Tne following text demonstrates a custom tag:</p> 19 <hl> 20 <advjhtpl:welcome /> 21 </hl> 22 </body> 23 24 </html> Рис. 3.30. JSP-страница aistomTagWelcome.jsp использует простой нестандартный тег
156 Глава 3 ьмямдишмшимиииЛйШ Fie ЕЛ Vim. gafj^S^C1 T- ' '^ *Г:* Step.. 1|Й|ЙИф:;|1и:а1Ь1^:а(ет;^^№1/]»/<^от1"11*=1сыпе.рр 3 ^?_J The following :ea demist: ales a custom tag: Welcome to JSP Tag Libraries! ъ __ d [d В»'. jswrpy Рис. 3.30. JSP-страница customTagWekome.jsp использует простой нестандартный тег (часть 2) Директива taglib в строке 9 дает возможность JSP-странице использовать теги из нашей библиотеки тегов. Директива задает uri файла дескриптора библиотеки (advjhtpl-taglib.tld, рис. 3.32), который предоставляет информацию о нашей библиотеке тегов контейнеру JSP, и префикс prefix для каждого тега (advjhtpl). Программисты JSP используют префикс библиотеки тегов при обращении к тегам, содержащимся в определенной библиотеке. В строке 20 используется нестандартный тег с именем welcome для вставки текста в JSP-страницу. Обратите внимание, что имя тега предваряется префиксом advjhtpl. Это дает возможность контейнеру JSP интерпретировать назначение тега и вызывать соответствующий обработчик тега. Также имейте в виду, что строка 20 может быть записана с указанием открывающего и закрывающего тегов следующим образом: <advjhtpl:welcome> </advjhtpl:welcome> На рис. 3.31 представлено определение класса WeleomeTagHandler: обработчика тега, который реализует функциональные возможности нашего нестандартного тега welcome. Каждый обработчик тегов должен реализовывать интерфейс Tag, определяющий методы, которые контейнер JSP вызывает для встраивания функциональных возможностей, описываемых тегом, в JSP-страницу. Большинство классов обработчиков тегов реализуют интерфейс Tag путем расширения либо класса TagSupport, либо класса Body TagSupport. Общая методическая рекомендация 3.13 Классы, которые содержат определения обработчиков нестандартных тегов, должны реализовывать интерфейс Tag из пакета javax.serv- let.jsp.tagext. Общая методическая рекомендация 3.14 Класс обработчика нестандартных тегов должен расширять класс Tag- Support, если тело тега игнорируется, или если во время обработки не стандартного тега осуществляется лишь вывод данных. Общая методическая рекомендация 3.15 Класс обработчика нестандартных тегов должен расширять класс BodyTagSupport, если обработчик взаимодействует с содержимым тела тега. Общая методическая рекомендация 3.16 Обработчики нестандартных тегов должны быть определены в пакетах Java.
JavaServer Pages (JSP) 157 Класс WelcomeTagHandler реализует интерфейс Tag, расширяя класс TagSup- port (оба из пакета Java.servlet.jsp.tagext). Наиболее важными методами интерфейса Tag являются методы doStartTag и doEndTag. Контейнер JSP вызывает эти методы, когда обнаруживает начальный (открывающий) нестандартный тег и конечный (закрывающий) нестандартный тег, соответственно. Эти методы возбуждают исключения JspException, если при обработке нестандартного тега возникают проблемы. 1 // Рис. 3.31. WelcomeTagHandler.Java 2 // Нестандартный обработчик тегов, обрабатывающий простой тег, 3 package com.deitel, advjhtpl. jsp. taglibrary ,- A 5 // Набор базовых пакетов Java 6 import, j ava.io.*; 7 8 // Пакеты расширений Java 9 import javax.servlet.jsp.*; 10 import javax.servlet.jsp.tagext.*; 11 12 public class WelcomeTagHandler extends TagSupport ( 13 14 // Метод/ вызываемый, чтобы начать обработку тега 15 public int doStartTag() throws JspException 16 { П II попытка обработки тега 18 try 1 19 // получение объекта JspWriter для вывода содержимого 20 JspWriter out = pageContexfc.getOut(); 21 22 // вывод содержимого 23 out.print{ "Welcome to JSP Tag Libraries!" ); 24 } 25 26 // повторное возбуждение исключения IOException контейнеру JSP как исключения JspException 27 catch( IOException ioException ) { 28 throw new JspException( ioException.getMessage{) ); 29 } 30 31 return SKIP_BODY; // игнорировать тело тега 32 } 33 } .. Рис. 3.31. Обработчик нестандартного тега WelcomeTagHandler Общая методическая рекомендация 3.17 Если в классе обработчика нестандартного тега возбуждаются исключения, отличные от JspException, они должны перехватываться и обрабатываться. Если такие исключения препятствуют надлежащей обработке тега, они должны быть возбуждены повторно как исключения типа JspException. В этом примере класс WelcomeTagHandler переопределяет метод doStartTag, чтобы вывести текст, который становится частью ответа, посылаемого JSP-страницей. В строке 20 используется объект pageContext обработчика нестандартного
158 Глава 3 тега (унаследованный от класса TagSupport) для получения объекта JSP JspWriter, который метод doStartTag использует для вывода текста. В строке 23 осуществляется вывода строки с использованием объекта Jsp Writer. В строке 31 возвращается статическая целочисленная константа SKIP_BODY (определенная в интерфейсе Tag) для указания, что контейнер JSP должен игнорировать любой текст или другие элементы, которые присутствуют в теле тега. Чтобы включить тело содержимого в ответ, задайте статическую целочисленную константу EVAL_BO- DY__INCLUDE в качестве возвращаемого значения. В этом примере при обнаружении контейнером JSP конечного тега какой-либо обработки не требуется, поэтому мы не переопределяем метод doEndTag. На рис. 3.32 представлено определение файла дескриптора библиотеки нестандартных тегов. Этот XML-документ содержит информацию, необходимую для контейнера JSP, например, номер версии библиотеки тегов (элемент tlibversion), номер версии JSP (элемепт jspversion), сведения о библиотеке (элемент info) и сведения о тегах в библиотеке (по одному элементу tag для каждого тега). В этом дескрипторе библиотеки тегов элемент tag в строках 18-30 определяет наш нестандартный тег welcome. В строке 19 задается имя пате тега, используемое программистами JSP для доступа к нестандартным функциональным возможностям с JSP-страниды. В строках 21-23 задается элемент tag class для класса обработчика нестандартного тега. Этот элемент связывает имя тега с определенным классом обработчика тега. Элемент bodycontext (строка 25) указывает, что наш нестандартный тег имеет пустое (empty) тело. Этот элемент также может иметь значение tagdependent или JSP. В строках 27-29 задается информацию о теге с помощью элемента info. [Замечание. По мере необходимости мы будем знакомиться с другими элементами дескриптора библиотеки тегов. Полное описание дескриптора библиотеки тегов имеется в спецификации JavaRerver Pages 1.1, которую можно загрузить по адресу Java.sun.com/products/jsp/downtoad.htmt.] Общая методическая рекомендация 3.18 Класс обработчика нестандартною тега должен задаваться в элементе tagclass дескриптора библиотеки тегов с указанием полного имени пакета. 1 <?xml version = "1.0" encoding = "ISO-8859-1" ?> 2<!DOCTYEE taglib PUBLIC 3 "-//Sun Microsystems, Inc.//DTD JSE Tag Library 1.1//EN" 4 "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_l l.dtd"> 5 6 <!— дескриптор библиотеки тегов —> 7 8 <taglib> 9 <tlibversion>l.0</tlibversion> 10 <jspversion>l.l</jspversion> 11 <shortname>advjhtpl</shortncune> 12 13 <info> 14 A simple tab library for the examples 15 </info> 16 1? <!— Простой тег, который осуществляет вывод содержимого — 18 <tag> 19 <name>welcome</name> 20 21 <tagclass>
JavaServer Pages (JSP) 159 22 com.deitel.advjhtpl. jsp.taqLibrary.WelcomeTagBandlei: 23 </tagclass> 24 25 <bodycontent>empty</bodycontent> 26 27 <in£o> 28 Inserts content welcoming user to tag libraries 29 </info> 30 </tag> 31 </taglib> ___^_ Рис. 3,32. Файл advjhtpl-taglib.tld дескриптора библиотеки нестандартных тегов Чтобы протестировать JSP-страницу customTagWelcome.jsp в Tomcat, скопируйте файлы customTagWelcome.jsp и advjhtpl-taglib.tld в каталог jsp, созданный в разделе 3.3. Скопируйте файл WelcomeTagHandler.class в каталог WEB-INF\ classes Web-приложения advjhtpl на сервере Tomcat. [Замечание. Класс Welcome- TagHandler должен быть размещен в каталоге classes с соблюдением надлежащей структуры каталогов пакета. Класс WelcomeTagHandler определен в пакете com.deitel. advjhtpl .jsp.taglibrary-] Откройте Web-браузер и введите следующий URL, чтобы протестировать JSP-страницу customTagWelcome.jsp: http://localhost:8080/advjhtpl/jsp/customTagWelcome.jsp 3.8.2. Нестандартный тег с атрибутами Многие элементы XHTML И JSP используют атрибуты для настройки своих функциональных возможностей. Например, элемент XHTML может иметь атрибут style, который задает, каким образом элемент должен быть отформатирован в клиентском Web-браузере. Аналогичным образом, элементы действий JSP имеют атрибуты, которые помогают настроить их поведения на JSP-странице. Следующий пример демонстрирует, как задавать атрибуты для нестандартных тегов, JSP-страница, представленная на рис. 3.33 (customTagAttribute.jsp), схожа со страницей, представленной на рис. 3.30. В этом примере для вставки текста в JSP-страницу используется новый тег с именем welcome2, который настраивается в зависимости от значения атрибута firstName. На копии экрана показаны результаты действия тега weIcome2 в строках 20 и 30. Тег в строке 20 задает значение "Paul" для атрибута iLrstName, В строках 26-28 определен скриптлет, который получает значение параметра запроса нате и присваивает его строковой ссылке name. В строке 30 ссылка name в выражении JSP используется в качестве значения для атрибута firstName. На образце копии экрана эта JSP-страница была вызвана с помощью следующего TJRL: http://localhost:8080/advjhtpl/jsp/ customTagAttribute.jsp?firstName=Sean 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTO XHTML 1.0 Strict//EN" 3 "http; //www.w3.org/TR/xhtrall/DTD/xhtJiai-strict.dtd"> 4 5 <!— Рис. З.ЭЗ. customTagAttribute.jsp —> 6<!-- JSP-страница, нспольэугацая нестандартный тег для вывода содержимого. --> 7 8 <%— директива taglib --%> 9 <%Й taglib uri = "advjhtpl-taglib.tld" prefix = "advjhtpl" %>
160 Глава 3 10 11 <html xmlns = "http://www.w3.org/1999/xhtira"> 12 13 <head> 14 <title>Specifying Custom Tag Attributes</title> 15 </head> 16 17 <body> 18 <p>Demonstrating an attribute with a string value</p> 19 <hl> 20 <adv]htpl:welcome2 firstName = "Paul" /> 21 </hl> 22 23 <p>Demonstrating an attribute with an expression value</p> 24 <hl> 25 <%— скриптлет для получения параметра паше запроса —%> 26 <% 27 String name = request.getParameter{ "name" ); 2S %> 29 30 <advjhtpl:welcome2 firetNajne = "<%= name i>" /> 31 </hl> 32 </body> 33 34 </html> Рис. 3.33. Задание атрибутов для нестандартного тега При определении обработчика нестандартного тега для тега с атрибутами необходимо предоставить методы, которые дают возможность контейнеру JSP задавать значения атрибутов в обработчике тега. Методы, которые манипулируют атрибутами, следуют тому же соглашению о присвоении имен методам set- и get-, что и свойства компонентов JavaBeans. Так, значение нестандартного атрибута first- Name задается с помощью метода setFirstName. Аналогично, для получения значения атрибута firstName использовался бы метод getFirstName (в данном примере этот метод не определяется). Класс \Velcome2TagHandler (рис. 3.34) определяет переменную firstName (сгрока 13) и соответствующий метод set setFirstName (строки 37-40). Когда контейнер JSP обнаруживает на JSP-странипе тег welco-
JavaServer Pages (JSP) 161 ше2, он создает новый объект Welcome2TagHandler для обработки тега и задания атрибутом тегов. Далее, контейнер вызывает метод doStartTag (строки 16-34) для выполнения обработки нестандартного тега. В строках 24-25 значение атрибута firstName используется как часть выводимого нестандартным тегом текста. 1 // Рис. 3.34. Welcome2TagHandler.java 2 // Нестандартный обработчик тегов, обрабатывающий простой тег. 3 package com.deitel.advjhtpl.jsp.taglibrary; 4 5 // Набор базовых пакетов Java 6 import 3 ava.io.*; 7 8 // Пакеты расширений Java 9 import javax.servlet.jsp.*; 10 import javax.servlet.jap.tagext.*; 11 12 public class Helcome2TagHandler extends TagSupport ( 13 private String firstName = ""; 14 15 // Метод, вызываемый, чтобы качать обработку тега 16 public int doStartTagО tbrows JspException 17 { 16 // попытка обработки тега 19 try { 20 // получение объекта JspWriter для вывода содержимого 21 JspWriter out = pageContext.getOut(); 22 23 // вывод содержимого 24 out.print( "Hello " + firstName + 25 ", <br />Welcome to JSP Tag Libraries!" ); 26 } 27 28 // повторное возбуждение исключения IOException контейнеру JSP как исключения JspException 29 eaten( IOException ioException ) ( 30 throw new JspException( ioException.getMessage() ); 31 > 32 33 return SKIP_BODY; // игнорировать тело тега 34 } 35 36 // задание в качестве значения атрибута firstName имени пользователя 37 public void setFirstName( String username ) 38 i 39 firstName = username; 40 } 41 1 ___ Рис. 3.34. Обработчик нестандартного тега Welcome2TagHandler для тега с атрибутами До того, как тег welcome2 будет использован на JSP-странице, необходимо уведомить контейнер JSP о добавлении его в библиотеку тегов. Чтобы сделать это, добавьте элемент tag, представленный на рис. 3.35, в качестве дочернего элемента в элемент taglib в дескрипторе библиотеки тегов advjhtpl-tagiib.tld. Как и в предыдущем примере, элемент tag содержит элементы name, tagclass, bodycontent и info.
162 Глава 3 В строках 16-20 используется элемент attribute для задания характеристик атрибутов тега. Каждый атрибут должен иметь отдельный элемент атрибута, который содержит элементы name, required и rtexprvalue. Элемент паше (строка 17) задает имя атрибута. Элемент required определяет, является ли атрибут обязательным (true) или необязательным (false). Элемент rtexprvalue определяет, может ли значение атрибута быть результатом вычисления выражения JSP в процессе выполнения (true), или же оно должно представлять собой строковый литерал (false). Чтобы протестировать JSP-страницу customTagAttribute.jsp в Tomcat, скопируйте файл customTagAttribute.jsp и модифицированный файл advjhtpl- taglib.tld в каталог jsp, созданный в разделе 3.3. Скопируйте файл Welcome2Tag- Handler.class в каталог WEB-INF\classes приложения advjhtpl на сервере Tomcat. [Замечание. Этот пример будет работать только в том случае, если в каталоге classes определена надлежащая структура каталогов пакета для класса WeIcome2TagHandler.] Откройте Web-бряузер и введите следующий URL, чтобы протестировать JSP-страницу customTagAttribute.jsp: http://localbost:8080/advjbtpl/jsp customTagAttribute.jsp?firstNaroe=Sean Текст ?firstName=Sean в приведенном выше URL задает значение для параметра запроса name, который используется нестандартным тегом weleome2 в строке 30 листинга, представленного на рис. 3.33. 1 <!-- Тег с ач?рибутом --> 2 <tag> 3 <name>welcome2</name> 4 5 <tagclass> 6 com.deitel.advjhtpl.jsp.taglibrary.Welcome2TagHandler 7 </t&gclass> 6 9 <bodycontent>eiiipty</bodycontent > 10 11 <info> 12 Inserts content welcoming user to tag libraries. Uses 13 attribute "name" to insert the user's name. 14 </info> 15 16 <attri±>ute> 17 <nama>firstname</name> 18 <required>tr-ue</required> 19 <rtexprvalue>true</rtexprvalue> 20 </attribute> 21 <tag> „___^_ __„ Рис. 3.35. Элемент tag для нестандартного тега welcome 3.8.3. Обработка тела нестандартного тега Нестандартные теги особенно хороши для обработки содержимого элемента body (тела документа). Для взаимодействий нестандартного тега с элементом тела документа необходимы дополнительные методы. Эти методы определены в классе BodyTagSupport. В следующем примере будет повторно реализована JSP-страница guestBookView.jsp (рис. 3.23), в которой вместо обработки компонента JavaBeans, выполняемой JSP-страницей, используется нестандартный тег guest list.
JavaServer Pages (JSP) 163 JSP-страница, представленная на рис. 3.36 (custoraTagBody.jsp) использует нестандартный тег guestiist в строках 41-52, Обратите внимание, что выражения JSP в теле элемента guestiist используют имена переменных, которые не определены на JSP-странице, Эти переменные определяются обработчиком нестандартного тега при обнаружении нестандартного тега. Обработчик нестандартного тега помещает переменные в контейнер PageContext JSP-страницы, чтобы цеременные могли быть использованы страницей. Хотя в JSP-странице не определен цикл повторного вычисления, обработчик нестандартного тега по определению обрабатывает все записи для гостей в базе данных guestbook. Это действие приводит к созданию строки таблицы в итоговой Web-странице для каждого гостя в базе данных. 1 <?xml version = "1.0"?> 2 -ODOCTiFE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 5<!-- customTagBody.jsp --> 6 7 <%-- директива taglib --%> 8 <%@ taglib uri = "advjhtpl-taglib.fcld" prefix = "advjhtpl" %> 9 10 <html xmlns = "http://www.w3.org/1999/xhtml"> 11 12 <head> 13 <title>Gueet List</title> 14 15 <style type = "text/ess"> 16 body { 17 font-family: tahoma, helvetica, arial, sans-serif 18 } 19 20 table, tr, td, th { 21 text-align: center; 22 font-size: . 9em; 23 border: 3px groove; 24 padding: 5px; 25 background-color: idddddd 26 } 27 </style> 28 </head> 29 30 <body> 31 <p style = "font-size: 2em">Guest List</p> 32 33 <table> 34 <thead> 35 <th style = "width: 100px">Last name</th> 36 <th style = "width: 100px">First name</th> 37 <th style = "width: 200px">Email</th> 38 </thead> 39 40 <%-- нестандартный тег guestiist --%> 41 <advjhtpl:guestlist> 42 <tr> 43 <tdX% = lastMame %x/td> 44 45 <tdx%= firstName %X/td>
164 Глава 3 46 47 <td> 48 <а href = "mailto:<%= email %>"> 49 <%= email %X/a> 50 </td> 51 </ti> 52 </advjhtpl:guestlist> 53 </table> 54 </body> 55 56 </html> Рис. 3.36. Использование нестандартного тега, который осуществляет взаимодействие с собственным телом (элемент body) Как и для страницы guestBookView.jsp, обработчик нестандартного тега Goest- BookTag (рис. 3.37) создает объект GuestDataBean для доступа к базе даниых guestbook. Класс GuestBookTag расширяет класс BodyTagSupport, который содержит несколько новых методов, включая doInitBody и do After Body (из интерфейса BodyTag). Метод doInitBody вызывается один раз, после вызова метода doStartTag и перед вызовом метода doAfterBody. Метод do After Body может многократно вызываться для обработки тела нестандартного тега. Ш Общая методическая рекомендация 3.19 Перед обработкой тела нестандартного тега методом doAfterBody обычно выполняется его однократная обработка с помощью метода doInitBody. Если метод doStartTag возвращает Tag.SKIP_BODY, метод doInitBody не вызывается. 1 // Рис. 3.37. GuestBookTag.java 2 // Нестандартный обработчик тегов, который получает информацию из 3 // базы данных гостевой книги и делает эти данные доступными для JSP-страницы. 4 package com.deitel.advjhtpl■jsp.taglibrary; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import iava.util.*; 9 10 // Пакеты расширений Java
JavaServer Pages (JSP) 165 11 import 3avax.servlet.3sp.*; 12 import javax.servlet.jsp.tagext.*; 13 14 // Пакеты Deitel 15 import com.deitel.advjhtpl.jsp.beans.*; 16 17 public class GuestBookTag extends BodyTagSupport { 18 private String firstName; 19 private String lastNarae; 20 private String email,- 21 22 private GuestDataBean guestData; 23 private GuestBean guest; 24 private Iterator iterator; 25 26 // Метод, который вызывается, чтобы начать обработку тега 27 public int doStartTagO throws JspException 28 { 29 // попытка обработки тега 30 try { 31 guestoata = new GuestDataBean(); 32 33 List list = guestData.getGuestList(); 34 iterator = list.iterator(); 35 36 if t iterator.hasNext() ) { 37 processNextGuest(); 38 39 return EVAL__BODY_TAG; // продолжение обработки тела тега 40 } 41 else 42 return SKIF_BODY; // завершение обработки тела тега 43 } 44 45 // если возникает исключение, не продолжать обработку 46 // тела тега 47 catch ( Exception exception ) { 48 exception.printstackTrace0; 49 return SKIP_BODY; // игнорировать тело тега 50 } 51 } 52 53 // обработка тела тега и определение, следует ли 54 // продолжать обработку 55 public int doAfterBodyO 56 { 57 // попытка вывода данных тела тега 58 try 1 59 DodyContent.writeOut< getPreviousOut<) ); 60 ) 61 62 // если возникает исключение, завершить обработку тела тега 63 catch ( IOException ioException ) { 64 ioException.printStackTraee0; 65 return SKIP_BODY; // завершение обработки тела тега 66 )
166 Глава 3 67 68 bodyС onten t.сlearBody() ; «9 70 if ( iterator.hasNext() ) { 71 processNextGuestO ,- 72 73 return EVAL_BODY_TAG,- // продолжение обработки тела тега 74 } 75 else 76 return SKIP_B0DY; // завершение обработки тела тега 77 } 78 79 // получение спедуящего объекта GuestBean и извлечение данных иэ него 80 private void processNextGuest() 81 { 82 // получение данных для следующего госия 83 guest = ( GuestBean ) iterator.next(); 84 85 pageContext.setAttribute ( 86 "firstName", guest.getFirstName() ); 87 88 pageContext.setAttribute( 89 "lastName", guest.getLastName() ); 90 91 pageContext.setAttribute( 92 "email", guest.getEmail() ); 93 ) 94 ) Рис. 3.37. Обработчик нестандартного тега GuestBookTag Контейнер JSF вызывает метод doStartTag (строки 27-51), когда он обнаруживает на J8F-cTpaHHn,e нестандартный тег guestlist. В строках 31-34 создается новый объект Guest Da taBean, извлекается список компонентов GuestBean из компонента GuestDataBean, и создается итератор Iterator для манипулирования содержимым списка ArrayList. Если в списке нет элементов (это проверяется в строке 36), в строке 42 возвращается значение SKIP_BODY, указывающее, что контейнеру не следует выполнять дальнейшую обработку тела тега guestlist. В противном случае в строке 37 осуществляется вызов частного метода processNextGuest (строки 80-93) для извлечения информации о первом госте и создания переменных, хранящих эту информацию в контексте страницы PageContext (представляемом переменной pagecoatext, унаследованной из класса BodyTagSupport). Метод processNextGuest использует метод setAttribute интерфейса PageContext для задания имени и значения каждой из переменных. Контейнер ответственен за создание реальных переменных, используемых JSP-страницей. Это осуществляется с помощью класса GuestBookTagExtralnfo (рис. 3.38). Метод doAfterBody (строки 55-77) выполняет циклическую обработку тела тега guestlist. Контейнер JSP определяет, следует ли вызывать метод doAfterBody, на основе возвращаемого методом значения. Если doAfterBody возвращает EVAL_BODY_TAG, контейнер вызывает метод doAfterBody снова. Если doAfterBody возвращает SKlP_BODY, контейнер прекращает обработку тела и вызывает метод doEndTag обработчика нестандартного тега для завершения собственной обработки. В строке 59 вызывается метод writeOut для переменной bodyContent (унаследованной от класса BodyTagSupport), чтобы обработать данные для первого клиента (сохраненные при вызове метода doStartTag). Переменная bodyContent
JavaServer Pages (JSP) 167 ссылается на объект класса BodyContent из пакета javax.servlet.jsp.tagext. Параметр, передаваемый методу writeOut, является результатом выполнения метода getPreviousOut (унаследованного от класса BodyTagSupport), который возвращает объект JspWriter для JSP-страиицы, вызывающей нестандартный тег. Это позволяет нестандартному тегу формировать ответ клиенту с помощью того же потока вывода, который использует JSP-страница. Далее, в строке 68 вызывается метод clearBody для переменной bodyContent, чтобы гарантировать, что выведенное содержимое тега не будет обрабатываться как чаеть следующего вызова doAfter- Body, В строках 70-76 определяется, имеются ли еще в гостевой книге записи, подлежащие обработке. Если да, метод do Afterbody вызывает частный метод processNextGuest для получения данных для следующего гостя и возвращает значение EVAL_BODY_TAG, указывающее, что контейнер должен снова вызвать метод doAfterBody. В противном случае doAfterBody возвращает значение SKIP_BODY, чтобы завершить обработку тела. Контейнвр JSP не может создавать переменные в контексте страницы Page- Context, если ему не известны имена и типы этих переменных. Эта информация определяется классом, который носит то же имя, что и обработчик нестандартного тега, но заканчивается на Extralnfo (GuestBookTagExtralnfo на рис. 3.38). Классы Extralnfo расширяют класс TagExtralnfo (пакет javax.servlet.jsp.tagext). Контейнер использует информацию, задаваемую подклассом класса TagExtralnfo, для определения, какие переменные следует создать (или использовать) в контексте PageContext. Чтобы задать информацию о переменной, следует переопределить мвтод getVaridblelnfo. Этот метод возвращает массив объектов Variablelnfo, которые контейнер использует либо для создания новых переменных в контексте страницы PageContext, либо для того, чтобы дать возможность нестандартному тегу использовать существующие переменные в контексте страницы PageContext, Конструктор Variablelnfo принимает четыре параметра: строку, представляющую имя переменной, строку, представляющую имя класса переменной, булево значение, указывающее, следует ли контейнеру создавать переменную (true, если да) и статическую целочисленную константу, задающую область видимости переменной на JSP-странице. В классе Variablelnfo определены константы NESTED, AT_BfiGIN и AT_END. Константа NESTED указывает, что переменная может быть использована только в теле нестандартного тега. Константа AT_BEGIN указывает, что переменная может быть использована в любом месте JSP-страницы после начального тега нестандартного тега. Константа AT_END указывает, что переменная может использоваться в любом месте JSP-страницы после конечного тега нестандартного тега. Прежде, чем использовать тег guestlist на JSP-странице, необходимо сообщить контейнеру JSP о теге, добавив его в библиотеку тегов. Для этого следует добавить элемент tag (см. рис. 3.39) в качестве дочернего элемента для элемента taglib в дескриптор adYJh.tpl-taglib.tld библиотеки тегов. Как и в предыдущем примере, элемент tag содержит элементы name, tagclass, bodycontent и info. В строках 10-12 вводится элемент teiclass для задания класса Extralnfo нестандартного тега. Чтобы протестировать JSP-странину customTagBody.jsp в Tomcat, скопируйте файл customTagBody.jsp и модифицированный файл advjb.tpl-taglib.tld в каталог jsp, созданный в разделе 3.3. Скопируйте файлы GuestBookTag.class и GuestBookTagExtralnfo. class в каталог WEB-INF\classes Web-приложеиия advjhtpl на сервере Tomcat. [Замечание. Этот пример будет работать только в том случае, если в каталоге classes определена надлежащая структура каталогов пакета для классов GuestBookTag GuestBookTagExtralnfo.] Откройте Web-браузер и введите следующий URL, чтобы протестировать JSP-страницу customTagBody.jsp: http://localhost:8080/advjhtpl/jsp/customTagBody.jsp
168 Глава 3 1 // Рис.3.38. GuestBookTagExtralnfo.Java 2 // Класс, который определяет имена и филы переменных, 3 // создаваемых нестандартным обработчиком тегов GuestBookTag. 4 package com,deitel.advjhtpl.j sp.taglibrary; 5 6 // Набор базовых пакетов Java 7 import javax.servlet.j sp.tagext.*; 8 9 public class GuestBookTagExtralnfo extends TagExtralnfo { 10 11 // метод, который возвращает информацию о переменных 12 // GuestBookTag, созданных для использования их на JSP-странице 13 public VariableInfo [] getVariablelnfо( TagData tagData ) 14 { 15 Variablelnfo firstName = new Variablelnfо( "firstName", 16 "String", true, Variablelnfо.NESTED ); 17 18 Variablelnfo lastName = new Variablelnfo( "lastName", 19 "String", true, Variablelnfо.NESTED ); 20 21 Variablelnfo email = new Variablelnfo( "email", 22 "String", true, Variablelnfо.NESTED ); 23 24 Variablelnfo varablelnfo [] = 25 { firstName, lastName, email }; 26 27 return varablelnfo,- 28 } 29 } __^ Рис. 3.38. Класс GuestBookTagExtralnfo используется контейнером для определения переменных сценария на JSP-странице, которая использует нестандартный тег guestlist 1 <!— Тех1, который осуществляет обход массива --> 2 <!— компонентов GuestBean для вывода их в JSP-странице. —> 3 <tag> 4 <name>guestlist</name> 5 <tagclass> 6 com.deitel.advjhtpl.jap.taglibrary.GuestBookTag 7 </tagclass> 8 9 <teiclass> 10 com.deitel.advj htpl.j sp.taglibrary.GuestBookTagExtraInfo 11 </teiclass> 12 13 <bodycontent>JSP</bodycontent> 14 15 <info> 16 Обход списка объектов GuestBean. 17 </info> 18 19 <tag> Рис. 3.39. Элемент tag для нестандартного тега guest list
JavaServer Pages (JSP) 169 В этой главе были рассмотрены многие из возможностей технологии JSP. Однако ряд дополнительных возможностей остался за пределами рассмотрения этой книги. За полной информацией обратитесь к спецификации JavaServer Pages 1.1, которую можно загрузить по адресу j a va.s an .com/product s/jsp/do wnload.html. Другие ресурсы по JSP приведены в разделе 3.9. В следующей главе будет продолжено обсуждение технологии JSP и сервлетов и рассмотрен большой практический пример, демонстрирующий их применение: книжный Internet-магазин. В этом примере нашли отражение многие технологии Java, в том числе JDBC, сервлеты, JSP и XML. В ходе рассмотрении примера также будут обсуждены некоторые дополнительные возможности, связанные с сервлетами. Представленные в практическом примере приемы и методы закладывают основу для создания большого примера приложения для электронного бизнеса, рассматриваемого в главах с 8 по 11. 3.9. Ресурсы в Internet и во Всемирной паутине Java.sun.com/products/jap Основная страница сайта Sun Microsystems, посвященного Java, которая содержит информацию по технологии JavaServer Pages. Java.sun.com/prodiicts/servlet Основная страница сайта Sun Microsystems, посвященного Java, которая содержит информацию по технологии сервлетов j ava. sun, com/ j 2ee Основная страница сайта Sun Microsystems, посвященного Java, которая содержит информации по Java 2 Enterprise Edition. ии.мЗ . oxg Основная страница консорциума World Wide Web Consortium. Этот сайт предоставляет информацию о действующих и разрабатываемых стандартах для Internet и Web, таких как XHTML, XML и CSS. jsptags.com Этот сайт содержит учебный материал, библиотеки тегов, программные продукты и другие ресурсы, полезные для программистов JSP. j spinsidsr.com Этот сайт удаляет основное внимание ресурсам для программистов JSP. Здесь представлены программные продукты, учебный материал, статьи, образцы кода, ссылки на другие ресурсы, имеющие отношение К JSP и к средствам программирования в Web. Резюме • Технология JavaServer Pages (JSP) является расширением технологии сервлетов. • Технология JavaServer Pages позволяет программистам Web-приложений создавать динамическое содержимое путем многократного использования ранее определенных компонентов и взаимодействия с компонентами с помощью сценария, выполняющегося на стороне сервера. • Программисты JSP могут создавать библиотеки собственных (нестандартных) тегов, которые позволяют дизайнерам Web-страниц, не знакомых с программированием на Java, усовершенствовать свои Web-страницы, добавляя новые возможности обработки и динамического отображения содержимого. • Классы и интерфейсы, относящиеся к программированию JSP-страниц, содержатся в пакетах javax.servlet.jsp и javax.servlet.jsp.tagext. • Спецификацию JavaServer Pages 1.1 можно загрузить по адресу java.sun.com/prodiicts/ jsp/download/htm]. • Имеется четыре ключевых компонента JSP: директивы, действия, скриптлеты и библиотеки тегов.
170 Глава 3 • Директивы задают глобальную информацию, которая не связана с конкретным запросом JSP. • Действия инкапсулируют функциональные возможности в предопределенных тегах, которые программисты могут встраивать в JSP-страницу. • Скриптлеты, или элементы сценария, дают возможность программистам включать код Java, который взаимодействует с компонентами JSP-страницы (и, возможно, с компонентами другого Web-приложения) для обработки запроса. • Библиотеки тегов являются частью механизма расширения тегов, который позволяет программистам создавать новые теги, инкапсулирующие сложные функциональные возможности Java. • JSP-страницы обычно содержат XHTML- или XML-разметку. Подобная разметка известка как данные с неизменной структурой илн текст с неизменной структурой. • Программисты используют JSP-страницы, если большая часть содержимого, отправляемого клиенту, представляет собой данные с неизменной структурой, и лишь малая часть содержимого генерируется динамически с помощью кода Java. • Программисты используют сервлеты, если лишь малая часть содержимого представляет собой данные с неизменной структурой. • JSP-страницы обычно выполняются на Web-сервере. Сервер при этом называют контейнером JSP. • Когда сервер, поддерживающий технологию JSP, получает первый запрос на JSP-страницу, контейнер JSP транслирует ее в сервлет, который обрабатывает текущий запрос и последующие запросы к этой JSP-странице. • Контейнер JSP помещает операторы Java, которые реализуют ответ JSP-страницы, в оператор _jspService на этапе трансляции. • Механизм запрос/ответ и жизненный никл для JSP-страниц и для сервлетов аналогичен. • JSP-страницы могут определять методы jsplnit и jspDestroy, которые вызываются, соответственно, когда контейнер инициализирует JSP-странкцу и когда контейнер завершает обработку JSP-страницы. • Выражения JSP заключаются б ограничители <% = и %>. Эти выражения преобразовываются контейнером JSP в строки и выводятся как часть ответа. • Элемент meta XHTML может устанавливать интернат обновления для документа, который загружен в браузер. При этом браузер периодически запрашивает документ через заданные в секундах интервалы времени. ■ Первый вызов JSP-страницы в Tomcat сопровождается задержкой времени, связанной с необходимостью трансляции JSP-страницы в сервлет и вызова сервлета для ответа на запрос, • Неявные объекты предоставляют программистам функциональные возможности сервлетов в контексте JavaServer Page. • Неявные объекты имеют четыре области видимости: приложения, страницы, запроса и сеанса. • Объекты с областью видимости в пределах приложения являются частью приложения контейнера JSP и сервлетов. • Объекты с областью видимости в пределах страницы существуют только в составе страницы, в которой они используются. Каждая страница имеет свои собственные экземпляры неявных объектов со страничной областью видимости. • Объекты с областью видииости в пределах запроса существуют в течение запроса. Объекты с областью видимости в пределах запроса теряют свое действие при завершении обработки запроса выдачей ответа клиента. • Объекты с областью видимости в пределах сеанса существуют в течение всего клиентского сеанса просмотра. • К компонентам сценария JSP относятся скриптлеты, комментарии, выражения, объявления и escape-последовательности. ■ Скриптлеты представляют собой фрагменты кода, ограничиваемые символами <% и % >. Они содержат операторы Java, которые помещаются в метод _j'spService, когда контейнер транслирует JSP-страницу в сервлет. • Комментарии JSP заключаются в ограничители <%— и —%>. Комментарии XHTML заключаются в ограничители <!— и —>. Внутри скриптлетов могут использоваться однострочные комментарии Java (//) и многострочные комментарии Java (заключенные в ограничители /* и */)■
JavaServer Pages (JSP) 171 • Комментарии JSP и комментарии языка сценария игнорируются и н ответе не присутствуют, ■ Выражение JSP, заключенное в ограничители <%= и %>, содержит выражение Java, которое вычисляется, когда клиент запрашивает JSP-страницу, содержащую это выражение. Контейнер преобразовывает результат вычисления выражения JSP в строку, а затем выводит строку в составе ответа клиенту. • Объявления, заключенные в ограничители <%! и %>, дают возможность программисту JSP определять переменные и методы. Переменные становятся переменными экземпляра класса, который представляет транслированную JSP-страницу. Аналогично, методы становятся членами класса, который представляет транслированную JSP-страницу. ■ Специальные символы или последовательности символов, которые контейнер JSP обычно использует в качестве ограничителей кода JSP, могут включаться в JSP-страницу в виде буквенных символов в элементы сценария, в данные с неизменной структурой и в значения атрибутов с помощью escape-последовательностей. • Стандартные действия JSP предоставляют программистам JSP-страниц доступ к нескольким наиболее типичным задачам, выполняемым JSP-стрянипей. Контейнеры JSP обрабатывают действия на этапе запроса. ■ Страницы JavaServer Pages поддерживают два механизма включений: действие <jsp: inclTJde> и директиву include. • Действие <jsp:inclnde> позволяет включать в JSP-страницу динамическое содержимое. Если включаемый ресурс в промежутке между последовательными запросами изменяется, следующий запрос к JSP-странице, содержащий действие <:jsp:inclttde>, будет осуществлять включение новое содержимое ресурса. • Директива include обрабатывается один раз, на этаге трансляции, и приводит к копированию содержимого на JSP-страницу. Если включаемый ресурс изменяется, новое содержимое не будет отражено в JSP-странице, использующей директиву включения, пока JSP-страница не будет откомпилирована повторно. • Действие <jsp:forward> дает возможность JSP-странице переадресовывать обработку запроса другому ресурсу. Обработка запроса исходной JSP-страницей завершается, как только запрос переадресовывается. • Действие <jsp:param> задает пары имя/эначение для информации, которая передается действиям Include, forward и plugin. Каждое действие <jsp:param> имеет два обязательных атрибута: name и value. Если действие рагаш задает параметр, который уже присутствует в запросе, новое значение для параметра имеет приоритет над исходным значением. Все значения для этого параметра могут быть получены с помощью метода getPa- rameterValues неявного объекта JSP request, который возвращает строковый массив. ■ Действие JSP <jsp:plugin> позволяет добавлять на Web-странйцу апплет или компонент JavaBeans в виде специфичного для браузера XHTML-элемеята object или embed. Это действие также дает возможность загружать и устанавливать подключаемые модули Java Plug-in, если они еще не установлены на компьютере клиента. • Действие <jsp;useBean^ позволяет JSP-странице манипулировать объектом Java. Это действие может быть использовано для создания объекта Java, используемого JSP-страницей, или для нахождения существующего объекта. • Подобно неявным объектам Java, объекты, задаваемые действием <jsp:oseBean>, имеют области видимости в пределах, страницы (page), запроса (request,), сеанса (session) иди приложения (application), которые указывают, где они могут использоваться в Web-приложении. • Действие <jsp:getProperty> получает значение свойства компонента JavaBeans. Действие <J5p:getProperty> имеет два атрибута — name и property — которые задают объект JavaBeans, подлежащий манипулированию, и свойство, которое следует получить- • Значения свойств компонента JavaBeans могут задаваться с помощью действия ^jsp: setProperty>. Это действие особенно полезно для установки соответствия между значениями параметров запроса и свойствами компонента JavaBeans. Параметры запроса могут бычь использованы для установки свойств примитивных типов boolean, byte, спаг, int, long, float и double и типов String, Boolean, Byte, Character, Integer, Long, Float и Double из пакета java.lang. • Директива page определяет информацию, которая глобально доступна дл» JSP-Страни- цы. Директивы заключаются в ограничители <%@ и %>. Атрибут errorPage директивы page задает, куда следует пересылать для обработки все перехваченные исключения. • Действие <jsp:setProperty> способно отождествлять параметры запроса СО свойствами компонента с теми же именами путем задания "*" в качестве значения атрибута property.
172 Глава 3 • Атрибут import директивы page дает возможность программистам задавать классы и пакеты Java, которые будут использоваться в контексте JSP-страницы. • Если для атрибута iaErrorPage директивы page задано значение true, JSP-страница является страницей обработки ошибок. При этом открывается возможность доступа к неявному объекту JSP exception, который представляет собой объект исключения, указывающий на наличие проблемы. • Директивы представляют собой сообщения для контейнера JSP, которые позволяют программисту задавать параметры страницы (например, страницу ошибок), включать содержимое других ресурсов и задавать библиотеки нестандартных тегов, которые могут использоваться JSP-страницей. Директивы обрабатываются на этапе трансляции JSP-страницы в сер влет и компиляции. Таким образом, директивы не выдают какой-либо результат немедленно. • Директива page задает глобальные настрэйкн для JSP-страницы в контейнере J3P. Возможно использование множества директив page при условии, что при этом имеется только одно вхождение каждого из атрибутов. Исключением из этого правила является атри- бут import, который может использоваться многократно дли импорта пакетов Java. • Библиотеки нестандартных тегов определяют один или несколько нестандартных тегов, которые программисты JSP-страниц могут использовать для создания динамического содержимого. Функциональные возможности этих нестандартных тегов определены в классах Java, которые реализуют интерфейс Tag (пакет javax.servlet.jsp.tagext), обычно путем расширения класса TagSupport или класса BodyTagSupport. • JSP-страница может осуществлять включение библиотеки нестандартных тегов с помощью директивы taglib. • При реализации нестандартных тегов необходимо определить для каждого из тегов класс обработчика тега, который содержит функциональные возможности тега, а также дескриптор библиотеки тегов, который предоставляет информацию о библиотеке тегов и нестандартных тегах контейнеру JSP и JSP-етранице, использующей нестандартный тег. • Наиболее важными методами интерфейса Tag являются методы dc-S tart Tag и doEndTag. Контейнер JSP вызывает эти методы, когда встречает, соответственно, начальный (открывающий) нестандартный тег и конечный (закрывающий) нестандартный тег. • Файл дескриптора библиотеки нестандартных тегов представляет собой XML-документ, содержащий информацию о библиотеке тегов, которая необходима для контейнера JSP. • Класс BodyTagSupport содержит несколько методов для взаимодействия с телом нестандартного тега, такие как методы doInitBody и doAfterBody (из интерфейса BodyTag). Метод doInitBody вызывается один раз после вызова метода doStartTag и перед вызовом метода doAfterBody. Метод doAfterBody может многократко вызываться для обработки тела нестандартного тега. Терминология % \>, escape-последовательность для символов %> <\— и —>, ограничители комментариев XHTML <%— и --—%>, ограничители комментарии ев JSP <% и %>, ограничители скршмлета <%! и %>, ограничители объявления <%= и %>, ограничители выражения JSP <%@ и %>, ограничители директивы <\%, escape-последовательыоеть для <% action — действие align, атрибут действия <jsp:plugin> application, неявный объект application scope — область видимости в пределах приложения archive, атрибут действия <jsp:plugin> AT_BEGIN, константа AT_END, константа attribute, элемент дескриптора библиотеки тегов autoFlash, атрибут директивы page bean Name, атрибут действия <jsp:usebean> bodyContent, элемент дескриптора библиотеки тегов BodyContent, интерфейс BodyTag, интерфейс BodyTagSupport, класс buffer, атрибут директивы page class, атрибут действия <jsp:usebean> client-server networking — сетевое взаимодействие между клиентом и сервером code, атрибут действия <jsp:plngin> codebase, атрибут действия <jsp:plugin> comment — комментарий coafig, неявный объект container — контейнер contentType, атрибут директивы page
JavaServer Pages (JSP) 173 custom tag — нестандартный тег custom tag attribute —■ атрибут нестандартного тега custom tag handler — обработчик нестандартного тега custom tag library — библиотека нестандартных тегов custom tag with attributes —нестандартный тег с атрибутами declaration — объявление directive — директива doAfterBody, метод интерфейса BodyTag doEndTag, метод интерфейса Tag doInitBody, метод интерфейса BodyTag doStartTag, метод интерфейса Tag dynamic content — динамическое содержимое error page — страница ошибок errorPage, атрибут директивы page escape sequence — escape-последовательность EVAL_BODY_INCLUDE, константа exception, неявный объект expression — выражение extends, атрибут директивы page file, атрибут директивы include fixed template data — данные с неизменной структурой fixed template text — текст с неизменной структурой flush, атрибут действия <jsp:include> forward a request — пересылка запроса getParatneterValues, метод объекта request getVariablelnfo, метод класса TagExtralnfo height, атрибут действия <jsp:plugin> hspace, атрибут действия <jsp:plugin> HttpSession (javax.servlet.http), интерфейс id, атрибут действия <jsp:oseBean> ieplugmurl, атрибут действия <jsp:plugin> implicit object — неявный объект implicit abject scopes — области видимости неявного объекта import, атрибут директивы page include a resource — включение ресурса include, директива info, атрибут директивы page isErrorPage, атрибут директивы page isThreadSafe, атрибут директивы page Java Plug-in — подключаемый модуль Java JavaServer Pages (JSP) — серверные страницы Java JavaServer Pages 1.1 specification, пгтеггифи- кация javax.servtet.jsp, пакет javax.servlet.jsp.tagext, пакет jreversion, атрибут действия <jsp:plugin> <jsp:forward>, действие <jsp:getProperty>, действие <jsp:include>, действие <jep:param>, действие <jsp:plugin>, действие <jsp:setProperty>, действие <jsp:useBean>, действие jspDestroy, метод jsplnit, метод _jspService, метод jspversion, элемент дескриптора библиотеки тегов Jsp Writer (пакет javax.servlet.jsp) language, атрибут директивы page match request parameters — отождествление параметров запроса meta, элемент name, атрибут действия <jsp:param> name, атрибут действия <jsp:plugin> name, атрибут действия <jsp:setProperty> name, элемент дескриптора библиотеки тегов name/value pair — пара имя/значение NESTED, константа nspluginurl, атрибут действия <jsp:plugin> out, неявный объект page, атрибут действия <jsp:forward> page, атрибут действия <jsp:include> page, директива page, неявный объект page scope — область видимости в пределах страницы PageContext, интерфейс pageContext, неявный объект рагат, атрибут действия <jsp:setProperty> prefix, атрибут директивы taglib property, атрибут действия <jsp:setProperty> refresh interval — интервал обновления request, неявный объект request scope — область видимости в пределах запроса request-time error — ошибка на этапе запроса required, элемент дескриптора библиотеки тегов response, неявный объект rtexprvalne, элемент дескриптора библиотеки тегов scope, атрибут действия <jsp:useBean> scope of a bean — область видимости компонента JavaBeans scripting element — элемент сценария scriptlet — скриптлет session, атрибут директивы page session, неявный объект session scope — область видимости в пределах сеанса setAttribute, метод интерфейса PageContext simple custom tag — простой нестандартный тег SKIP_BODY, константа specify attributes of a custom tag — задание атрибутов нестандартного тега
174 Глава з standard actions — стандартные действия teiclass, элемент дескриптора библиотеки тегов tag, элемент дескриптора библиотеки тегов title, атрибут действия <jsp:plugin> tag extension mechanism — механизм рас- tlibversion, элемент дескриптора библиоте- ширения тегов ки тегов tag handler — обработчик тега translation-time error — ошибка на этапе Tag, интерфейс трансляции tag library — библиотека тегов translation-time include — включение на tag library descriptor — дескриптор библио- этапе трансляции теки тегов type, атрибут дейстаия <jsp:plugin> tagclass, элемент дескриптора библиотеки type, атрибут дейстаия <jsp:ngeBean> тегов uri, атрибут директивы taglib TagExtralnfo, класс value, атрибут действия <jsp:setProperty> taglib, директива value, атрибут действия <jsp:plngin> tagPrefix, атрибут директивы taglib value, атрибут действия <jsp;param> TagSupport, класс width, атрибут действия <jsp;plugin> Упражнения для самоконтроля 3.1. Заполните пропуски в следующих высказываниях: a) Действие JSP позволяет добавлять апплет или компонент JavaBeans на Web-страницу в виде специфичного для браузера XHTML-элемеНта object или embed. b) Действие способно отождествлять параметры запроса со свойствами компонента с теми же именами при задании "*" в качестве значения атрибута property. c) Имеется четыре ключевых компонента JSP-страяицы: _, , и . d) JSP-страница может осуществлять включение библиотеки нестандартных тегов с помощью директивы . e) Неявные объекты имеют четыре области видимости: , , И ■ f) Директива ^___ обрабатывается один раз на этапе трансляции JSP-страни- цьг и приводит к копированию содержимого на JSP-страницу. g) Классы и интерфейсы, относящиеся к программированию страниц JavaServer Pages, содержатся в пакетах __ и . h) JSP-страницы обычно выполняются на Weh-сервере, который называется i) Метод может многократно вызываться для обработки тела нестандартного тега. j) К компонентам сценария JSP относятся , , , __ и . 3.2. Ответьте, являются ли следующие высказывания истинными или ложными. Если высказывание ложно, объясните, почему. a) Объект со страничной областью видимости существует в пределах всех JSP-страниц определенного Web-приложения. b) Директивы задают глобальную информацию, которая не связана с определенным запросом JSP. c) Контейнер JSP вызывает методы doInitBody и doAfterBody, когда встречает, соответственно, начальный нестандартный тег и конечный нестандартный тег. d) Библиотеки тегов представляют собой составную часть механизма расширения тегов, который позволяет программистам создавать новые теги, инкапсулирующие сложные функциональные возможности Java. e) Дейстнне <jsp:include> обрабатывается один раз на этапе трансляции страницы. f) Подобно комментариям XHTML, комментарии JSP и комментарии языка сценариев присутствуют в ответе клиенту. g) Объекты с областью видимости в пределах приложения явлвются частью определенного Web-приложения. h) Каждая страница имеет свои собственные экземпляры неявных объектов со страничной областью видимости.
iavaServer Pages (iSP) 175 i) Действие <jsp:setProperty> способно отождествлять параметры запросов со свойствами компонента с теми же именами при задании "*" в качестве значения атрибута property. j) Объекты с областью видимости в пределах сеанса существуют в течение всего клиентского сеанса просмотра. Ответы на упражнения для самоконтроля 3.1. a) <jsp:plugin>- b) <jsp:setProperty>. с) директивы, действия, скриптлеты, библиотеки тегов, d) taglib. e) приложения, страницы, запроса и сеанса, f) include, g) ja- vax.servlet.jsp, javax.servlet.jsp.tagext. h) контейнером JSP. i) doAfterBody, j) скриптлеты, комментарии, выражения, объявления, escape-последовательности, 3.2. а) Ложно. Объекты со страничной областью видимости существуют только на той странице, на которой они используются. b) Истинно. c) Ложно. Контейнер JSP вызывает метод doStartTag и doEndTag, когда встречает, соответственно, начальный нестандартный тег и конечный нестандартный тег. d) Истинно. e) Ложно. Действие <Jsp:ittclude> позволяет аключать динамическое содержимое в страницу JavaServer Page. f) Ложно. Комментарии JSP и комментарии языка сценариев игнорируются и в ответе не присутствуют. g) Ложно. Объекты с областью видимости в пределах приложения являются частью приложения контейнера JSP и сервлетов, h) Истинно. i) Истинно, j) Истинно. Упражнения 3.3. Создайте класс ResultSetTag (обработчик нестандартного тега), который может отображать информацию из любого результирующего множества ResuItSet. Возьмите за основу класс GuestBw>kTag {ряс. 3.37). В качестве имен атрибутов объекта page- Context должны использоваться имена столбцов в результирующем множестве Result- Set. Имена столбцов могут быть получены с помощью объекта ResnltSetMetaData, ассоциированного с результирующим множеством ResuItSet. Создайте дескриптор библиотеки тегов для нестандартного тега н протестируйте нестандартный тег на JSP-странице. 3.4. Создайте JSP-страницу и адресную книгу на основе технологии JDBC. Возьмите за основу пример приложения гостевой книги (рис. 3,20-3,24). Rama адресная книга должна давать возможность вставлять записи, удалять записи и осуществлять поиск записей. 3.5. Инкорпорируйте класс ResultSetTag ая Упражнения 3.3 в приложение адресной книги из Упражнения 3.4. 3.6. Создайте новую реализацию вашего решения Упражнения 2.5 (Dynamic Web FAQ) с использованием JSP-страниц вместо сервлетов. Создайте обработчик нестандартного тега, подобный тому, который вы создали в Упражнении 3.3, чтобы отображать информацию для FAQ. 3.7. Модифицируйте ваше решение Упражнения 3.6, чтобы первая вызываемая пользователем JSP-стрвлиця возвращала список рубрик часто задаваемых вопросов (FAQ), из которого можно выбрать интересующую тему. Для каждой темы должна быть предусмотрена гиперссылка, которая вызывает другую JSP-страницу с параметром, указывающим, к какой теме хотел бы обратиться пользователь, JSP-страница должна обращаться с запросом к базе данных FAQ и возвращать XHTML-документ, содержащий только FAQ по этой теме. 3.8. Реализуйте Web-приложение, представленное на рис. 2.27 (Web-onpoc «ваше любимое животное»), с использованием JSP.
1?6 Глава 3 3.9. Модифицируйте ваше решение Упражнения 3.8, чтобы пользователь мог видеть результаты опроса, не отвечай на его вопросы, 3.10. Реализуйте пример, представленный на рис. 2.24 (рекомендуемые книги по программированию), с использованием JSP-страннц. Воспользуйтесь неявным объектом JSP session, чтобы отслеживать выбор пользователя и определять, какие книги рекомендовать. Используйте директиву page для указания, что каждая из JSP-страниц участвует в сеансе.
4 Книжный Internet-магазин, реализованный с использованием сервлетов и JSP Цели • Построить трехуровневое распределенное Web-приложение по схеме клиент/сервер, использующее сервлеты Java и технологию JavaServer Pages. • Научиться осуществлять вз аимо действия с сервлетами/JSP. • Научиться использовать интерфейс Re quest Dispatcher для переадресации запроса и дальнейшей его обработки. • Научиться создавать XML-код с помощью сервлетов и применять XSLT-трансформации для преобразования его в формат, который может быть отображен клиентом. • Познакомиться с сервером эталонной реализации Java 2 Enterprise Edition. • Научиться развертывать Web-приложение с использованием средств Java 2 Enterprise Edition. Мир — это книга, и те, кто не путешествуют, читают лишь одну ее страницу. Святой Августин Если мы не поставим себя на службу человечеству, кому мы будем служить? Джон Адаме Пользуйтесь подарками судьбы, ибо милость ее не беспредельна. Уильям Шекспир
178 Глава 4 4.1. Введение Эта глава является кульминацией нашего рассмотрения технологии JSP и серв- летов. В ней мы реализуем Web-приложение книжного Internet-магазина, которое использует технологии JDBC, XML, JSP и сервлеты. Практический пример знакомит и с дополнительными возможностями сервлетов, которые мы будем обсуждать по ходу рассмотрения примера. В этой главе вы также познакомитесь с эталонной реализацией Java 2 Enterprise Edition 1Л.1, которая будет использоваться в главах 6-10. В отличие от глав, посвященных JSP и сервлетам, в которых примеры демонстрировались с применением JSP Apache Tomcat и контейнера сервлетов, в этой главе развертывание приложения осуществляется на эталонной реализации сервера приложений J2EE 1.2.1, которую можно загрузить по адресу java.sim.com/j2ee/dowiiload.html. В состав эталонной реализации J2EE 1.2,1 входят JSP Apache Tomcat и контейнер сервлетов. Прочитав эту главу, вы сможете реализовать большое распределенное Web-приложение с множеством компонентов, а также выполнить развертывание этого приложения на сервере приложений J2EE 1.2.1.
Книжный Internet-магазин, реализованный с использованием сервлетов и J5P 179 4.2. Архитектура приложения книжного Internet-магазина В этом разделе представлен обзор архитектуры приложения книжного Internet-магазина Bug2Bug.com. Будет рассмотрена схема основных взаимодействий между XHTML-документами, JSP-страницами и сервлетами. Мы также представим таблицу всех документов и классов, используемых в примере. Образцы выходных результатов демонстрируют, как отображаются XHTML-документы, отправляемые клиенту. Наше приложение Bug2Bug.com состоит из ряда XHTML-документов, JSP- страниц и сервлетов, которые взаимодействуют между собой, имитируя книжный магазин, торгующий продукцией компании Deitel- Пример реализован как распределенное трехуровневое Web-приложение. Клиентский уровень представлен Web- браузером пользователя. Браузер отображает статические XHTML-документы и XHTML-документы, создаваемые динамически, которые дают возможность пользователю взаимодействовать с серверным уровнем. Серверный уровень состоит из нескольких JSP-страниц и сервлетов, которые действуют в интересах клиента. Эти JSP-страницы и сервлеты выполняют такие задачи, как создание списка публикаций, создание документов, содержащих сведения о публикации, добавление элементов в магазинную тележку, просмотр содержимого магазинной тележки и обработка окончательного заказа. Некоторые JSP-страницы и сервлеты осуществляют взаимодействие с базой данных в интересах клиента. На информационном уровне приложение использует базу данных books. О том, как создать эту базу данных, будет рассказано в разделе 4.10.1. На рис. 4.1 представлена схема взаимодействий между компонентами приложения. На схеме имена без расширений (displayBook и addToCart) представляют собой псевдонимы сервлетов (т.е. имена, используемые для вызова сервлетов). Как вы узнаете далее, при развертывании приложения в разделе 4.10, в состав реализация Java 2 Enterprise Edition 1.2.1 входит инструментальное средство развертывания Application Deployment Tool. Помимо множества других возможностей, оно позволяет задавать псевдоним, используемый для вызова сервлета. Например, addToCart является псевдонимом для сервлета addToCartServlet. Средство Application Deployment Tool создает дескриптор развертывания для сервлета как часть процесса развертывания приложения. После того как приложение развернуто, пользователи могут посетить книжный магазин, введя следующий URL в адресной строке браузера: http://localhost:8000/advjhtpl/store/ index.html М XHTML-доку мент <^> JSP-страница или сервлет order,html Рис. 4.1. Взаимодействия между компонентами приложения Bug2Bug.com
180 Глава 4 Этот URL представляет собой запрос основной страницы Internet-магазина (index.html). Пользователь может просматривать список товаров (книг), щелкая на кнопке, имеющейся на основной странице. При этом вызывается сценарий faook.jsp, который взаимодействует с базой данных для динамического создания списка книг. Результатом является XHTML-документ, содержащий ссылки на сервлет с псевдонимом display Book, Этот сервлет принимает в качестве параметра ISBN-код для выбранной книги и возвращает XHTML-документ, содержащий информацию об этой книги. В этом документе пользователь может щелкнуть на кнопке, чтобы поместить книгу в магазинную тележку или просмотреть содержимое тележки. Добавление книги в магазинную тележку приводит к вызову сервле- та с псевдонимом addToCart. При просмотре содержимого тележки вызывается сценарий viewCart.jsp, который возвращает XHTML-документ с описанием содержимого тележки, подсчитывает цену в долларах для каждой позиции и общую сумму в долларах по всем позициям в тележке. Когда пользователь добавляет книгу в магазинную тележку, сервлет addToCart обрабатывает запрос пользователя, а затем осуществляет переход к сценарию viewCart.jsp для создания документа, который представляет текущее содержимое тележки. В этот момент пользователь может либо продолжить покупки (books.jsp), либо перейти к подсчету стоимости и оформлению заказа (order.html). В последнем случае пользователю предоставляется форма для ввода имени, адреса и информации о кредитной карте. Затем пользователь отправляет форму, чтобы активизировать сценарий process.jsp, который завершает транзакцию отправкой пользователю подтверждающего документа. В таблице на рис. 4.2 дано краткое описание XHTML-док у ментов, JSP-страниц, сервлетов, компонентов JavaBeans и других файлов, используемых в этом практическом примере. Файл index.html styles.ess books.j sp BookBean.Java TitlesBean.Java Описание ! Это основная страница книжного магазина, испольэуемзя по умолчанию, которая отображается при вводе следующего URL I в Web-бэаузере клиента: http://localhost:8000/advjhtpl/store Этот файл каскадной таблицы стилей (CSS) связан со всеми XHTML-документами, отображаемыми клиентом, файл CSS позволяет применять единообразное форматирование ко всем воспроизводимым статическим и динамическим документам. Эта JSP-страница использует объекты BookBean и объект TitlesBean для создания XHTML-документа, содержащего список товаров. Объект TitlesBean запрашивает базу данных books для получения списка названий книг, имеющихся в базе данных. Результаты обрабатываются и помещаются в список ArrayList объектов BookBean. Список хранится как атрибут сеанса для данного клиента. Экчрмппяр чтого компонента JavaBean содержит данные для одной книги Метод getXML компонента возвращает элемент XML, представляющий книгу. JSP-страницэ books.jsp использует экземпляр этого компонента JavaBear для получения списка ArrayList, содержащего объекты BookBean для каждой книги в базе данных.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 181 Файл BookServlet.Java book.xsl CartltemBean.Java AddToCartServlet.Java viewCart.j sp order.html process,j sp Описание | Этот сервлет (на рис. 4.1 он обозначен под псевдонимом displayBook) получает XML- представление книги, выбранной пользователем, а затем применяет таблицу стилей XSL (XS'.T-трэнсформацию) к XML-коду для получения XHTML-документа, который может бьггь отображен клиентом. В этом примере предполагается, что клиентом является браузер, поддерживающий каскадные таблицы стилей (CSS). 8 последующих рассматриваемых в этой книге примерах для различных типов клиентов применяются различные XSLT-трэнсформэции. Эта таблица стилей XSL определяет, как преобразовать XML-представление книги в XHTML-документ, который может отображаться браузером клиента. Экземпляр этого компонента JavaBean содержит объект Book Bean и количество экземпляров данной книги в магазинной тележке. Эти компоненты хранятся в хэш {объект HashMap), который представляет содержимое магазинной тележки. Этот сервлет (на рис. 4.1 он обозначен под псевдонимом addToCart) обновляет содержимое магазинной тележки. Ёсти тележка не существует, сервлет создает тележку (в данном примере - объект HashMap). Если компонент CartltemBean для этого товара (книги) уже имеется в тележке, сервлет обновляет в компоненте сумму для этой позиции. В противном случае сервлет создает новый компонент CartltemBean с количеством, равным 1. После обновления содержимого тележки пользователь переадресовывается на стэаницу viewCart.jsp для просмотра текущего содержимого тележки. Эта JSP-страница извлекает компоненты CartttemBean из магазинной тележки, подсчитывает стоимость по каждой позиции в тележке, общую сумму для всех позиций в тележке и создает XHTML-документ, который дает возможность клиенту просматривать содержимое тележки в табличном представлении. При просмотре содержимого тележки пользователь щелкает на кнопке Check Out для просмотра бланка заказа (формы). В этом примере форма не несет какого-либо функционального назначения Однако она присутствует, чтобы создать целостную картину приложения. | Эта JSP-страница предназначена для обработки информации о кредитной карте пользователя и создания XHTML-документ, сообщающего, что заказ был обработан, и содержащего общую сумму заказа, J Рис. 4.2. Сервлеты и JSP-страницы, используемые в приложении книжного магазина 4.3. Доступ в Internet-магазин На рис. 4.3 представлена основная страница по умолчанию (index.html) для приложения Bug2Bug.com. Этот файл также называют файлом приветствия. Файл приветствия задается на этапе развертывания приложения (см. раздел 4.10). Если приложение выполняется на вашем компьютере в эталонной реализации Java 2 Enterprise Edition 1.2.1, вы можете ввести следующий TJRL в вашем Web-браузере, чтобы отобразить основную страницу: http://localhost:8000/advjHtpl/atore
182 Глава 4 1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> 4 <!-- index.html —> 5 6 <html xmlns = "http://www.w3.org/1999/xbtml"> 7 8 <head> 9 <title>Shopping Cart Case Study</title> 10 11 <link rel = "stylesheet" href = "styles.ess" 12 type = "text/ess" /> 13 </head> 14 15 <body> 16 <p class = "bigFont">Bug2Bug.com</p> 17 18 <p class = "bigFont italic"> 19 Deitel £amp; Associates, Inc.<br /> 20 Shopping Cart Case Study 21 </p> 22 23 <!-- форма для запроса страницы books.jsp --> 2Л <form method = "get" action = "books.jsp"> 25 <pXinput type = "submit" name = "enterButton" 26 value = "Click here to enter store" /x/p> 27 </form> 28 </body> 29 30 </html> Рис. 4.3. Основная страница книжного Internet-магазина (index.html) В строках 11-12 задается связанная таблица стилей styles.ess (рие. 4.4). Все XIITML-документы, отправляемые клиенту, используют эту таблицу стилей, что позволяет применить единообразное форматирование к документам. Форма в строках 24-27 содержит кнопку submit, которая позволяет войти в магазин. Щелчок на этой кнопке активизирует сценарий books.jsp (рис. 4.7), который создает и воз-
Книжный Internet-магазин, реализованный с использованием сер влетов и JSP 183 вращает XHTML-доку мент, содержащий список имеющихся в магазине товаров (книг). В таблице стилей styles.ess, представленной на рис. 4.4, определены общие стили для отображения XHTML-документов в этом практическом примере. В строках 1-2 задается, что весь текст в элементе body должен быть центрирован, и что в качестве фонового цвета должен использоваться стольной голубой цвет. Фоновый цвет представляется шестнадцатеричным числом #b0c4de. В строке 3 определен класс .bold для применения к тексту жирного начертания. Цвет шрифта — темно-синий (представляется шестнадцатеричным числом #00008Ь). Если шрифт Helvetica недоступен, браузер будет пытаться использовать пхрифт Arial, а затем общеродовой шрифт sans-serif. В строке 8 определен класс -italic для применения курсивного стиля начертания к тексту. В строке 9 определен класс .right для выравнивания текста по правому краю. В строках 10-11 задается, что все элементы table (таблицы), th (данные заголовка таблицы) и td (данные таблицы) должны иметь объемную рамку толщиной три пиксела с внутренним просветом между текстом в ячейке таблицы и рамкой этой ячейки, равным пяти пикселам. В строках 12—14 задается, что все элементы tables должны иметь ярко-голубой цвет фона (представляемый шестнадцатеричным числом #649Sed), и что все элементы tables должны использовать автоматически устанавливаемые поля слева и справа. Тем самым таблица размещается по центру страницы. Не все эти стили используются в каждом из XHTML-документов. Однако применение единой связанной таблицы стилей позволяет легко и быстро изменить внешний вид Internet-магазина путем модификации CSS-файла. Для получения более подробной информации по каскадным таблицам стилей посетите сайт www.v3.org/Style/CSS Здесь вы найдете ряд спецификации по CSS. Каждая спецификация содержит предметный указатель всех используемых на сегодняшний день атрибутов CSS и перечень их допустимых значений. гт с<>ветпо переносимости программ ТчвУЯ Различные браузеры таблиц стилей. имеют различный 4.1 уровень поддержки каскадных 1 body { text-align: center; 2 background-color: #B0C4DE } 3 .bold { font-weight: bold; } 4 .bigFont { font-family: helvetica, arial, sans-serif; 5 font-weight: bold; 6 font-size: 2em; 7 color: #0000SB; ) 8 .italic { font-style: italic; ) 9 .right { text-align: right; } 10 table, th, td ( border: 3px groove; 11 padding: 5px; } 12 table ( background-color: #6495ed; 13 margin-left: atlto; Id margin-right: auto; } ^^ Рис. 4.4. Общая каскадная таблица стилей (styles.css), используемая для применения одинакового форматирования к XHTML-документам, отображаемым клиентом
184 Глава 4 4.4. Получение списка книг из базы данных Страницы JavaServer Pages часто генерируют XHTML-код, который посылается клиенту для отображения. JSP-страница books.jsp (рис. 4.7) генерирует XHTML-документ, содержащий список гиперссылок для получения информации по каждой из книг, имеющейся в таблице titles базы данных books. Из этого списка пользователь может перейти к просмотру информации об определенной книге, щелкая на гиперссылке для этой книги. Эта JSP-страница использует объект TitlesBean (рис. 4.5) и объекты ВоокВеап (рис. 4.6) для создания списка товаров. Каждый из объектов JavaBeans и сценарий books.jsp будут рассмотрены далее в этом разделе. На рис. 4.7 показано, как отображается XHTML-докумеита, отправляемый в браузер сценарием books.jsp. Компонент JavaBean TitlesBean (рис. 4.5) выполняет запрос к базе данных для получиния списка названий книг в базе данных. Затем полученные результаты обрабатываются и помещаются в список ArrayList объектов ВоокВеап. Как мы увидим при рассмотрении кода, представленного на рис. 4.7, список ArrayList хранится сценарием books.jsp как атрибут сеанса для данного клиента. 1 // TitlesBean.Java 2 // Класс TitlesBean устанавливает соединение с базой данных 3 // и извлекает информацию о книгах иэ базы данных. 4 package com.deitel.advjhtpl.store; 5 6 // набор базовых пакетов Java 7 import java.io.*; 8 import java.sql.*; 9 import java.util.*; 10 11 // Пакеты расширений Java 12 import javax.naming.*; 13 import javax.sql.*; Id 15 public class TitlesBean implements Serializable ( 16 private Connection connection; 17 private PreparedStatement titlesQuery; 18 19 // формирование объекта TitlesBean 20 public TitlesBean() 21 { 22 // попытка соединения с базой данных и создание операторов SQL 23 try ( 24 InitialContext ic = new InitialContext(); 25 2 6 DataSource source = 27 ( DataSource ) ic.lookup( 28 "java:comp/env/jdbc/books" ); 29 30 connection = source.getConnection(); 31 32 titlesQuery = 33 connection.prepareStatement( 34 "SELECT isbn, title, editionNumber, " + 35 "copyright, publisherlD, imageFile, price " + 36 "FROM titles ORDER BY title" 37 ) ;
Книжный Internet-магазин, реализованный с использованием сервлетов и J5P 185 38 } 39 40 // обработка исключений при установке базы данных 41 catch ( SQLException sqlException ) { 42 sqlException.printStackTrace() ,-. 43 } 44 45 // обработка исключений при нахождении источника данных 4Б catch ( NamingException namingException } { 47 namingException.printStackTrасе(); 48 } 49 } 50 51 // возврат списка компонентов BookBean 52 public List getTitlesO 53 '{ 54 List titlesList = new ArrayList{) ,- 55 56 // получение списка книг 57 try { 58 ResultSet results = titlesQuery.executeQuery(); 59 60 // получение строки данных 61 while ( results.next() ) { 62 BookBean book = new BookBean(}; 63 64 book.setISBN( results.getString( "isbn" ) J; 65 book.setTitie( results.getString( "title" j ); 66 book.setEditionNumber( 67 results.getlnt( "editionNumber" ) ); 68 book.setCopyright( results.getString( "copyright" ) ); 69 book.setPublisherID( 70 results.getint( "publisheriD" ) ); 71 book.setImageFile( results.getString( "imageFile" ) ); 72 book.setPrice( results.getDouble( "price" ) ); 73 74 titlesList.add( book ); 75 } 76 i 77 78 // обработка исключений при запросе базы данных 79 catch ( SQLException exception ) { 80 exception.printstackTrace(); 61 1 82 83 // возврат списка названий 84 finally { 85 return titlesList; 86 } 87 > 88 89 // закрытие операторов и завершение соединения с базой данных 90 protected void finalize() 92 // попытка закрыть соединение с базой данных 93 try {
186 Глава 4 94 connection. close () ,- 95 ) 96 97 // обработка исключения SQLException при операции закрытия 9в catch ( SQLException sqlException ) { 99 sqlException.printStackTrace(); 100 ) 101 } 102 } Рис. 4.5. Компонент THIesBean для получения информации о книге из базы данных books и создания списка объектов BookBean Чтобы применять компонента JavaBeans TitlesBean, необходимо иметь представление об интерфейсе идентификации и маршрутизации Java Naming and Directory Interface (JNDI). Приложения Enterprise Java часто осуществляют доступ к информации и ресурсам (например, к базам данных), которые являются внешними для этих приложений. В некоторых случаях эти ресурсы являются ресурсами, распределенными в сети. Точно так же, как RMI-клиент использует реестр RMI для нахождения объекта сервера, чтобы клиент мог обратиться с запросом к серверу, компоненты корпоративного приложения должны уметь находить ресурсы, которые они используют. Контейнер приложения Enterprise Java должен предоставлять сервис идентификации, который реализует интерфейс JNDI и дает возможность компонентам, выполняющимся в контейнере, осуществлять поиск соответствующих имен для нахождения ресурсов. Сервер эталонной реализации J2EE 1.2.1 содержит такой сервис идентификации, который будет использоваться для нахождения базы данных books в ходе выполнения приложения. Объект TitlesBean использует JNDI для взаимодействия с сервисом идентификации и нахождения источника данных (т.е. базы данных books). Конструктор TitlesBean (строки 20-49) пытается установить соединение с базой данных, используя класс InitialContext из пакета javax.naming и интерфейс DataSource из пакета javax.sql. [Эти пакеты должны быть доступны компилятору, чтобы иметь возможность откомпилировать данный пример.] При развертывании приложения Enterprise Java (см. раздел 4.10) указываются ресурсы (например, базы данных), необходимые приложению, и имена JNDI для этих ресурсов. Используя класс контекста InitialContext, компонент приложения может найти нужный ресурс. Класс контекста InitialContext предоставляет доступ к окружению идентификации приложения. В строке 24 создается новый объект InitialContext. Конструктор InitialContext возбуждает исключение NamingException, если не может найти источник данных books. В сроках 26—28 вызывается метод lookup класса InitialContext для нахождения источника данных books. Текст java:comp:/env в составе параметра задает, что метод lookup должен осуществлять поиск ресурса в записях окружения компонента приложения (т.е. в списке имен ресурсов, задаваемых при развертывании). Текст jdbc/books задает, что ресурс представляет собой источник данных JDBC под именем books. Метод lookup возвращает объект DataSource и возбуждает исключение NammgException, если он не может вычислить имя, которое получает в качестве параметра. В строке 25 используется объект DataSource для установки соединения с базой данных. В строках 32-37 создается оператор SQL Prepared- Statement, который при выполнении возвращает информацию о каждом названии книги, содержащейся в таблице titles базы данных books. Метод getTitles (строки 52-87) возвращает список titleList, содержащий компоненты JavaBeans BookBean для каждой книги, имеющейся в базе данных. В строке 58 выполняется запрос titlesQuery. В строках 57-76 обрабатывается результирующее множество results (объект класса ResnltSet). Для каждой записи
Книжный lnternet-магззин, реализованный с испальзованием сервлетов и JSP 187 в результирующем множестве results в строке 62 создается новый о&ъегст ВооЬ- Веап, а в строках 64-72 задаются атрибуты объекта ВоокВеап для столбцов в строке результирующего множества. Методы getString, getlnt и getDouble интерфейса ResultSet возвращают данные из столбца в соответствующем формате. В строке 74 в список titleList добавляется новый объект ВоокВеап. Список titleList возвращается в блоке finally. Если в процессе взаимодействий с базой данных возбуждается исключение, или если в базе данных нет запиеей, список будет пуст. Экземпляр объекта JavaBean ВоокВеап (рис. 4.6) представляет свойства для одной книги, включая ISBN-код книги, название, авторские права, имя файла с рисунком обложки, номер издания, идентификатор издателя и цену. Каждое из этих свойств является свойством для чтения/залиеи. Часть этой информации в данном примере не используется. Метод getXML класса ВоокВеап возвращает XML-зле- мент (объект Element), представляющий книгу. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 3d 35 36 37 38 39 40 41 42 // ВоокВеап,Java // Объект ВоокВеап содержит данные для одной книги. package com.deitel.advjhtpl.store; // Набор базовых пакетов Java import j ava.io.*; import j ava,text.*; import java.util.*; // Пакеты сторонних поставщиков import org.w3c.dom.*; public class ВоокВеап implements Serializable { private String ISBN, title, copyright, imageFile; private int editionNumber, publisherlD; private double price; // задание кода ISBB public void setISBN( String isbn } ISBN i sbn ; // возврат кода ISBN public String getISBN() return ISBN; // задание названия книги public void setTitle( String booJcTitle ) title = bookTitle; // возврат названия книги public String getTitle() return title; // задание года издания
public void setCopynght ( String year ) copyright = year; // возврат года издания public String getCopyright() return copyright; // задание имени файла с рисунков обложки public void setlmageFile( String filename ) imageFile = fileName; // возврат имени файла с рисунком обложки public String getImageFile() return imageFile; // задание номера издания public void setEditionNumber( int edition ) editionNumber = edition; // возврат номера издания public int getEditionNumber() return editionNumber; // задание идентификатора издателя public void setPublisherlD( int id ) publisherID = id; // возврате идентификатора издателя public int getPublisherlDO return publisherlD; // задание цены public void setPrice( double amount ) price = amount; // возврат цены public double getPrice() (
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 189 99 return price; 100 } 101 102 // получение XML-представления (сниги 103 public Element getXML( Document document ) 104 { 105 // создание корневого элемента product для товара 106 Element product = document.createElement{ "product" ); 107 108 // создание элемента isbn, добавление его как дочернего элемента для product 109 Element temp = document.createElement( "isbn" ); 110 temp.appendChild( document.createTextNode( getlSBNf) ) ); 111 product.appendChild( temp ); 112 113 // создание элемента title, добавление его как дочернего элемента для product 114 teny = document.createElementt "title" ) ; 115 temp.appendChild( document.createTextNode( getTitle{) ) ); 116 product.appendChild( temp ); 117 118 // создание объекта форматирования денежных единиц для долл.США 119 NumberFormat priceFormatter = 120 NumberFormat.getCurrencyInstance{ Locale.US ); 121 122 // создание элемента price, добавление его ках дочернего элемента для product 123 temp = document.createElement( "price" ); 124 temp.appendChild( document.createTextNode( 125 priceFormatter.format( getPrice() ) ) ); 126 product.appendChild( temp ); 127 128 // опадание элемента imageFile, добавление его ках дочернего элемента для product 129 temp = document.createElement( "imageFile" ); 130 temp.appendChild( 131 document.createTextNode( getlmageFile() ) ); 132 product.appendChild( temp ); 133 134 // создание элемента copyright, добавление его как дочернего элемента для product 135 temp = document.createElement( "copyright" ); 136 temp.appendChild( 137 document.createTextNode( getCopyrightf) ) ) ; 138 product.appendChild( temp ); 139 140 // создание элемента publisherlD, добавление его как дочернего элемента для product 141 temp = document.createElement', "publisherlD" ) ; 142 temp.appendChild( document.createTextNode( 143 String.valueOf( getPublisherlDO ) ) ); 144 product.appendChild( temp ); 145 146 // создание элемента editionMumber, добавление его как дочернего элемента для product 147 temp = document.createElement( "editionNumber" );
190 Глава 4 14в temp.appendChild( document.createTextNode[ 149 String.valueOf( getEditionNumber() ) ) ); 150 product.appendChild( temp ); 151 152 // возврат элемента product 153 return product; 154 } 155 } __ ____ Рис. 4.6. Класс ВоокВеап, представляющий информацию об одной книге и определяющий формат XML для этой информации Метод getXML (строки 103-154) использует интерфейсы Document и Element из пакета org.w3c.dom для создания XML-представления данных для книги как часть документа (объекта Document), которая передается методу в качестве параметра. Полная информация для одной книги помещается в элемент product (созданный в строке 106). Элементы для индивидуальных свойств книги добавляются в элемент product как дочерние. Например, в строке 109 используется метод create Element интерфейса Document для создания элемента Isbn. В строке 110 используется метод createTextNode интерфейса Document для задания текста в элементе isbn и метод appendChild интерфейса Element для добавления текста в элемент isbn. Затем, в строке 111 элемент isbn добавляется как дочерний в элемент products с помощью метода appendChild интерфейса Element. Схожие операции выполняются и для других свойств книги. В строках 119-120 осуществляется получение объекта Number-Format, который форматирует денежные единицы для отображения цены книги в долларах США (строка 125). В строке 150 элемент product возвращается вызвавшему процессу. Мы вновь обратимся к методу getXML в ходе рассмотрения сервлета BookServlet (рис. 4.8). JSP-страница books.jsp динамически генерирует список названий книг как XHTML-документ, отображаемый клиентом. В строках 7—11 задаются параметры JSP-страницы. Эта страница использует классы из пакета com.deitel.advjhtpl.store и пакета java.util. Данная JSP-страница также использует возможности отслеживания состояния сеанса. Динамические составляющие этой JSP-страницы определены в строках 31-64 с помощью сценариев JSP и выражений. 1 <?xml version = "1.0"?> 2 <»DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtmll/QTD/jehtmll-strict.dtd"> 4<!— books.jsp --> 5 6 <%-- параметры JSP-страницы —%> 7 <%@ 8 page language = "java" 9 import = "com.deitel.advjhtpl.store.*, java.util.*" 10 session = "true" 11 %> 12 13 <!-- начало документа —> 14 <html xmlns = "http://www.w3.org/1999/xhtml"> 15 16 <head> 17 <title>Book List</title> 18 19 <link rel = "stylesheet" href = "styles.ess" 20 type = "text/ess" />
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 191 21 </head> 22 23 <body> 24 <р class = "bigFont">Available Books</p> 25 26 <p class = "bold">Click a link to view book in£ormation</p> 27 28 <p> 29 30 <%-- начало екриптлета JSP для создания списка книг --%> 31 <% 32 TitlesBean titlesBean = пен TitlesBean () ; 33 List titles = titlesBean.getTitlesО; 34 BookBean currentBook; 35 36 // сохранение названий в данных сеанса для последующего исп оль зов ания 37 session.setAttribute( "titles", titles ); 38 39 // получение итератора для набора ключей в списке List 40 Iterator iterator = titles.iterator(); 41 42 // использование итератора для получения каждого 43 // компонента BookBean и создания ссылки для каждой из книг 44 while ( iterator.hasNext() ) { 45 currentBook. = ( BookBean ) iterator.next(); 46 47 %> <%-- завершение скриптлета для вставки литерала XHTML --%> 48 <%— и выражений JSP, вычисленных в этом чикле --%> 49 50 <%— ссылка на информации о книге —%> 51 <span class = "bold"> 52 <а href = 53 "displayBook?isbn=<%= currentBook.getISBN() %>"> 54 55 <%= currentBook.getTitie() + ", " + 56 currentBook.getEditionHuiriber 0 + "e" %> 57 </a> 58 </spanXbr /> 59 60 <% // продолжение екриптлета 61 62 } // конец цикла while 63 64 %> <%-- конец екриптлета --%> 65 66 </р> 67 </body> 68 69 </html> __ Рис. 4.7. JSP-страница books.jsp, возвращающая клиенту XHTML-документ, содержащий список книг (часть 1)
192 Глава 4 Рис. 4.7. JSP-страница books.jsp, возвращающая клиенту XHTML-доку мент, содержащий список книг (часть 1) Фрагмент кода сценария (скриптлет) начинается в строке 31. В строке 32 создается объект TitlesBean, а в строке 33 вызывается его метод get Titles для получения списка объектов BookBean. В строке 37 задается атрибут сеанса titles для хранения списка, который в дальнейшем будет использоваться в клиентском сеансе. В строке 40 осуществляется получение итератора Iterator для списка List. В строках 44-45 начинается цикл, который использует итератор для вывода каждой из гиперссылок. Скриптлет здесь временно завершается, чтобы дать возможность вставить XHTML-разметку (строки 51-58). В этой разметке в строке 53 используется выражение JSP для вставки ISBN-кода в качестве значения в паре имя/значение, которая передается сервлету displayBook (объект класса BookServlet) в качестве параметра. В строках 55-56 используется другое выражение JSP для вставки названия книги и номера издания в качестве текста, отображаемого в гиперссылке. В строках 60-64 скриптлет продолжается закрывающей фигурной скобкой цикла while, начатого в строке 44. 4.5. Просмотр информации о книге Подобно многим другим компаниям, компания Bng2Bug.com начала активно использовать на своем Web-сайте XML. Когда пользователь выбирает книгу на странице books.jsp, приложение Bug2Bug.com преобразует информацию о книге в XML-код. Сервлет BookServlet (рис. 4.S) трансформирует XML-представление книги в XHTML-документ с помощью XSL-таблицы стилей book.xsl (рис. 4.9).
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 193 Метод doGet объекта BookServlet (строки 24-103) состоит из двух основных частей. Е строках 28—62 осуществляется поиск объекта ВоокВеап для книги, вы бранной пользователем на странице books.jsp. В строках 65-102 обрабатывается XML-представление книги и применяется XSLT-трансформация. 1 // BookServlet.java 2 // Сервлет для возврата клиенту информации об одной книге. 3 // Сервлет выдает XML-код, который преобразуется с помощью 4 // XSL для формирования XHTML-страниц», отображаемой клиентом. 5 package com.deitel.advjhtpl.store; 6 1 I/ Набор базовых пакетов Java 8 import java.io- *; 9 import java.util.*; 10 11 // Пакеты расширений Java 12 import javax.servlet.*; 13 import javax.servlet.http.*; 14 import javax.xml.parsers.*; 15 import j avax.xml.transform.*; 1 б import j avax.xml.trans form.dom.*; 17 import javax.xml.transform.stream.*; 18 19 // Пакеты сторонних поставщиков 20 import org.w3c.dom.*; 21 import org.xml.sax.*; 22 23 public class BookServlet extends HttpServlet { 24 protected void doGet{ HttpServletRequest request, 25 HttpServletResponse response ) 26 throws ServletException, IOException 27 { 28 HttpSession session = request.getSession( false ); 29 30 // диспетчер RequestDispatcher для переадресации клиента на 31 // основную страницу, если сеанс не существует, или ни одна из книг не выбрана 32 RequestDispatcher dispatcher = 33 request.getRaquestDispatcher( "/index.html" ); 34 35 // если сеанс не существует, переадресация к index.html 36 if ( session = null ) 3"? dispatcher .forward! request, response ) ; 38 39 // получение книг из объекта сеанса 40 List titles = 41 < List ) session.getAttribute( "titles" ); 42 43 // нахождение компонента ВоокВеап для выбранной книги 44 Iterator iterator = titles.iterator(); 45 ВоокВеап book = null; 46 47 String isbn = request, get Parameter ( "isbn" ) ,- 48 49 while ( iterator.hasNextO ) { 50 book = ( ВоокВеап > iterator.next();
194 Глава 4 51 52 if ( isbn.equals< book.getlSBN() ) ) { 53 54 // сохранение книги в атрибуте сеанса 55 session.setAttribute( "bookToAdd", book ); 56 break; // isbn-код для текущей книот* 57 > 58 } 59 60 // если книги кет в списке, переадрееация к index.html 61 if ( book = null > 62 dispatcher.forward{ request, response >; 63 64 // получение XML-дакумента и преобразование его для просмотра клиентом 65 try { 66 // получение объекта DocumentBuilderFactory для создания 67 // синтаксического анализатора XHL DocumentBuilder 68 DocumentBuilderFactory factory = 69 DocumentBuilderFactory.newlnstanceО; 70 71 // получение объекта DoeumentrRiii lder для построения дерева DCH 72 DocumentBuilder builder = 73 factory.newDocumentBuilder(); 74 75 // создание нового объекта Document (пустое дерево DOM) 76 Document messageDocument = builder.newDocumentО; 77 78 // получение XHL из объекта BookBean и добавление в объект Document 79 Element bookElement = book.getXML{ messageDocument ) ; 80 messageDocument.appendChild( bookElement ); 81 82 // получение объекта PrintWriter для записи данных клиенту 83 response.setContentType( "text/html" ); 84 PrintWriter out = response.getWriter(); 85 86 // открытие потока ввода InputStream для XSL-документа 87 InputStream xslStream = 88 getServletContext().getResourceAsStream( 89 "/Ьоок.кз!" ); 90 91 // преобразование XML-документа с использованием XSLT 92 transform( messageDocument, xslStream, out ); 93 94 // очистка и закрытие объекта PrintWriter 95 out.flush(); 96 out.close(); 97 ) 98 99 // перехват исключений от синтаксического анализатора XML 100 catch ( ParserConfigurationException pcException ) { 101 pcException.printStackTrace(>; 102 ) 103 } 104
Книжный Internet-магазин, реализованный с использованием сервлетов и J5P 195 105 // преобразование XML-документа с использованием потока ввода 106 // XSLT InputStream и запись полученного документа в объект PrintWriter 107 private void transform( Document document, 108 InputStream xslStream, PrintWriter output ) 109 { 110 try { 111 // создание объекта DOMSource для исходного XML-документа 112 Source xmlSource = new DOMSource( document ); 113 114 // создание объекта StreamSource для XSLT-документа 115 Source xslSource = 116 new StreamSource( xslStream ); 117 118 // создание объекта StreamResult пня результата трансформации 119 Result result = new StreamResult( output ); 120 121 // создание объекта TransformerFactory для получения объекта Transformer 122 TransformerFactory transformerFactory = 123 TransformerFactory.newlnstance(}; 124 125 // создание объекта Transformer для выполнения XSLT-трансформачип* 126 Transformer transformer = 127 transformerFactory.newTransformer( xslSource ); 128 129 // выполнение трансформации и доставка содержимого клиенту 130 transformer.transform( xmlSource, result ); 131 J 132 133 // обработка исключения при трансформации XML-документа 134 catch ( TransformerSxception transformerException ) { 135 transformerException.printStackTrace( System.err }; 136 ) 137 } 138 1 _____ Рис. 4.8. Сервлет BookServiet получает ХМ!_-предста8ление книги и применяет XSLT-трзнсформацию для вывода XHTML-документа в ответ на запрос клиента В строке 28 осуществляется получение объекта HttpSession для текущего клиента. Этот объект содержит атрибут сеанса, указывающий на книгу, выбранную пользователем на странице books,jsp. В строках 32-33 осуществляется получение диспетчера запросов Requ.estDispatch.er для документа "/index.html" путем вызова метода getRcquestDispatcher класса ServletRcquest. Класс RequestDispatcher (пакет javax.scrvlet) предоставляет два метода — forward и include — которые дают возможность сервлету переадресовывать клиентский запрос другому ресурсу или включать в ответ сервлета содержимое из другого ресурса- В этом примере в случае, если для текущего клиента объект сеанса не создан (строки 36-37), или если никакая книга не выбрана (строки 61-62), запрос пересылается обратно на основную страницу index.html книжного магазина. Каждый из методов forward и include принимает два параметра: объект HttpServletRequest и объект Http- ServletRespon.se для текущего запроса.
196 Глава 4 Заметим, что объект RequestDispatcher может быть получен с помощью метода getRequestDispatcher из объекта, реализующего интерфейс SorvletKequest, или из объекта ServletContext с помощью методов getRequestDispatcher или getNa- medDispatcher. Метод getNamedDispatcher интерфейса ServletContext принимает в качестве параметра имя сервлета, а затем ищет объект контекста ServletContext для сервлета с указанным именем. Если такой сервлет не найден, метод возвращает null. Методы getRequestDispatcher как класса ServletKequest, так и интерфейса ServletContext просто возвращают клиентскому браузеру содержимое заданного пути, если путь не указывает на сервлет, В строках 40—41 из объекта сеанса извлекается список (List) объектов Book- Bean. В строках 44-58 осуществляется линейный поиск для нахождения объекта BookBean для выбранной книги. (Замечание. Для больших баз данных удобнее использовать в качестве списка объект типа Map, а не List.) ISBN-код для этой книги хранится в параметре isbn, передаваемом сервлету (извлекается в строке 47). Если объект BookBean найден, в строке 55 этот объект BookBean устанавливается в качестве значения атрибута сеанса bookToAdd. Сервлет AddToCartServIet (рис. 4.10) использует этот атрибут для обновления содержимого магазинной тележки. Блок try (строки 65-97) выполняет обработку кода XML и XSL, в результате чего формируется XHTML-документ, содержащий информацию об одной книге. Прежде, чем воспользоваться возможностями XML и XSL, необходимо загрузить и установить программное средство Sun Java API for XML Parsing (JAXP) версии 1.1 со страницы java.sun.com/xml/download.htm. Корневой каталог JAXP (jaxp-1.1) содержит три JAR-файла — crimson.jar, jaxp.jar и xalan.jar — которые необходимы для компиляции и выполнения программ, использующих JAXP. Эти файлы должны быть добавлены к средствам расширения Java 2 Standard Edition. Поместите копии этих файлов в каталог расширений Java, (jre/lib/ext в Linux/UNIX и jre\ lib\ext в Windows.) Общая методическая рекомендация 4.1 JAXP 1.1 является составной частью эталонной реализации J2EE 1.3. Для создания из XML-документа дерева объектной модели документа (DOM) требуется объект синтаксического анализатора DocumentBuilder. Объекты Docu- mentBuilder создаются мастером DocumentBuilder Factory. В строках 68 69 осуществляется получение объекта мастера DocumentBuilderFactory. В строках 72-73 получается объект синтаксического анализатора DocumentBuilder, который дает возможность программе создать дерево объекта Document, в котором элементы XML-документа представляются объектами Element. В строке 76 используется объект DocumentBuilder для создания нового документа (объекта Document). В строке 79 вызывается метод getXML компонента BookBean для получения представления элемента (объект Element) книги. В строке 80 этот элемент добавляется в документ messageDocument (объект Document). Классы DocumentBuilderFactory и DocumentBuilder содержатся в пакете javax.xml.parsers. Классы Document и Element содержатся в пакете org.w3c.dom. Далее, в строке 83 задается тип содержимого ответа, а в строке 84 получается объект PriniWriter для вывода ответа клиенту. В строках 87-89 создается поток ввода InputStream, который будет использоваться процессором XSLT для чтения XSL-файла. Ответ создается с помощью XSLT-трансформации, выполняемой методом transform (строки 107-137). Этому методу передается три параметра: XML-документ, к которому будет применена XSLT-транс формация (messageDocument), поток ввода, который считывает XSL-файл (xslStream), и целевой поток, в который будут записываться результаты (out). Целевой поток вывода может быть раз-
Книжный Internet-магазин, реализованный с использованием сер влетов и JSP 197 личным, в том числе символьным потоком (как поток response объекта Print- Writer в данном примере). В строке 112 создается объект DOMSource, который представляет XML-Доку- мент. Этот объект служит в качестве источника XML-кода для XSLT-трансформации. В строках 115-116 создается объект StreamSource для XSL-файла. Он содержит исходный XSL-код, который используется для трансформации документа DOMSource. В строке 119 создается объект StreamResult для потока PrintWriter, в который записываются результаты XSLT-трансформации. В строках 122-123 с помощью статического метода newlnstance создается объект TransformerFac- tory. Этот объект дает возможность программе получать объект Transformer, который применяет XSLT-трансформацию. В строках 126-127 с помощью метода newTransformer класса Transform erF ас tor у создается объект Transformer, который принимает параметр StreamSource, представляющий собой XSL-таблицу стилей (в данном примере — xslSource). В строке 130 вызывается метод transform объекта Transformer для выполнения XSLT-трансформации над заданным объектом DOMSource (xmlSource) и записи результата в заданный объект StreamResult (result). В строках 134-136 перехватывается исключение TransformeTExceptioii, если возникает проблема при создании объекта TransformerFactory, создании объекта Transformer или выполнении трансформации. На рис. 4,9 представлен файл таблицы стилей book.xsl, используемой при XSLT-трансформации. В результирующий XHTML-документ помещаются значения шести элементов XML-документа. В строке 23 название книги помещается в элемент title документа, а в строке 30 название книги помещается в первый абзац элемента body документа. В строке 36 определяется элемент img, в котором значение элемента imageFile XML-документа задает имя файла, содержащего изображение обложки книги. В строке 37 определяется атрибут alt элемента img с ис- нолъзованием названия книги title. В строках 43, 51, 59 и 67 элементы price, isbn, editionNumbcr и copyright помещаются в ячейки таблицы. Полученный XHTML- документ представлен на копии экрана на рис. 4.9. 1 <?xml version = "1.0"?> 2 3 <xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" 4 version = "1.0"> 5 6<xsl:output method = "xml" omit-xml-declaration = "no" 7 indent » "yes" doctype-system = 8 "http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd" 9 doctype-public = ."-//W3C//DTD XHTML 1.0 Strict//EN"/> 10 11 <!— book.xsl —> 12 <!-- XSL-докумект, который преобразует XML в XHTML —> 13 14 <!-- задание корня XML-документа, —> 15 <<-- на который ссылается эта таблица стилей —> 16 <xsl:template match = "product"> 17 18 <html xrolns = "http://www.w3.org/1999/xhtml"> 19 20 <head> 21 22 <!— получение названия книги от JSP-страницы для помещения его в заголовок —> 23 <titleXxsl:value-of select = "title"/x/title> 24
25 <link rel = "stylesheet" href = "styles.ess" 26 type = "text/ess" /> 27 </head> 26 29 <body> 30 <p class = "bigFont"Xxsl:value-of select = "title"/x/p> 31 32 <table> 33 <tr> 34 <•-- создание ячейки таблицы для рисунка обложки —> 35 <td rowspan = "5"> <!— ячейка занимает 5 строк --> 36 <img style = "border: thin solid black" sre = 37 "images/{ imageFile }" alt = "\ title }" /> 38 </td> 39 40 <!-- создание ячеек таблицы для отображения цены в строке 1 --> 41 <td class = "bold">Price:</td> 42 43 <tdXxslrvalue-of select = "price "/X/td> 44 </tr> 45 46 <tr> 47 48 <!— создание ячеек таблицы для ISBN-кода в строке 2 - 49 <td class = "bold">ISBN #:</td> 50 51 <tdXxsl:value-of select = "isbn"/X/td> 52 </tr> 53 54 <tr> 55 56 <<— создание ячеек таблицы для отображения номера издания в строке 3 —> 57 <td class = "bold">Edition:</td> 58 59 <tdXxsl:value-of select = "editionHumber"/x/td> 60 </tr> 61 62 <tr> 63 64 <!— создание ячеек таблицы для отображения года издания в строке 4 --> 65 <td class = "bold">Copyright:</td> 66 67 <tdXxsl:value-o£ select = "copyright"/X/td> 68 </tr> 69 70 <tr> 71 72 <!-- создание кнопки добавления книги в тележку в строке 5 --> 73 <td> 7 4 <fonn method = "post" action = "addToCart"Xp> 75 <input type = "submit" value - "Add to Cart" /> 76 </pX/form>
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 199 77 </td> 7В 79 <!— создание в строке 5 кнопки для просмотра содержимого тележки —> 80 <td> 81 <foxm method = "get" action = "viewCart. jsp"Xp> 82 <input type = "submit" value = "View Cart" /> 83 </pX/form> 84 </td> 85 </tr> 86 </table> 87 88 </body> 89 90 </htxnl> 91 92 </xsl:template> 93 94 </xsl:sty!esheet> _^^^___^_^ Рис. 4.9. Таблица сталей XSL (books.xsl), которая трансформирует XML-представление книги в XHTML-документ 4.6. Добавление элемента в магазинную тележку Когда пользователь щелкает на кнопке Add To Cart в XHTML-документе, сформированном в предыдущем разделе, сервлет AddToCartServIet (он имеет псевдоним addToCarf) обновляет содержимое магазинной тележки. Если тележка не существует, сервлет создает ее (в данном примере — объект HashMap). Товары в ма-
200 Глава 4 газинной тележке представлены компонентами CartltemBean. Экземпляр этого компонента JavaBean содержит объект BookBean и текущее количество экземпляров для данной книги в магазинной тележке. При добавлении' пользователем книги в тележку в случае, если такая книга уже имеется в тележке, компонент CartltemBean, обновляет количество экземпляров этой книги в составе данного компонента. В ином случае сервлет создает новый компонент CartltemBean с атрибутом количества, равным 1. После обновления содержимого тележки пользователь переадресовывается к странице viewCart.jsp для просмотра текущего содержимого тележки. Класс CartltemBean (рис. 4.10) содержит объект BookBean и количество экземпляров для этой книги. Он хранит объект BookBean как свойство компонента, допускающее только чтение, и переменную quantity как свойство компонента, допускающее запись. 1 // CartltemBean.java 2 // Класс, который хранит информацию о книге и ее количестве. 3 package com.deitel.advjhtpl.store; 4 5 import java.io.*; 6 7 public class CartltemBean implements Serializable -I 8 private BookBean book; 9 private int quantity; 10 11 // инициализация объекта CartltemBean 12 public CartltemBean( BookBean bookToAdd, int number ) 13 { 14 book = bookToAdd; 15 quantity = number; 16 ) 17 IB // получение книги (это свойство только для чтения) 19 public BookBean getBookO 20 { 21 return book; 22 } 23 24 // задание количества 25 public void setQuantity( int number ) 26 ( 2 7 quantity = number; 28 } 29 30 // получение количества 31 public int getQuantityO 32 { 33 return quantity; 34 } 35) Рис. 4.10. Компоненты CartltemBean содержат объект BookBean и количество экземпляров книги (quantity) в магазинной тележке Класс AddToCartServlet представлен на рис. 4.11. Метод doPost класса AddTo- CartServlet получает объект httpSession для текущего клиента {строка 18). Если сеанс для этого клиента не существует, диспетчер запросов RequestDispatchcr пе-
Книжный Internet-магазин, реализованный с использованием сервлетов и J5P 201 реадресовывает аялрос основной странице книжного магазина index.html (строки 22-26), В противном случае в строке 29 извлекается значение атрибута сеанса cart — объект типа Map, который представляет магазинную тележку. В строках 30-31 извлекается значение атрибута сеанса bookToAdd. Этим атрибутом является объект BookBean, представляющий книгу, добавляемую в магазинную тележку. Если магазинная тележка не существует, в троках 34—39 создается новый объект типа HashMap для хранения содержимого тележки, а затем этот объект HashMap помещается в атрибут cart объекта session. В строках 42—43 осуществляется попытка найти компонент CartltemBean для книги, добавляемой в тележку. Если таковой существует, в строке 48 инкрементирует количество экземпляров для этого компонента. В противном случае в строке 50 создается новый компонент CartltemBean с количеством, равным 1, и помещается в магазинную тележку (объект cart типа Map). Затем в строках 53-55 создается диспетчер запросов RequestDispatcher для JSP-страницы viewCart.jsp, и обработка запроса переадресовывают этой JSP-странице, которая отображает содержимое тележки. 1 // AddToCartServlet.Java 2 // Сервлет для добавления книги в магазинную тележку. 3 package com.deitel.advjhtpl.store; 4 5 // Набор базовых пакетов Java 6 import java.io.*; 7 import Java.Util.*; 8 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http.*; 12 13 public class AddToCartServLet extends HttpServlet { Id protected void doPost( HttpServletRequest request, 15 HttpServletRespOnse response ) 16 throws ServletException, lOException П { 18 HttpSession session = request.getSession( false ); 19 RequestDispatcher dispatcher; 20 21 // если сеанс не существует, переадресация к странице index.html 22 if ( session == null ) { 23 dispatcher = 24 request.getRequestOispatcher{ "/index.html" ); 25 dispatcher.forward( request, response ); 26 } 27 29 // если сеанс существует, подучить объект HashMap для тележки и книгу для добавления 29 Map cart = ( Map ) session.getAttribute( "cart" ); 30 BookBean book = 31 ( BookBean ) session.getAttributs( "bookToAdd" ); 32 33 // если тележка не существует, создать ее 34 if { cart = null ) { 35 cart = new HashMap О ; 36 37 // задание атрибута сеанса cart 38 session,setAttribute( "cart", cart ) ;
202 Глава 4 39 } 40 41 // определение, имеется ли книга в тележке 42 CartltemBean cartltem = 43 ( CartltemBean ) cart.get( book.getlSBN{) >; 44 45 // Если книга уже имеется в тележке, обновить ее количество. 46 //Б противном случае, создать элемент для помещения в тележку. 47 if { cartltem != null ) 48 cartltem.setQuantity( cartltem.getQuantityO + 1 ); 49 else 50 cart.put( book.getISBN(), new CartltemBeaM book, 1 ) ); 51 52 // отправка страницы viewCart.jsp пользователю 53 dispatcher = 54 request.getRaquestDispatcher( "/viewCajrt.jsp" ); 55 dispatcher.forward! request, response ); 56 } 57) Рис. 4.11. Сервлет AddToCarrServlet помещает товар е магазинную тележку и вызывает сценарий viewCart.jsp для отображения содержимого тележки 4.7. Просмотр содержимого магазинной тележки JSP-страница viewCart.jsp (рис. 4.12) извлекает компоненты CartltemBean из магазинной тележки, подсчитывает стоимость для каждого товаров в тележке, вычисляет суммарную стоимость всех товаров в тележке и создает XHTML-доку мент, который дает возможность клиенту просматривать содержимое тележки в табличном формате. Эта JSP-страница использует классы из пакета com.deitel.advjhtpl.store и из пакетов java.util и Java,text. Скриптлет в строках 25-43 извлекает атрибут сеанса для объекта магазинной тележки (строка 26). Если магазинной тележки не существует, JSP-страница выводит сообщение, указывающее, что тележка пуста. В противном случае в строках 34-41 создаются переменные, используемые для получения информации, которая отображается в результирующем XHTML-документе. В частности, в стропе 34 осуществляется получение набора ключей, содержащихся в объекте-списке cart. Эти ключи используются для извлечения объектов CartltemBean, которые представляют каждую из книг в тележке. В строках 45-51 осуществляется вывод XHTML-разметки, которой начинается таблица, фигурирующая в документе. В строках 55-63 скриптлет продолжается циклом, который использует каждый из ключей, содержащихся в объекте-списке cart, для получения соответствующего компонента CartltemBean, извлекает данные из него, вычисляет стоимость в долларах для этого товара и вычисляет общую стоимость в долларах всех имеющихся в данный момент в тележке товаров (книг). Окончание цикла находится вне тела скриггтлета в строках 70-86. Здесь выполняется форматирование ранее полученных данных и помещение их в строку XHTML-таблицы. Для помещения каждого значения данных в соответствующую ячейку таблицы используются выражения JSP. После окончания цикла (строка 90) в строках 95-100 выводится суммарная стоимость в долларах всех книг в тележке, а в строке 105 устанавливается атрибут сеанса, содержащий сумму. Это значение используется сценарием process.jsp (рис. 4.14) для отображения суммарной стоимости в долларах в сообщении, подтверждающем обработку заказа.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 203 В строке 101 осуществляется зывод в последнюю строку XHTML-таблицы суммарной стоимости в долларах всех книг, имеющихся в тележке. 1 <?xml version = "1.0"?> 2 <!D0CTYPE html PUBLIC "-//H3C//DTD XHTML 1.0 Strict//EN" 3 "http:/ /www .w3 . org/TR/xhtmll/DTD/xntmll-strict. dtd"> 4 <!— viewCart.psp --> 5 6 <%-- параметры JSP-етраницы --%> 7 <%@ page language = "java" session = "true" %> 8 <%@ page import = "com.deite1.advjhtpl.store.*" %> 9 <%9 page import = "java.util. *'' %> 10 <%@ page import = "Java.text.*" %> 11 12 <html xmlns = "http://www.w3.org/1999/xhtml"> 13 14 <head> 15 <title>Shopping Cart</title> 16 17 <linJc rel = "stylesheet" href = "styles.ess" 18 type = "text/ess" /> 19 </head> 20 21 <body> 22 <p class = "bigFont">Shopping Cart</p> 23 24 <%-- начало скриптлета для отображения содержимого магазинной тележки —%> 25 <% 26 Map cart = { Map ) session.getAttribute{ "cart" ); 27 double total = 0; 28 29 if ( cart = null |[ cart.size() == 0 ) 30 out.println( "<p>Shopping cart is currently empty.</p>" ); 31 else { 32 33 // создание переменных, используемых при отображении содержимого тележки 34 Set eartltems = cart.keySetО; 35 Iterator iterator = eartlterns.iterator(); 36 37 BOOkBean book; 38 cartltemBean cartltem; ЗЭ 40 int quantity; 41 double price, subtotal; 42 43 %> <%-- завершение скриптлета для вывода литерала XHTML -- %> 44 45 <table> 46 <theadxtr> 47 <th>Produet</th> 48 <th>Quantity</th> 4 9 <th>Priee</th> 50 <th>Total</th> 51 </tr></thead>
204 Глава 4 52 53 <% // продолжение скриптлета 54 55 while ( iterator.hasKextО ) { 56 5*7 // получение данных о книге; вычисление промежуточных и итоговых сумы 58 cartrtem = ( CartltemBean ) cart.get( iterstor.next О )/ 59 book = cartltern.getBook(); 60 quantity = cartltem.getQuantity(); 61 price = book.getPrice(); 62 subtotal = quantity * price; 63 total += subtotal; 64 65 %> <%— Завершение скриптлета для вывода XHTML-кода и —%> 66 <%—'выражений JSP в этом цикле —%> 67 68 <%-- отображение строки таблицы с названием книги, —%> 69 <%-- количеством, ценой и промежуточной суммой --%> 70 <tr> 71 <tdx%= book.getTitlet) %X/t<J> 72 73 <tdX%= quantity %x/td> 74 75 <td class = "rights 7 6 <%= 77 new DeeimalFomiat( "0.00" ).format( price ) 78 %> 79 </td> 80 81 <td class = "boid right"> 82 <%= 83 new DacimalForaatl "0.00" ).format( subtotal ) 84 %> 85 </td> 86 </tr> 87 88 <% // продолжение скриптлета 89 90 f // конец цикла while ' 91 92 %> <%— завершение скриптлета для литерала XHTML и —%> 93 94 <%— отображение строки таблицы, содержащей общую стоимость --%> 95 <tr> 96 <td eolspan = "4" class = "bold right">Total: 97 <%= new DeciatalFonnat ( "0,00" ).fonnat( total ) %> 98 </td> 99 </tr> 100 </table> 101 102 <% // продолжение скриптлета 103 104 // запись текущей суммы в атрибут сеанса 105 session. setAt tribute ( "total", new Doublet total ) ) ,- 106 } // конец блока else
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 205 107 108 %> <%-- конец екришглсгса --%> 109 110 <!-- ссыпка для возврата к books.jsp и продолжения покупок —> 111 <p class = "bold green"> 112 <а href = "books.jsp">Continue Shopping</a> 113 </p> 114 115 <!-- фориа для полечена общей стоимости —> 116 <£orm method = "get" action = "order.html"> 117 <pXinput type = "submit" value = "Check Out" /X/p> 118 </form> 119 </body> 120 121 </html> Рис. 4.12, JSP-страница viewCart.jsp получает объект магазинной тележки и выводит XHTML-документ, отображающий с содержимое тележки в табличном формате Просматривая XHTML-док у мент, сформированный этой JSP-страницей, пользователь может либо продолжить покупку, либо щелкнуть на кнопке Check Out, чтобы перейти к странице оформления заказа order.html (рис. 4.13). * 4.8. Подсчет стоимости и оформление заказа При просмотре содержимого тележки пользователь может щелкнуть на кнопке Check Out, чтобы перейти к странице подсчета общей стоимости и оформления заказа order.html (рис. 4.13). В этом примере форма не несет каких-либо функциональных возможностей, однако она помогает создать целостную картину приложения. Обычно в реальных приложениях предусмотрена определенная проверка элементов формы на стороне клиента, определенная проверка элементов формы на стороне сервера или сочетание этих проверок. Когда пользователь щелкает на кнопке Submit, браузер запрашивает страницу process.jsp для окончательной об работки заказа.
1 <?xml version = "1.0"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EH" 3 "http://www.w3.org/TR/xhbnll/DTD/xhtmll-strict.dtd"> 4<!-- order.html —> 5 6 <html xmlns = "http://www.w3.org/1999/xhtml"> 7 8 <head> 9 <title>Order</title> 10 11 <link rel = "stylesheet" href = "styles.ess" 12 type = "text/ess" /> 13 </head> 14 15 <body> 16 <p class = "bigFont">Shopping Cart Check Out</p> 17 18 <!— Форма для ввода сведений о пользователе и о его кредитной карте. --> 19 <'-- Примечание: Б этом примере реальные данные вводить не нужно. —> 20 <form method = "post" action = "process.jsp"> 21 22 <p style = "font-weight: bold"> 23 Please input the following information.</p> 24 25 <F— таблица элементов формы --> 26 <table> 27 <tr> 28 <td class = "right bold">First name:</td> 29 30 <td> 31 <input type = "text" name = "firstname" 32 size = "25" /> 33 </td> 34 </tx> 35 36 <tr> 37 <td class = "right bold">Last name:</td> 38 39 <td> 40 <input type = "text" name = "lastname" 41 size = "25" /> 42 </td> 43 </tr> 44 45 <tr> 46 <td class = "right bold">Street:</td> 47 48 <td> 49 <input type = "text" name = "street" size = "25" /> 50 </td> 51 </tr> 52 53 ' <tr> 54 <td class = "right bold">City:</td>
Книжный Internet-магазин, реализованный с использованием сервлетоа и JSP Z07 55 56 <td> 57 <input type = "text" name = "city" size = "25" /> 58 </td> 59 </tr> 60 61 <tr> 62 <td class = "right bold">State:</td> 63 64 <td> 65 <input type = "text" name - "state" size = "2" /> 66 </td> 67 </tr> 68 69 <tr> 70 <td class = "right bold">Zip code:</td> 71 72 <td> 73 <input type = "text" name = "zipcode" 74 size = "10" /> 75 </td> 76 </tr> 77 78 <tr> 79 <td class = "right bold">Phone #:</td> 80 81 <td> 82 ( 83 <input type = "text" name = "phone" size = "3" /> 84 ) 85 86 <input type = "text" name = "phone2" 87 size = "3" /> - 88 89 <input type = "text" name = "phone3" size = "4" /> 90 </td> 91 </tr> 92 93 <tr> 94 <td class = "right bold">Credit Card #:</td> 95 96 <td> 97 <input type = "text" name = "creditcard" 98 size = "25" /> 99 </td> 100 </tr> 101 102 <tr> 103 <td class = "right bold">Expiration (шш/уу):</td> 104 105 <td> 106 <input type = "text" name = "expires" 107 size = "2" /> / 108 109 <input type = "text" name = "expires2" 110 size = "2" />
208 Глава 4 ill </td> 112 </tr> 113 </table> 114 115 <!-- разрешить пользователю отправку формы --> 116 <pxinput type = "submit" value = "Submit" /x/p> 117 </fonti> 118 </body> 119 120 </html> Рис. 4.13. Форма заказа (order.html), в которой пользователь вводит имя, адрес и информацию о кредитной карте для завершения обработки заказа 4.9. Обработка заказа JSP-страница process.jsp (рис. 4.14) предназначена для обработки информации о кредитной карте пользователя и создания XHTML-документа, содержащего сообщение, что заказ был обработан, и отображающего окончательную сумму заказа в долларах. Скриптлет в строках 19-28 получает атрибут сеанса total. Объект Double возвращается приведенным к типу double и сохраняется в переменной total Java. В нашей имитации книжного Internet-магазина реальная обработка кредитной карты не выполняется, потому завершения транзакции не происходит.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 209 В этой связи в строке 26 вызывается метод invalidate интерфейса HttpSession для уничтожения объекта сеанса для текущего клиента. В реальном Internet-магазине сеанс не будет завершен, пока покупка не будет подтверждена компанией, обслуживающей кредитную карту. В строках 30-40 определено тело XHTML-докумен- та, отправляемого клиенту. Б строке 37 используется выражение JSP для вставки общей стоимости всех приобретенных книг. 1 <?xml version = "1.0"?> 2<TDOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EM" 3 "httpi//www.w3.org/TR/xhtioll/DTD/xhtmll-strict.dfcd"> 4 <!— process.jsp —> 5 6 <%— параметры JSP-страницы --%> 7 <%@ page language = "Java" session = "true" %> 8 <%@ page import = "}ava.. text .«" %> 9 10 <html xrolns = "http://www.w3.org/1999/xhtml"> 11 12 <head> 13 <title>Thank You!</title> 14 15 <link rel = "stylesheet" href ■ "styles.ess" 16 type = "text/ess" /> 17 </head> 18 19 <% // качало скриптлета 20 21 // получение общей суммы заказа 22 Double d = ( Double ) session,getAttribute{ "total" ); 23 double total = d.doubleValue(}; 24 25 // уничтожение сеанса по причине завершения обработки 2 6 session.invalidate(); 27 28 %> <%— конец скриптлета —%> 29 30 <body> 31 <р class = "bigFont">TJiank You</p> 32 33 <p>Your order has been processed.</p> 34 35 <p>Your credit card has been billed: 36 <span class = "bold"> 37 $<%= new DecimalFosraat( "0.00" ).£ormat( total ) %> 38 </span> 39 </p> 40 </body> 41 42 </html> Рис. 4.14. JSP-стрэница process.jsp, выполняющая окончательную обработку заказа (часть 1)
210 Глава 4 isoft Interne!: f«ptorer В* щт : л^дгчд )ei«w»- •m ГЧЭ" ^iilF Рис. 4.14. JSP-страница process.jsp, выполняющая окончательную обработку заказа (часть 2) АЛО. Развертывание приложения в J2EE 1.2.1 Теперь мы выполним развертывание приложения в эталонной реализации Java 2 Enterprise Edition 1.2.1. Предполагается, что вы уже загрузили и установили пакет J2EE 1.2.1. Если нет, обратитесь к инструкциям по настройке. Файлы для этого законченного приложения книжного Internet-магазин а можно найти на сайте www. deitel.com. В разделах с 4,10.1 по 4.10.8 будут описаны действия, необходимые для развертывания этого приложения: 1. Настройте источник данных books для использования его с сервером эталонной реализации J2EE 1.2.1. 2. Запустите сервер баз данных Cloudscape и сервер эталонной реализации J2EE 1,2,1 для развертывания и выполнения приложения. 3. Запустите инструментальное средство развертывания Application Deployment Tool. Это средство предоставляет графический интерфейс пользователя для развертывания приложений на сервере J2EE 1.2.1. 4. Создайте новое приложение в Application Deployment Too]. 5. Добавьте библиотеку JAR-файлов в приложение. Эти файлы доступны для всех компонентов приложения. 6. Создайте в приложении новый Web-компонент для сервлета BookServlet. 7. Создайте в приложении новый Web-компокент для сервлета AddToCart- Servlet. 8. Добавьте в приложение компоненты, не являющиеся сервлетами. К ним относятся XHTML-документы, JSP-страницы, изображения, CSS-файлы, XSL- файлы и компоненты JavaBeans, используемые в приложении. 9. Задайте контекст Web, который способствует выполнению данного приложения J2EE. Это предполагает задание URL, который будет использоваться для активизации приложения. 10. Задайте ресурс базы данных (т.е. books), используемый приложением, 11. Установите имя JNDI для базы данных в приложении. Это предполагает регистрацию имени с помощью сервиса идентификации и маршрутизации Java Naming and Directory Service, что позволит находить базу данных во время выполнения.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 211 12. Задайте файл приветствия длл приложения. Это начальный файл, который возвращается, когда пользователь вызывает приложение. 13. Осуществите развертывание приложения. L4. Выполните приложение. В конце раздела 4.10.8 будет описано, как осуществить развертывание и тестирование приложения книжного Internet-магазина. 4.10.1. Настройка источника данных books Прежде, чем осуществить развертывание приложения, вы должны настроить источник данных books, чтобы сервер J2EE зарегистрировал источник данных с помощью сервера идентификации. Это даст возможность приложению использовать JNDI для нахождеиия источника данных в процессе выполнения. В состав J2EE входит Cloudscape — программное средство для управления базами данных, написанное на «чистом» Java, разработанное компанией Informix Software. Мы используем Cloudscape для выполнения манипуляций над базой данных в этом практическом примере. Для создания базы данных books, используемой в этом примере, мы предоставляем сценарий SQL, который будет устанавливать базу данных и ее таблицы. Этот сценарий иожет быть выполнен с помощью интерактивного инструментального средства под названием ij, которое входит в состав Cloudscape. Мы предоставляем командный файл (createDatabase.bat) и командный сценарий (createData- base.ksh), которые вы можете использовать для запуска ij и выполнения SQL-сценариев. В каталоге примеров для этой главы имеются сценария createDatabase и SQL-сценарий books.sql1. Чтобы создать базу данных books, сначала убедитесь, что сервер Cloudscape запущен. Откройте новое окно команд, а затем перейдите к каталогу frameworks/RmiJdbc/bin Cloudscape. В этом каталоге выполните командный файл или командный сценарий, начинающейся с имени setCHentClo- udscapeCP, чтобы установить переменные окружения, необходимых сценарию createDatabase. Далее, перейдите к каталогу на вашем компьютере, в который вы поместили файлы примеров для этой главы, и взедите createDatabase books.sql чтобы выполнить SQL-сценарий, База данных books будет создана. [Замечание. Мы предусмотрели этот сценарий, чтобы вы в любое время снова могли его выполнять для восстановления исходного содержимого базы данных. При первом выполнении сценария он выдаст четыре сообщения об ошибках, поскольку пытается удалить четыре таблицы в базе данных. Раз база данных еще не существует, то нет и таблиц, которые можно было бы удалить. Эти сообщения следует просто игнорировать.] Чтобы настроить источник данных Cloudscape, необходимо модифицировать файл конфигурации J2EE по умолчанию default, properties в каталоге config J2EE. Ниже комментария JDBC URL Example имеется строка, которая начинается с jdbc.datasources. Добавьте к этой строке следующий текст: |jdbc/books|jdbc:cloudscape:rmi:books;create=true Вертикальная линия | в начале текста отделяет новый источник данных, который мы регистрируем, от источника данных, который регистрируется по умолчанию, когда вы инсталлируете J2EE. Текст jdbc/books представляет собой имя JNDI для базы данных. После второго символа | в тексте следует URL JDBC jdbc:cloud- scape:rmi:books. URL указывает, что J2EE будет использовать протокол JPBC для 1 Эти сценарии имеются в комплекте примеров для главы 8 книги «Технологии программирования на Java 2. Книга 1е (папка ch08). Их следует скопировать в папку с примерами для данной главы (примеры для этой главы находятся в папке chili.— /Трим. ред.
212 Глава 4 взаимодействия с подчиненным протоколом Cloudscape, который, в свою очередь, использует RMI для взаимодействия с базой данных (в нашем случае books). Наконец, create=tme задает, что J2EE следует создать базу данных, если она пока не существует. [О том, как создать базу данных, будет рассказано ниже.] После настройки базы данных сохраните файл default.properi.ies. Этим завершается 1-й шаг процесса развертывания (см. список в Разделе 4.10). Совет по переносимости программ 4.2 Каждый драйвер базы данных обычно имеет свой собственный формат URL, который дает возможность приложению взаимодействовать с базой данных, хранящейся на этом сервере баз данных. За более подробной информацией обратитесь к документации на сервер баз данных. 4.10.2. Запуск сервера Cloudscape и сервера J2EE На шаге 2 (см. список в разделе 4.10) необходимо запустить сервер баз данных Cloudscape и сервер J2EE, чтобы иметь возможность осуществить развертывание и выполнить приложение. Сначала откройте окно команд и запустите сервер Cloudscape. Чтобы запустить сервер, сначала откройте окно команд. Перейдите к каталогу установки Cloudscape (Cloudscape_3.6 по умолчанию). В этом каталоге имеется каталог frameworks. В Cloudscape имеется два режима выполнения: embedded я RmiJdbc. Режим embedded дает возможность выполнять Cloudscape как часть приложения Java. Режим RmiJdbc дает возможность выполнять Cloudscape как автономный сервер баз данных. Именно этот режим выполнения Cloudscape мы и будем использовать. В каталоге каждого режима выполнения имеется подкаталог bin, содержащий командные файлы (Windows) и командные сценарии (Linux/Unix) для установки переменных окружения и выполнения Cloudscape. Перейдите к подкаталогу bin режима RmiJdbc. Выполните командный файл или командный сценарий, начинающийся с имени setServerCIoudscapeCP, чтобы установить переменные окружения, необходимые серверу. Затем выполните командный файл или командный сценарий, начинающийся с имени startCS, чтобы запустить сервер баз данных Cloudscape. Работу сервера можно завершить, выполнив сценарий stopCS из другого окна команд. После того как сервер баз данных Cloudscape запущен, откройте окно команд и перейдите к подкаталогу bin J2EE. Затем выполните следующую команду: j2ee -verbose для запуска сервера J2EE. В состав сервера J2EE входит сервер Tomcat JSP и контейнер сервлетов, которые рассматривались в главах 2 и 3. Совет по переносимости программ 4.3 В некоторых системах UNIX/Linux вам может потребоваться предварять команды, которые запускают сервер Cloudscape и сервер J2EE, символами ./ для указания, что команда расположена в текущем каталоге. Совет по тестированию и отладке 4.1 Используйте отдельные окна команд (или процессоры команд) для выполнения команд, которые запускают сервер базы данных Cloudscape и сервер J2EE 1.2.1, чтобы иметь возможность наблюдать все сообщения об ошибках, генерируемые этими программами.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 213 jg&L Совет по тестированию и отладке 4.2 \^?у Чтобы гарантировать корректное взаимодействие сервера J2EE с сервером Cloudscape (или любым другим сервером базы данных), всегда запускайте сервер базы данных до запуска сервера J2EE. В противном случае при попытках сервера J2EE настроить свои источники данных будут выдаваться исключения. Чтобы завершить работу сервера J2EE, выполните в окне команд следующую команду из подкаталога bin J2EE: j2ee -stop jC&l Совет по тестированию и отладке 4.3 \^№у Всегда останавливайте сервер J2EE перед остановом сервера баз данных ^"""^ Cloudscape, чтобы гарантировать, что сервер J2EE не попытается связаться с сервером баз данных: Cloudscape после того, как работа сервера баз данных была завершена. Если сначала был отключен сервер Cloudscape. существует возможность, что сервер J2EE получит другой запрос и попытается снова осуществить доступ к базе данных. Это приведет к возбуждению исключений. 4.10.3. Запуск средства развертывания приложений J2EE Шаг 3 (см. список в разделе 4.LQ) является начальным этаном в процессе развертывания приложения книжного Internet-магазина. Эталонная реализация J2EE имеет в своем составе служебное приложение под названием Application Deployment Tool, которое содействует развертыванию приложений Enterprise Java. В главе 2 мы вручную создавали XML-дескриптор развертывания, чтобы внедрить наши сервлеты. Инструментальное средство Application Deployment Tool хорошо тем, что оно само записывает файлы дескрипторов развертывания и автоматически архивирует компоненты Web-приложения. Инструментальное средство помещает все компоненты Web-приложения и вспомогательные файлы для конкретного приложения в один файл архива Enterprise Application Archive (EAR). Этот файл содержит информацию о дескрипторе развертывания, WAR-файлы с компонентами Web-приложеняя и некоторую дополнительную информацию, о чем пойдет речь далее в этой книге. Чтобы запустить инструментальное средство развертывания, откройте окно команд К перейдите к подкаталогу bin J2EE. Затем введите следующую команду: deploytool Откроется окно Application Deployment Tool {рис. 4.15). [Замечание. При обсуждении процесса развертывания мы касаемся только тех аспектов инструментального средства развертывания, которые необходимы для развертывания нашего приложения. Далее в книге будут более подробно рассматриваться и другие аспекты.] 4.10.4. Создание приложения книжного Internet-магазина Инструментальное средство Application Deployment Tool упрощает задачу развертывания приложений Enterprise Java. Далее (Шаг 4 в списке в разделе 4.10) мы создадим новое приложение. Щелкните на кнопке New Application, чтобы отобразить окно New Application (рис. 4.16).
214 Глава 4 В поле Application File Name можно ввести имя EAR-файла, в котором средство Application Deployment Tool хранит компоненты приложения, либо можно щелкнуть на кнопке Browse, чтобы задать и имя, и место размещения файла. В поле Application Display Name можно задать имя для приложения. Это имя будет присутствовать в списке Local Application основного окна инструментального средства развертьшшшя (рис, 4.15). Щелкните наОК, чтобы создать приложение. Появится основное окно Application Deployment Tool, как показано на рис. 4.17. Новое приложение Новый Web-компонент Сохранить Рис. 4.15. Основное окно Application Deployment Tool Рис. 4.16. Окно создания приложения New Application 4.10.5. Создание Web-компонентов BookServlet и AddToCartServlet Шаг 6 (см. список в разделе 4.10) состоит в создании Web-компонентов для сервлетов BookServlet и AddToCartServlet. Это дает возможность задавать псевдоним, который будет использоваться для вызова каждого из ссрвлетов. Ниже мы подробно рассмотрим процесс создания Web-компонента BookServlet. Затем вы можете повторить эти действия для создания Web-компонента AddToCartServlet. Щелкните на кнопке New Web Component (см. рис. 4.15), чтобы отобразить начальное окно Introduction мастера создания нового Web-компонента New Web Component Wizard (рис. 4.18). Щелкните на кнопке Next>, чтобы отобразить окно WAR File General Properties (рис. 4.19) мастера New Web Component Wizard.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 215 ^Application Repayment Too]: J5i» and Setytet Boqfestort fchanged). г ь* "'eSfeasw». h^> <JSlSJ wt«tiwj$p}Ms*<™fceiiiie*»*"" Sstvlet Botietim JJSP and E?ivIf1 Books lore :«■■ *«*cation № Same; Contents: META-INF/MANIFEST.MF META-3N F;ap pitc afcon *m I МЕТА-IN F/s и n-J 2ee- n лггч Add "-:.*. ■"« J>esiiyt^;| Application description "1 ft««K' a* ""'КЗ Ш- ■ттш«&:: • - ^ Рис. 4.17, Основное окно средства развертывания Application Deployment Tool после создания нового приложения и.ц.ц-и..щ1.лтим— &&*SSSS£3-sii лгг fill» tW тзшгз'-есШ. tben puke^ft +b*-etlect«f jiJ#o into * Web iftcftiSt L H ,wut) - £^14**($£&иь r pt*mt* the iiepbsi*tft?;4»*i*lpbwr гыдт^Гв& + * -. £е%*£1вЗ t5jjj tadp ti^HBtlit- №ptii« tbt*Ctii&«lfrJ ••& • Kg ifc, &.k ; m<> 3 Ц§*ц Рис. 4.18. Начальное окно мастера New Web Component Wizard - Introduction
216 Глава 4 Рис. 4,19. Окно общих свойств WAR-файлой мастера New Web Component Wizard - WAR Files General Properties . Убедитесь, что в раскрывающемся списке Web Component Will Go In: выбрано JSP and Servlet Bo ok Store. В поле WAR Display Name введите имя (StoreCompo- nents) для WAR-файла, которое будет отображаться в списке Local Applications основного окна средства развертывания (см. рис. 4.15). Затем щелкните на кнопке Add..,, чтобы отобразить окно добавления в архив файлов содержимого Add Files to .WAR — Add Content Files (рис. d.20). Файлы содержимого (content files) — Рис. 4.20. Окно добавления в архив файлов содержимого Add Files to .WAR - Add Content Files
Книжный Internet-магазин, реализованный с использованием сер влетов и JSP 217 это файлы, не являющиеся сервлетами, такие как изображения, XHTML-докумен- ты, таблицы стилей и JSP-страницы. Эти файлы мы добавим на другом этапе процесса развертывания, поэтому щелкните на Next>, чтобы перейти к окну добавления в архив файлов классов Add Files to .WAR — Add Class Files (рис. 4.21). Рис. 4.21. Окно добавления е архив файлов классов Add Files to .WAR - Add Class Files Для добавления файла класса BookServlet.class щелкните на кнопке Browse..., чтобы отобразить окно выбора корневого каталога Choose Root Directory (рис. 4.22). При добавлении файла класса для класса, входящего в пакет (каковыми являются все классы в нашем примере) требуется, чтобы файлы добавлялись и сохранялись в соответствии с полной структурой каталогов их пакета или входили в состав JAR-файла, который содержит полную структуру каталогов пакета. В этом примере мы не создавали JAR-файл, содержащий весь пакет com.deitel.advjhtpl.store. Следовательно, нужно найти каталог, в котором содержится первый каталог пакета. Рис. 4.22. Окно выбора корневого каталога Root Directory
218 Глава 4 В нашей системе каталог com, который является первым каталогом в имени пакета, расположен в каталоге Development. Когда бы щелкаете на кнопке выбора корневого каталога Choose Root Directory, вы возвращаетесь в окно Add Files to .WAR — Add Class Files. В этом окне вам следует найти каталог com (рис. 4.23). Рис. 4.23. Окно добавления файлов классов Add Files to .WAR - Add Class Files после выбора корневого кзтапсга, в котором находятся файлы Дважды щелкните на имени каталога com, чтобы развернуть в окне его содержимое. Проделайте то же самое для подкаталога deitel, затем для подкаталога advjhtpl и, наконец, для каталога store. В каталога store выберите файл класса .class для BookServlet, а затем щелкните на кнопке Add. В нижней части окна Add Files to .WAR — Add Class Files будет отображен файл .class с полной структурой каталогов пакета. Проделав это, щелкните на кнопке Finish, чтобы вернуться в окно общих свойств WAR-файла New Web Component Wizard — WAR File General Properties. Обратите внимание, что файлы, выбранные в окне Add Files to .WAR, теперь отображаются в списке Contents (рис. 4.24). Типичная ошибка программирования 4.1 Если вы не указали полную структуру каталогов пакета для класса, входящего в пакет, приложение не сможет загрузить класс и надлежащим образом его выполнить. Щелкните на кнопке Next>, чтобы перейти к окну выбора типа компонента New Web Component Wizard — Choose Component Type, и выберите Servlet (рис. 4.25). Щелкните на кнопке Next>, чтобы перейти к окну общих свойств компонента New Web Component Wizard — Component General Properties (рис. 4,2б>. Выберите класс BookServlet в раскрывающемся списке Servlet Class и введите BookServlet в поле Web Component Display Name.
Книжный Internet-магазин, реализованный с использованием сервлетов и J5P 219 М-^ТТ ^БЭЗ ^4^itKii<tu»«**:!!^n1ta5»^#$™^ 14* (»ЧЧ(ВДКЭЙОЛ. ОСЙЙЙ«I1C|9Ub1inB1ОДДО J^?rf№-M (ДОШфв. fOt^K fta«^fl|^tfWin*.sdu!rie.«Md11l«Ffc&t$fi£t». * — in ..?*. , Рис. 4.24. Окно общих свойств WAR-файла New Web Component Wizard - WAR Files General Properties после выбора файла BookServlet.class Рис. 4.25. Окно выбора типа компонента New Web Component Wizard - Choose Component Type Два раза щелкните на кнопке Ncxt>, чтобы отобразить окно псевдонимов компонентов New Web Component Wizard — Component Aliases (рис. 4.27). Щелкните на кнопке Add, "чтобы задать псевдоним для сервлета BookServlet. Щелкните на пустом поле, которое имеется в окне, введите displayBook в качестве псевдонима для сервлета и нажмите клавишу Enter. Далее, щелкните на кнопке Finish, чтобы закончить настрийку параметров для сервлета BookServlet. Теперь создайте Web-компонент для сервлета AddToCartServlet (Шаг 7 в списке в разделе 4.10), повторив действия, описанные в этом разделе. Для этого Web-компонента задайте Add To Cart Servlet в качестве отображаемого имени Web Component Display Name и AddToCart в качестве псевдонима для сервлета. После добавления двух Web-компонентов сервлетов окно Application Deployment Tool должно выглядеть, как показано на рис. 4.28.
220 Глава 4 Рис. 4.26. Окно общих свойств компонентов New Web Component Wizard - Component General Properties Рис 4.27. Окно псевдонимов компонентов New Web Component Wizard — Component Aliases 4.10.6. Добавление в приложение компонентов, не являющихся сервлетами Далее мы добавим в приложение компоненты, не являющиеся сервлетами (Шаг 8 в списке в разделе 4.10), К этим компонентам относятся JSP-страницы, .ХЬГШЬ-до- кументы, таблицы стилей, изображения и компоненты JavaBeans, используемые в приложении. Для начала раскройте дерево компонентов приложения и щелкните на Store Component в списке Local Applications в окне инструментального средства Application Deployment Tool (см. рис. 4,28). В области содержимого окна Application Deployment Tool щелкните на кнопке Add..., чтобы отобразить окно добавления в WAR-архив файлов содержимого Add Files to .WAR — Add Content Files (рис. 4.29).
Книжный Internet-магазин, реализованный с использованием сералетов и JSP 221 Рис 4.28. Окно инструментального средства Application Deployment Tool после развертываний серзпетоа BookServlet AddToCartServlet О AtfaToOrtS&rYlel.tJaes О AddToCartSetviei jawa D G Dolus I Q ВооШеЗП class Q Bookfiearijjva Q boots jsP Q Books enrfetctass i^I^finiikSfjMfllJJiiKa s Рис. 4.29. Окно добавления в WAR-архив файлов содержимого Add Files to .WAR Add Content Files
222 Глава 4 Перейдите в вашей системе к каталогу, в котором содержатся файлы приложения книжного Internet-магазина. В поле списка, которое имеется в окне, найдите каждый из следующих файлов и каталогов: book.xsl, books.jsp, Images, index.html, order.html, process.jsp, styles.css и viewCart.jsp. Для каждого файла или каталога щелкните на кнопке Add. Вы можете выбрать несколько элементов за раз, удерживая клавишу <Ctrl> и щелкая на каждом из элементов. Все добавленные вами элементы должны появляться в текстовой области в нижней части окна. Сделав это, щелкните на Next>, чтобы отобразить окно добавления в WAR-архив файлов классов Add Files to .WAR — Add Class Files (рис. 4.30). Мы используем это окно для добавления в наше приложение файлов .class классов, не являющихся сервлетами (т.е. компонентов JavaBeans), Помните, что компоненты JavaBeans, используемые в приложении, содержатся в составе пакета, поэтому файлы .class должны добавляться и сохраняться в соответствии с полной структурой каталогов их пакета. Снова щелкните на кнопке Browse..., чтобы отобразить окно выбора корневого каталога Choose Root Directory и найти каталог, в котором содержится имя первого каталога пакета. Выберите этот каталог в качестве корневого. Дважды щелкните на имени каталога com, чтобы раскрыть его содержимое в окне. Проделайте то же самое для подкаталога deitel, затем для подкаталога advjhtpl и, наконец, для каталога store. В каталоге store выберите файлы .class для каждого из компонентов JavaBeans в этом примере (Book- Bean.class, CartltemBean.class и TitlesBean.class), затем щелкните на кнопке Add. В нижней части окна Add Files to .WAJR — Add Class Files каждый из файлов .class будет отображен в соответствии с полной структурой каталогов пакета. Проделав это, щелкните на кнопке Finish, чтобы вернуться в окно Application Deployment Tool. Обратите внимание, что файлы, выбранные в окне Add Files to .WAR, теперь фигурируют в текстовой области Contents окна. Щелкните на кнопке Save, чтобы сохранить результаты вашей работы. Рис. 4.30. Окно добавления в WAR-архив файлов классов Add Files to .WAR — Add Class Files
Книжный Internet-магазин, реализованный с использованием серелетов и JSP 223 4.10.7. Задание контекста Web, ссылок на ресурсы, имен JNDI и файлов приветствия На шагах с 9 по 13 (см. список в разделе 4.10) осуществляется окончательная настройка и развертывание приложения книжного Tnternet-магазина. Проделав действия, описываемые в этом разделе, вы сможете выполнить приложение. Мы начнем с задания Web-контекста для нашего приложения (шаг 9 в списке в разделе 4.10). В начале этой главы мы указывали, что пользователь должен ввести URL http://localhost:8000/advjhtpl/store в адресной строке браузера, чтобы осуществить доступ к приложению книжного Internet-магазина. Контекст Web является частью указанного URL, которая дает возможность серверу определять, какое приложение следует выполнить при получении запроса от клиента. В данном случае контекстом Web является advjhtpl/ store. Снова обращаем ваше внимание, что сервер J2EE использует порт 8000, а не порт 8080, используемый, сервером Tomcat. •57jfq Типичная ошибка программирования 4.2 При задании в VRL неправильного номера парта, который предполагается использовать для доступа к серверу J2BE, ваш Web-браузер просигнализирует, что сервер не был найден. Совет по тестированию и отладке 4,4 При развертывании приложения Enterprise Java на рабочем сервере (сервер J2EE предназначен только для тестирования) обычно нет необходимости указывать в URL номер порта для доступа к приложению. За более подробной информацией обратитесь к документации на ваш сервер приложений. Чтобы задать контекст Web, щелкните на узле JSP and Servlet Bookstore в списке Local Applications в окне Application Deployment Tool. Затем щелкните на вкладке Web Context (рис. 4.31). Щелкните на пустом поле в столбце Context Root и введите advjhtpl/store; затем нажмите Enter. ^ Ф Store ConpDnenrj ф AdeToCalSaiYlel ЗШЕТЩЕ &№ -У' Ssnra АдАвопх Jtn*4*frt^ Рис. 4.31. Задание контекста Web Context в окне Application Deployment Tool Далее, мы должны задать ресурс базы данных, на который ссылается приложение (шаг 10 в списке в разделе 4.10). Щелкните на узле Store Components в списке Local Applications в окне Application Deployment Tool. Затем щелкните на вкладке Resource RePs (рис, 4.32). Щелкните на кнопке Add. Ниже столбца Coded Name щелкните на пустом поле и введите jdbc/books (имя JNDI нашего источника данных). На рис. 4.32 показано окно Application Deployment Tool после создания ссылки на ресурс.
224 Глава 4 Рис. 4.32. Задание ссь лки на ресурс в окне Application Deployment Tool Далее, мы задаем имя JNDI для базы данных в приложении {шаг 11 в списке в разделе 4.10), Оно используется для регистрации имени с помощью сервиса Java Naming and Directory Service, чтобы база данных могла быть найдена приложением в процессе выполнения. Чтобы задать имя JNDI для базы данных, щелкните на узле JSP and Servlet Bookstore в списке Local Applications в окне Application Deployment Tool. Затем щелкните яа вкладке JNDI names (рис, 4,33). В столбце JNDI Name щелкните на пустом поле и введите jdbc/books; затем нажмите Enter. Рис. 4.33. Задание имени JNDI в окне Application Deployment Tool Последнее действие, которое нам осталось выполнить перед тем, как приложение будет развернуто, — это задать файл приветствия, которое будет отображаться при первом посещвнии пользователем книжного магазина. Щелкните на узле Store Components в списке Local Applications в окне Application Deployment Tool. Затем щелкните на вкладке File Reps (рис. 4,34). Щелкните на кнопке Add.
Книжный Internet-магазин, реализованный с использованием сер влетов и JSP 225 В разделе Welcome Files щелкните на пустом поле и введите index.html. На рис. 4.34 представлено окно Application Deployment Tool после задания файла приветствия. Щелкните на кнопке Save, чтобы сохранить настройки приложения. Рис. 4.34. Задание файла приветствия на вкладке File Ref s окна Application Deployment Tool 4.10.8. Развертывание и выполнение приложения Теперь можно выполнить развертывание приложения книжного Inter net-магазина, чтобы протестировать его. На рис. 4.35 показаны кнопки панели управления инструментального средства Application Deployment Tool для обновления файлов приложения и развертывания приложения. Кнопка Update Application Files обновляет ЕАВ-файл приложения после внесения изменений в любой из файлов, например, после повторной компиляции классов или модификации файлов. Кнопка Deploy Application заставляет средство Application Deployment Tool во взаимодействии с сервером J2EE выполнить развертывание приложения. Функциональные возможности обеих кнопок объединены в кнопке Update and Redeploy Application. Развертывание приложения м Обновление файлов приложения Обновление и повторное развертывание приложения Рис. 4.35. Кнопки панели инструментов Application Deployment Tool для обновления файлов приложения и развертывания приложения
226 Глава А Щелкните на кнопке Deploy Application, чтобы отобразить окно Deploy JSP and Servlet Bookstore — Introduction (рис. 4.36). Щелкните яа кнопке Next> три раза, затем щелкните на кнопке Finish. Появится окно индикации процесса развертывания Deployment Progress. Это окно дает возможность наблюдать, когда развертывание завершится. После того как это произойдет, откройте Web-брауаер и введите следующий URL для тестирования приложения: http://localhoat:8000/advjhtpl/store Рис. 4.36. Окно Deploy JSP and Servlet Bookstore — Introduction В этой главе было представлено первое значительное приложение Enterprise Java. Рассмотренные в этом разделе действия по развертывания приложения книжного Internet-магазина, по сути, являются лишь некоторыми из действий, необходимых для развертывания типового приложения Enterprise Java. Например, в приложении книжного магазина не предъявлялось каких-либо требований к безопасности. В реальных приложениях Enterprise Java некоторые, или даже все компоненты приложения имеют ограничения, связанные с безопасностью, например, «пользователь должен ввести правильное имя пользователя и пароль, прежде чем он получит доступ к компоненту». Такие ограничения задаются на этапе развертывания с помощью инструментального средства Application Deployment Tool или какого-либо другого подобного средства в среде разработки Enterprise Java. Эти ограничения, связанные с обеспечением безопасностью, накладываются сервером приложений. В нашем примере книжного Inteniet-магазина, в случае, если JSP-страницы имеют ограничения, связанные с безопасностью, следует осуществлять развертывание каждой из них индивидуально, как мы это делали для сервле- тов BookServlet и AddTo Cart Servlet. В последующих главах будут более подробно рассмотрены параметры развертывания для компонентов приложения. Спецификация Java 2 Enterprise Edition Specification (доступная по адресу java.sun.com/ j2ee/download.html) описывает полный набор параметров развертывания, которые обязательны для серверов приложений, совместимых с J2EE. В следующей главе будет продолжено обсуждение архитектуры клиент/сервер. В ней мы используем сервлеты и XML для создания содержимого для беспроводных устройств, таких как пейджеры, сотовые телефоны и карманные компьютеры.
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 227 Резюме • В состав эталонной реализации Java 2 Enterprise Edition 1.2.1 входит сервер Apache Tomcat JSP и контейнер сервлетов. • Трехуровневое распределенное Web-приложение состоит из клиентского уровня, серверного уровня и уровня базы данных. • Клиентский уровень в Web-приложен и и часто представлен Web-браузером пользователя, • Серверный уровень в Web-приложении часто реализуется с помощью JSP-страяиц и сервлетов, которые действуют в интересах клиента. • Уровень баз данных содержит, базу данных, доступ к которой осуществляется с серверного уровня, • В состав эталонной реализации Java 2 Enterprise Edition 1.2.1 входит инструментальное средство развертывания Application Deployment Tool. Помимо многих других возможностей, это средство позволяет задавать псевдонимы, используемые для вызова сервлета. • Средство Application Deployment Tool создает дескриптор развертывания для сервлета в процессе развертывания приложения, • Файл приветствия — это документ по умолчанию, отправляемый в качестве ответа клиенту, когда клиент первый раз взаимодействует с приложением J2EE, • Различные браузеры имеют различный уровень поддержки каскадных таблиц стилей Cascading1 Style Sheet. • Страницы JavaServer Pages часто генерируют XHTML-код, который отправляется клиенту для отображения. • Интерфейс идентификации и «аршрутизации Java Naming and Directory Interlace (JNDI) дает возможность компонентам приложения Enterprise Java осуществлять доступ к информации и ресурсам (например, к базам данных), которые являются внешними дли приложения. В некоторых случаях эти ресурсы являются распределенными в сети. • Контейнер приложения Enterprise Java должен предоставлять сервис идентификации, который реализует интерфейс JNDI и дает возможность компонентам выполняться в этом контейнере, чтобы осуществлять поиск имен для нахождения нужных ресурсов. Эталонная реализация сервера J2EE 1.2.1 содержит подобный сервис идентификации. • При развертывании приложения Enterprise Java задаются ресурсы, используемые приложением (например, базы данных), и имена JNDI для этих ресурсов. • Используя класс InitialContext, компонент приложения Enterprise Java может осуществлять поиск нужного ресурса. Класс InitialContext предоставляет доступ к окружению идентификации приложения. • Метод lookup класса InitialContext находит ресурс по заданному имени JNDI. Метод lookup возвращает объект (Object) и возбуждает исключение NamingException, если не может найти ресурс по имени, полученном в качестве параметра, • Интерфейс Data Source используется для установки соединения с базой данных. • Интерфейсы Document и Element пакета org.w3c.dom используются для создания дерева XML-документа. • Метод createElemeat интерфейса Document создает элемент в XML-документе. • Метод createTextNode интерфейса Document задает текст для элемента Element. ■ Метод appendChild интерфейса Element добавляет узел в элемент Element как его дочерний элемент. • XML-документ может быть трансформирован в XHTML-документ с аошэщью таблицы стилей XSL.. • Метод getRequestDispatcher интерфейса ServIctRequest возвращает объект Request- Dispatcher, который может переадресовывать (forward) запросы к другим ресурсам или включать (include) другие ресурсы в ответ сервлета. • В случае вызова метода forward интерфейса RequestDispatcher обработка аанроса данным сервлетом завершается, • Объекты типа RequestDispatcher могут быть получены с помощью метода getRequestDispatcher из объекта, который реализует интерфейс ServletRequest, или из объекта ServletContext с помощью методов getRequestDispatcher и getNamedDispatcher. • Метод getNamedDispatcher интерфейса. ServletRequest получает имя сервлета в качестве параметра, а затем ищет сервлет в контексте ServletContext по этому имени. Если сера- лет не найден, метод возвращает null.
228 Глава 4 • Как метод getRequestDispateher интерфейса ServletRequest, так и метод getReqnest- Dispatcher интерфейса ServletContext просто возвращают содержимое заданного пути, если путь не указывает на сервлег. • Прежде, чем вы сможете воспользоваться возможностями XML и XSL, вы должны загрузить и установить Java API (or XML Parsing (JAXP) версии 1.1 с сайта java.sun.com/xml/ download.htm. • Корневой каталог JAXP (обычно он носит имя jaxp-1-l) содержит три JAR-файла, которые необходимы для компиляции и выполнения программ, использующих JAXP: crim- sun.jar, jaxp.jar и xalan.jar. Эти файлы должны быть добавлены к средствам расширения Java 2 Standard Edition. • JAXP 1.1 является составной частью эталонной реализации J2EE 1.3. • Для создания дерева объектной модели документа (DOM) из XML-документа требуется объект синтаксического анализатора DocumentBuilder. Объекты DocumentBuilder создаются объектом-мастером Document Builder Factory. • Классы Document и Element содержатся в пакете org.w3e.dom. • Объект DOMSoarce представляет XML-документ в XSLT-трансформации. Для чтения потока байтов, содержащихся в XSL-файле, может быть использовав объект StreamSource. • Объект StreamResnlt определяет поток PrintWriter, в который будут записываться результаты XSLT-трансформации. • Статический метод newlnstance интерфейса TransformerFactory создает объект Trans- forme rFaetory. Этот объект дает возможность программе получать объект Transformer, который применяет XSLT-трансформацию. ■ Метод newTransformer интерфейса TransformerFactory принимает параметр Stream- Source, представляющий XSL-таблицу стилей, которая будет применяться к ХМЪ-доку- менту. • Метод transform интерфейса Transformer выполняет XSLT-трансформацию над заданным объектом DOM Source и записывает результат в заданный объект StreamResult. • Исключение TransformerException возбуждается, если возникает проблема при создании объекта TransformerFactory, создании объекта Transformer или выполнении трансформации. • Метод invalidate интерфейса HttpSessica уничтожает объект сеанса для текущего клиента. • Перед развертыванием приложения Enterprise Java необходимо настроить источники данных и другие ресурсы, чтобы сервер J2EE мог зарегистрировать эти ресурсы с помощью сервера идентификации. Это дает возможность приложению использовать JNDI для нахождения ресурсов в процессе выполнения. • В состав J2EE входит Cloudscape — приложение, разработанное компанией Informix Software для управления базами данных, наиисанное на Java. • Чтобы настроить источник данных Cloudscape, необходимо модифицировать файл конфигурации J2EE по умолчанию default.properties в каталоге config J2EE. Ниже строки комментария JDBC URL Examples имеется строка, которая начинается с jdbc.datasources. Добавьте в эту строку следующий текст (в котором Источник Данных представляет собой имя вашего источника данных): I jdbc/ИстоминкДанных| jdbo: cloudscape: тл.\ИстачникДанных-,ъпа.Ъв=Ъ.Т11е • Каждый сервер баз данных обычно имеет свой формат URL, который дает возможность приложению взаимодействовать с базами данных, размещенными на сервере баз данных. • Прежде, чем осуществить развертывание и выполнить приложение, необходимо запустить сервер баз данных Cloudscape и сервер J2EE. • Чтобы обеспечить надлежащее взаимодействие сервера. J2EE с сервером Cloudscape (или любым другим сервером баз данных), всегда запускайте сервер баз данных перед запуском сервера J2EE. • Всегда отключайте сервер J2EE перед отключением сервера баз данных Cloudscape, чтобы гарантировать, что сервер J2EE не попытается взаимодействовать с сервером баз данных Cloudscape после того, как сервер баз данных был отключеп. • В состав эталонной реализации J2EE входит инструментальное средство Application Deployment Tool, которое помогает осуществлять развертывание приложений Enterprise Java. • Инструментальное средство развертывания Application Deployment Tool хорошо тем, что оно само записывает файлы дескрипторов развертывания и автоматически архивирует компоненты Web-приложения. Инструментальное средство помещает все компоненты
Книжный Internet-магазин, реализованный с использованием сервлегов и JSP 229 Web-приложения и служебные файлы для определенного приложения в один файл архива Enterprise Application Archive (EAR). Этот файл содержит информацию о дескрипторе развертывания, WAR-файлы с компонентами Web-приложения и дополнительную информацию, о которой будет говориться далее в этой книге. • При добавлении в приложение с помощью средства Application Deployment Tool файла класса для класса в составе пакета, обязательно следует добавлять и сохранять файлы с их полной структурой каталогов пакета или же в составе JAR-файла, который содержит полную структуру каталогов пакета. • Если вы не включите полную структуру каталогов пакета для класса в составе пакете, приложение не сможет надлежащим образом загрузить и выполнить класс. • Контекст Web для приложения представляет собой часть URL, которая дает возможность серверу определить, какое именно приложение выполнять, когда сервер получает запрос от клиента. • Сервер J2EE использует порт 8000, тогда как Tomcat использует порт 8080. • При развертывании приложения Enterprise Java на рабочем сервере приложений обычно нет необходимости указывать в URL номер порта для доступа к приложению. • Частью процесса развертывания приложения является задание ссылок на ресурсы для компонентов приложения. • Каждая ссылка на ресурс имеет соответствующее имя JNDI, которое используется инструментальным средством развертывания для регистрации ресурса с помощью сервиса Java Naming and Directory Service. Это дает возможность приложению находить ресурс в процессе выполнения. Терминология Application Deployment Tool, средство развертывания приложения Cascading Style Sheets (CSS) — каскадные таблицы стилей component environment entries — записи окружения компонента configure a data source — настройка источника данных create a Web component — создание Web-компонента database resource — ресурс базы данных Data Sou гее, интерфейс deploy an application — развертывание приложения dynamic XHTML document — динамический XH ТМ L-док умен т Enterprise Application Archive (EAR) file — файл архива приложения external resource — внешний ресурс forward, метод интерфейса Requ es tDispat cher getRequestDispatcher, метод интерфейса Servlet Request include content from a resource — включение содержимого из ресурса include, метод интерфейса RequestDispatcher InitislContext, класс invalidate, метод интерфейса HttpSession J2EE с on fig directory — каталог конфигурации J2EE Java 2 Enterprise Edition 1.2,1 reference implementation — эталонная реализации Java 2 Enterprise Edition 1.2.1 Java API for XML Parsing (JAXP) — API Java для синтаксического анализа XML Java Naming and Directory Interface (JNDI) — интерфейс идентификации и маршрутизации j a vax. naming, пакет jdbc.datasource, свойство конфигурации J2EE jdbc:cloudscape:rmi:books, URL JDBC JNDI name — имя JNDI locate a naming service — нахождение сервиса идентификации lookup, метод класса Initial Context name lookup — поиск имен name resolution — вычисление имени register a data source with a naming server — регистрация источника данных сервером идентификации RequestDispatcher, интерфейс server tier — серверный уровень ServletContext, интерфейс shopping cart — магазинная тележка style sheet — таблица стилей Web component — Web-компонент Web context — контекст Web welcome file — файл приветствия XML XSL transformation — XSLT-трансформация
230 Глава А Упражнения для самоконтроля 4.1. Заполните пропуски в следующих высказываниях: a) Трехуровневое распределенное Web-приложение состоит из уровней , и . b) __^ представляет собой документ по умолчанию, отправляемый в качестве ответа клиенту, когда клиент в первый раз взаимодействует с приложением J2EE, c) Интерфейс дает возможность компонентам приложения Enterprise Java осуществлять доступ к информации и ресурсам (таким, как базы данных), которые являются внешними для приложения. d) Объект предоставляет доступ к окружению идентификации приложения. e) Объект RequestDispatcher может запросы к другим ресурсам или другие ресурсы как часть ответа текущего сервлета. 1) API Sun предоставляет Java-программе возможности работы с XML и XSL. g) Метод интерфейса уничтожает объект сеанса для текущего клиента. h) для приложения является частью URL, которая дает возможность серверу определять, какое именно приложение выполнять, когда сервер получает запрос от клиента. i) Контейнер приложения Enterprise Java должен предоставить , который реализует интерфейс JNDI и дает возможность компонентам, выполняющимся в этом контейнере, выполнять поиск имен для нахождения ресурсов. j) В состав эталонной реализации J2EE входит приложение, называемое , которое содействует развертыванию приложений Enterprise Java. 4.2. Ответьте, является ли каждое из следующих высказываний истинным или ложным. Если высказывание ложно, объясните, почему. a) Сервер J2EE ожидает клиентские запросы на порту 8080. b) При развертывании приложений с помощью сервера J2EE вы можете запускать серверы Cloudscape и J2EE в любом порядке. c) Метод lookup класса InitialContext находит ресурс по указанному имени JNDI. d) Метод lookup возвращает объект Connection.1 представляющий соединение с базой данных. e) В состав эталонной реализации Java 2 Enterprise Edition 1.2.1 входит сервер Apache Tomcat JSP и контейнер сервлетов. f) Когда вызывается метод forward интерфейса RequestDispatcher, обработка текущего сервлета яремеано иряостакавливаегея в ожидании ответа от ресурса, к которому был переадресован запрос. g) И метод getRequestDispateher интерфейса ServletRequest, и метод getRequest- Dispatcher интерфейса ServletContext возбуждают исключение, если параметром метода getRequestDispateher не является сервлет. h) Каждая ссылка на ресурс имеет соответствующее имя JNDI, которое используется инструментальным средством развертывания для регистрации ресурса с помощью сервиса Java Naming and Directory Service. i) Если вы ке настроили ваши источники ценных и другие ресурсы перед развертыванием приложения Enterprise Java, сервер J2EE будет осуществлять поиск в приложении, чтобы определить используемые ресурсы и зарегистрировать эти ресурсы с помощью сервера идентификации. j) Если вы не включите полную структуру каталогов пакета для класса, приложение не сможет надлежащим образом загрузить и выполнить класс. Ответы на упражнения для самоконтроля 4.1. а) клиента, сервера, базы данных, о) Файл приветствия, с) Java Naming and Directory Interface (JNDI). d) InitialContext. e) переадресовывать (forward), включать (include).
Книжный Internet-магазин, реализованный с использованием сервлетов и JSP 231 f) Java for XML Parsing (JAXP). g) invalidate, HttpSession. h) Контекст Web. i) сервис идентификации, j) Application Deployment Tool. 4.2. а) Ложно. Порт 80SO является портом по умолчанию для сервера Tomcat, Сервер J2EE использует порт 8000. b) Ложно. Чтобы обеспечить надлежащее взаимодействие сервера J2EE с сервером Cloudscape («ли любым другим сервером баз данных), сервер баз данных должен быть запущен перед запуском сервером J2EB. c) Истинно, d) Ложно. Метод lookup возвращает объект DataSource, который может быть использован для получения соединения. e) Истинно. f) Ложно. Когда вызывается метод forward интерфейса ReqnestDispatcher, обработка запроса текущим еервлетом завершается. g) Ложно. И метод getltequestDispatcher интерфейса ServletRequest, и метод getRe- questDispatcher интерфейса ScrvletContext просто возвращают содержимое заданного пути, если путь не указывает на сервлет. h) Истинно. i) Ложно. Перед развертыванием приложения Enterprise Java вы должны настроить ваши источники данных и другие ресурсы, чтобы сервер J2EE мое зарегистрировать эти ресурсы с помощью сервера идентификации. В противном случае при попытках приложения осуществить доступ к ресурсам будут возбуждаться исключения. j) Истинно. Упражнения 4.3. Модифицируйте практический пример книжного Inlernet-магазина чтобы дать возможность клиенту изменять количество для каждой из книг, содержащихся в данный момент в магазинной тележке. В странице viewCart.jsp отобразите это количество в элементе input типа text в форме form. Предусмотрите кнопку UpdateCart, которая дает возможность пользователю отправлять форму сервлету, осуществляющему обновление количества для каждой из книг в тележке. Сервлет должен переадресовывать запрос сценарию viewCart.jsp, чтобы пользователь мог увидеть обновленное содержимое тележки. Выполните развертывание приложеаия и протестируйте его возможности по обновлению содержимого тележки. 4.4. Усовершенствуйте компонент TitlcsBean в примере для книжного Internet-магазина, чтобы получать информацию об авторе из базы данных books. Включите данные об авторе в класс ВоокВеап и отображайте информацию об авторе как часть 1ЛГеЪ-Страни.цы, которую пользователи будут видеть, когда выбирают книгу и просматривают информацию о книге. 4.5. Добавьте средства проверки серверной формы в бланк заказа в примере для книжного Internet-магазина. Проверяйте дату истечения срока действия кредитной карты. Сделайте все поля в форме обязательными. Если пользователь указал данные не во всех имеющихся полях, возвратите XHTML-документ, содержащий бланк заказа. Все поля, в которые пользователь ранее ввел данные, должны содержать эти данные. Дли выполнения этого упражнения замените документ order.html JSP-сценарием, который динамически генерирует форму. 4.6. Создайте таблицу order и таблицу orderltems в базе данных books, чтобы хранить заказы, размещенные покупателями. Таблица order должна хранить идентификатор заказа orderlD, дату заказа orderDate и адрес email покупателя, разместившего заказ. [Замечание. Вам понадобится модифицировать форму из Упражнения 4.6, чтобы включить в нее адрес email покупателя.] Таблица orderltem должна хранить идентификатор заказа orderlD, код ISBN, цену price и количество quantity для каждой книги в заказе. Модифицируйте страницу process.jsp, чтобы она хранила информацию о заказе в таблицах order и ordeTltems. 4.7. Создайте JSP-странипу, чтобы дать возможность клиенту просматривать архив заказов. Интегрируйте эту JSP-страницу в приложение книжного магазина.
232 Глава 4 4.8. Создайте и выполните разъертываяие единого приложения, которое дает возможность пользователю тестировать все примеры JSP-страниц из главы 3. Приложение должно содержать файл приветствия, который представляет собой XHTML-докумект, содержа- щий ссылки на каждый из примеров главы 3. 4.9. Создайте и выполните развертывание единого приложения, которое дает возможность тестировать все сервлеты из главы 2. Приложение должно содержать файл приветствия, который представляет собой XHTML-документ, содержащий ссылки на каждый из примеров главы 2,
5 Разработка приложений для беспроводной связи на базе Java и J2ME Цели • Построить трехуровневое приложение клиент/сервер. • Научиться использовать XML и XSLT для представления содержимого нескольким типам клиентов, • Уяснить основные возможности платформы Java 2 Micro Edition (J2ME) Platform. • Получить представление о жизненном цикле мидлета. • Научиться использовать средства CLDC и МЮР J2ME, • Понять, каким образом в практическом примере используются возможности технологии J2ME. Я знаю одно: по-настоящему счастливы будут лишь те из вас, которые будут искать и найдут свой путь служения обществу. Альберт Швейцер Это был удивительный, редкий прибор... Сэмюел Тейлор Колеридж Знание бывает двух видов. Мы либо знаем сам предмет, либо знаем, где можно найти информацию о нем. Сэмюел Джонсон ... должно быть, весь имеющийся свет сосредоточился на этой переплетенной сети... Джордж Эллиот Если вы способны делать обычные вещи необычным способом, вы заслуживаете всеобщего внимания. Джордж Вашингтон Карвер
234 Глава 5 5.1. Введение В этой главе представлен практический пример приложения для проведения тестирования (Tip-Test), предлагающего несколько вариантов ответов, чтобы читатели могли проверить, насколько хорошо они запомнили условные обозначения рубрик советов и рекомендаций по программированию, В каждом вопросе теста предлагается рисунок, обозначающий рубрику советов, и приводится список из четырех возможных вариантов ответов. Клиент загружает тест с сервера. Затем пользователь выбирает ответ, который ему кажется правильным, и отправляет его серверу. Сервер в ответ сообщает, является ли ответ верным или неверным. После этого клиент может загрузить другой вопрос теста, и так далее. Приложение Tip-Test имеет трехуровневую архитектуру, представленную на рис. 5.1. Информационный уровень состоит из базы данных и содержит таблицу (она определена в SQL^cueHapHH tips.sql), состоящую из семи строк и пяти столбцов. Каждая строка содержит информацию о рубрике советов по программированию Deitel: «Хороший стиль программирования» (Good Programming Practice), «Общая методическая рекомендация» (Software-Engineering Observation), «Совет по повышению эффективности» (Performance Tip), «Совет по переносимости программ» (Portability Tip), «Внешний вид программы» (Look-And-Feel Observation), «Совет по тестированию и отладке* (Testing and Debugging Tip) и «Типичная ошибка программирования» (Common Programming Error). В первом столбце базы данных хранятся целые числа, которые представляют собой уникальные идентификаторы для каждой рубрики советов. Во втором столбце хранятся названия рубрик советов. В третьем столбце хранятся описания рубрик советов и пояснения значений каждой из них. В четвертом столбце хранятся имена рисунков условных обозначений для каждой из рубрик советов. В пятом столбце хранятся сокращенные названая рубрик советов; например, рубрика *Хороший стиль программирования» (Good Programming Practice) имеет аббревиатуру GPP, На рис. 5,2 представлено содержимое базы данных tips.sql.
Разработка приложений для беспроводной связи на базе Java и J2ME 235 WAP i-mode ЙМЕ Клиентский уровень [— (верхний) I— □ □ □ □ Средний уровень WelcomeServlet TipTestServlet Информационный уровень (нижний) База данных Рис, 5.1. Трехуровневая архитектура приложения Tip Test Средний уровень представлен двумя сервлетами: WelcomeServlet и TipTestServlet. Сервлет WelcomeServlet доставляет «окно приветствия», которое первым отображается пользователю. Затем сервлет WelcomeServlet переадресовывает клиента к сервлету TipTestServlet. Используя базу данных, сервлет TipTestServlet произвольно выбирает рисунок для рубрики советов и четыре возможных варианта ответа (в виде аббревиатур названий рубрик советов) и размечает эту информацию как XML-документ. После этого сервлет TipTestServlet применяет XSLT-трансфор- мацию к XML-документу и отправляет полученное содержимое клиенту. tlpID 1 2 3 tipName Хороший стиль программирования (Good Programming Practice) Типичная ошибка программирования (Common Programming Error) Внешний вид программы Oonk-And-Feel Observation) tipDescription Рубрика «Хороший стиль программирования» привлекает внимание к приемам, которые... Студенты, изучающие язык программирования, как правлпо, совершают определенного рода ошибки... Мы предоставляем рубрику «Внешний вид программы», чтобы сконцентрировать внимание на графическом интерфейсе... tiplmage good Prog ramming prog ramming Error LookAndFeel shortName GPP CPE UF
236 Глава 5 tiplD 4 5 б 7 tipName Совет по повышению эффективности (Performance Tip) Совет no переносимости программ (Portability Tip) Общая методическая рекомендация (Software Engineering Observation) Совет по тестированию и отладке {Testing and Debugging Tip) tipDescription В рубрике «Совет по повышению эффективности» подчеркиваются яозможкости для повышения эффективности работы программ... Организации, разрабатываю! i 1ие программные продукты, часто предоставляют версии программы, настраиваемые... В рубрике «Общая методическая рекомендация» уделяется внимание приемам, архитектурным... Большая часть этих советов представляет собой замечания относительно возможностей и особенностей... tiplmage pe'f portability software Engineering testing Debugging shortName PERF PORT SFO TAD Рис. 5.2. Содержимое базы данных tips.sql Клиентский уровень представлен клиентами четырех типов: Internet Explorer, WAP (Wireless Application Protocol), i-mode и J2ME. Каждый из клиентов может отображать содержимое определенного типа. Сервлет TipTestServlet управляет всей логикой работы приложения, т.е. произвольно выбирает вопрос, определяет, является ли ответ пользователя правильным, и отправляет содержимое каждому из клиентов. Браузер Microsoft Internet Explorer принимает содержимое в формате XHTML (Extensible HyperText Markup Language). Имитатор браузера Openwave UP — это WAP-клиент, который принимает содержимое в формате WML (Wireless Markup Language). WAP — это протокол, который дает возможность беспроводным устройствам передавать информацию через Internet. Содержимое, отображаемое беспроводным устройством, размечается в формате WML. Pixo Internet Micro- browser — это клиент i-mode, который принимает cHTML-содержимое, i-jnode предоставляет популярный Internet-сервис на японском языке, a cHTML (компактный HTML) представляет собой подмножество языка HTML, предназначенное для устройств с ограниченными ресурсами. Имитатор MTDP-устройства Sun выступает в качестве клиента J2ME, который принимает содержимое в виде обычного текста. J2ME™ (Java™ 2 Micro Edition) — это новая платформа Java, созданная Sun, предназначенная для разработки приложений для различных бытовых устройств, таких как телевизионные игровые приставки, Web-терминалы, встроенные системы, мобильные телефоны и пейджеры. MIDP (Mobile Information Device Profile) представляет собой набор интерфейсов прикладного программирования, который дает возможность разработчикам решать специфичные для управления мобильными устройствами задачи, такие как создание интерфейсов пользователя, локальное хранение информации и подключение к сети. Устройства, на которых выполняются приложения для MIDP, называются MTDP-устройстеами (например, сотовые телефоны или пейджеры). Более подробно о J2ME и MIDP будет рассказываться в разделе 5.4.
Разработка приложений для беспроводной связи на базе Java и J2ME 237 При обсуждении данного практического примера будет рассмотрено, как каждый из сервлетов отправляет информацию клиентам каждого типа, и как эти клиенты отображают предоставленное им содержимое. В разделах 5.2 и 5.3 рассказывается, как сервлеты WelcomeServlet и TipTestServIet обрабатывают клиентские запросы. В разделах 5.3.1-5.3.4 рассматривается, как сервлет TipTestServIet реагирует на клиентские запросы, и как каждый из клиентов отображает сформированное сервлетом содержимое. Наконец, в разделе 5.4 обсуждаются возможности пакета Java 2 Micro Edition. Нас будут интересовать лишь клиентские возможности J2ME (поскольку наши сервлеты не используют технологию J2ME), поэтому эти возможности обсуждаются после рассмотрения сервлетов. До изучения раздела' 5.4 мы рекомендуем воспринимать клиента J2ME просто как «другой тип клиента», который принимает сформированное сервлетом содержимое (т.е. вам не нужно разбираться в технологии J2ME, чтобы понять поведение сервлета). В разделе 5.4 будет обсуждаться, как клиент J2ME принимает и интерпретирует содержимое, например, как клиент J2ME отображает эти данные в интерфейсе пользователя. Мы также рекомендуем следовать приведенным в разделе 5.5 инструкциям для установки и настройки программного обеспечения, используемого в данном практическом примере. 5.2. Обзор сервлета WelcomeServlet Для начала рассмотрим класс WelcomeServlet (рис. 5Л), который переадресовывает клиентский запрос статической странице, отображающей справочную информацию по работе с приложением Tip-Test. Эта статическая страница содержит ссылку на сервлет TipTestServIet, который дает возможность пользователю непосредственно выполнить тест. Клиенты взаимодействуют с сервлетами, посылая им запросы get или post. Клиенты посылают запросы get сервлету WelcomeServlet, чтобы получить окно приветствия. Если клиент посылает сервлету WelcomeServlet запрос get, этот запрос обрабатывается методом doGet (строки 15-39). Клиент каждого типа принимает от сервлета свое собственное окно приветствия, поскольку каждый из них поддерживает свой тип содержимого. Например, Internet Explorer принимает в качестве окна приветствия страницу Lndex.html, так как Internet Explorer способен отображать XHTML-документы. С другой стороны, имитатор Openwave UP принимает страницу index.wml, поскольку WAP-брау- зер может отображать только WML-документы. Имитатор МШР-устройств Sun может отображать только обычный текст, поэтому сервлет WelcomeServlet отправляет этому устройству файл index.txt.1 Браузер Pixo для i-mode может воспроизводить cHTML (компактный вариант HTML), поэтому сервлет посылает страницу index.html, отличную от той, которая предназначалась для Internet Explorer. 1 // WelcomeServlet.Java 2 // Доставка экрана приветствия клиенту 3 package com.deitel.advjhtpl.wireless; 4 5 // Базовый пакет Java 6 import java.io.*; 7 8 // Пакеты расширений Java На момент написания книги клиенты J2MB могли интерпретировать XML-документы только с помощью специального программного обеспечения, поставляемого дополнительно, т.е. какого-либо стандарта для интегрирования XML в J2ME не существовало.
9 import javax.servlet.*; 10 import javax. servlet.http. * ; 11 12 public class WelcomeServlet extends HttpServlet { 13 14 // oinev на sanpoc get 15 protected void doGet( HttpServletRequest request, 16 HttpServletResponse response ) 17 throws ServletException, IOException IB { 19 // определение заголовка User-Agent 20 String userAgent = request.getHeader( "User-Agent" ); 21 22 // отправка экрана приветствия соответствующему клиенту 23 if ( userAgent.indexOf { 24 ClientOserAgentHeaders.IE ) != -1 ) 25 sendXEClientResponse( request, response ); 26 27 else if ( userAgent.indexOf( // WAP 28 ClientUserAgentHeaders.WAP ) != -1 ) 29 sendWAFClientsesponse( request, response ); 30 31 else if ( userAgent.indexOf( // i-mode 32 ClientUserAgentHeaders.IMODB ) ! = -1 ) 33 sendIModeClientResponse( request, response ) ,- 34 35 else if ( userAgent.indexOf( // J2ME 36 ClientUserAgentHeaders.J2ME ) != -1 ) 37 sendJ2MEClientResponse( request, response ) ,- 38 39 > // конец метода doGet 40 41 // отправка экрана приветствия клиенту IE 42 private void sendlEClientResponse( 43 HttpServletRequest request, KttpServletResponee response ) 44 throws IOException, ServletException 45 { 46 redirect( "text/html", "/XHTML/index.html", request, 47 response ); « } 49 50 // отправка экрана приветствия WAP-клиенту Nokia 51 private void sendHAPClientResponse( 52 HttpServletRequest request, HttpServletResponse response ) 53 throws IOException, ServletException 54 { 55 redirect! "text/vnd.wap.wml", "/WAP/index.wml", request, 56 response ); 57 } 58 59 // отправка экрана приветствия клиенту i-mode 60 private void sendlModeClaentResponse( 61 HttpServletRequest request, HttpServletResponse response ) 62 throws IOException, ServletException 63 { 64 redirect( "text/html", "/iMode/index.html", request, 65 response );
Разработка приложений для беспроводной связи на базе Java и J2ME 239 66 } 67 в8 // отправка, экрана приветствия клиенту J2KE 69 private void sendJ2MEClientResponse( 70 HttpServletRequest request, HttpServletResponse response ) 71 throws TOException 72 { 73 // отправка текстовых данных клиенту J2ME 74 response.setContentType\ "text/plain" ); 75 PrintWriter out = response.getWriter(); 76 77 // открытие файла для отправки клиенту J2ME 78 BufferedReader bufferedRe&der = 79 new BufferedReader( new FileReader( 60 getServletContext0.getRealPath( 81 "j2me/index.txt" ) ) ); 82 83 String inputString = bufferedReader.readLine(); 84 85 // отправка каждой из содержащихся в файле строк клиенту J2ME 86 while { inputString != null ) { 87 out.println( inputString ); 88 inputString = buf f eredReader. readLine (),- 89 } 90 91 out.close(); // отправка данных 92 93 } // конец метода sendJ2MEClientResponse 94 95 // переадресация клиентского запроса другой странице 96 private void redirect( String contentType, String redirectPage, 97 HttpServletRequest request, HttpServletResponse response ) 98 throws IOException, ServletException 99 { 100 // установка нового типа содержимого 101 response. setContentType ( contentType ) ,- 102 RequestDispatcher dispatcher = 103 getServietCon.textO .getRequeatDiepatcher ( 104 redirectPage ); 105 106 // переадресация запроса странице redirectPage 107 dispatcher.forward( request, response ); 108 } 109 1 Рис. 5.3. Класс WelcomeServIet отправляет начальное окно, которое предоставляет клиенту справочную информацию, как пользоваться тестом Прежде чем ответить клиенту, метод doGet должен определить тип клиента, обратившегося с запросом. Каждый клиент включает в каждый запрос заголовок User-Agent. Этот заголовок содержит информацию о типе клиента, запросившего данные с сервера. В интерфейсе ClientUserAgentHeader (рис. 5.4) определены уникальные строки заголовка User-Agent для каждого из клиентов в нашем приложении. Например, заголовок User-Agent для браузера Microsoft Internet Explorer под Windows 2000, имеет следующий вид: Moailla/4.0 (compatible; MSIE 5.0; Windows NT 5.0}
240 Глава 5 Мы ищем подстроку "MSIE 5" в заголовке User-Agent, чтобы отличить запросы Internet Explorer от запросов, посылаемых браузерами других платформ. Кроме того, сервлет WelcomeServlet будет распознавать различные версии Internet Explorer 5 (например, v.5.0, v.5.5 и т.д.). Например, заголовок User-Agent для клиента Windows 98 может не совпадать с приведенным выше, но, тем ие менее, содержать подстроку "MSIE 5". 1 // ClientUserAgentHeaders.Java 2 // Содержит заголовки User-Agent для различных клиентов 3 package coin.deitel. advjhtpl. wireless ; 4 S public interface ClientUserAgentHeaders { 6 7 // заголовок User-Agent для браузера Internet Explorer 8 public static final String IE = "MSIE 5"; 9 10 // заголовок User-Agent для браузера WAP 11 public static final String WAP = "UP"; 12 13 // заголовок User-Agent для браузера iMode 14 public static final String IMODE = "Pixo"; 15 16 // заголовок User-Agent для браузера J2ME 17 public static final String J2ME = "MIDP-1.0"; 18 } Рис. 5.4. Интерфейс ClientUserAgentHeader содержит уникальны? строки заголовка User-Agent для всех типов клиентов В строке 20 класса WelcomeServlet из запроса HttpServJetRequest извлекается заголовок User-Agent. В строках 23-37 определяется, какой клиент послал запрос, путем сопоставления фактического заголовка User-Agent с заголовками, определенными в интерфейсе ClientUserAgentHeader. Если запрос отправлен браузером Internet Explorer, в строке 25 вызывается метод sendlEClientResponse (строки 42-48). В строках 46-47 вызывается метод redirect (строки 96-108), который переадресовывает запрос статической странице. В случае поступления запроса от Internet Explorer в строке 10 вызывается метод setContentType объекта HttpServ- letResponse для установки MIME-muna. Значение text/html представляет собой МШЕ-тип для клиентов XHTML. MIME-mun {Multipurpose Internet Mail Extension) помогает браузерам определить, как интерпретировать данные. В строках 102-107 осуществляется переадресация запроса странице index.html путем создания объекта RequestDispatcher с именем, соответствующим имени статической страницы, и вызова метода forward объекта RequestDispatcher. После этого браузер IE отображает страницу index.html (рис. 5.5). Если запрос был сделай имнгагором Openwave UP, в строке 29 вызывается метод sendWAPClientReaponse (строки 51-57), который вызывает метод redirect с указанием в качестве MIME-типа text/vnd.wap.wml (МШЕ-тип для WML клиентов) и переадресовывает запрос странице index.wml. После этого имитатор отображает страницу index.wml, как показано на рис. 5.6. Если запрос был сделан браузером Pixo i-mode, в строке 33 вызывается метод sendModeClientResponse (строки 60-66). Этот метод также вызывает метод redirect, но устанавливает в качестве MIME-типа text/html (МШЕ-тип для cHTML- клиентов), после чего запрос переадресовывается версии страницы index.html в формате cHTML. После этого браузер Pixo отображает страницу index.html, как показано на рис. 5.7.
Разработка приложений для беспроводной связи на базе Java и J2ME 241 eLeandug Deitel Programming Tips Рис. 5.5. Результат, формируемый сервлетом WekomeServlet (index.html) для клиента XHTML Рис. 5.6. Результат, формируемый сервлетом WekomeServlet (index.wml) для WAP-клиента. (Изображение UP SDK предоставлено с разрешения корпорации Openwave Systems Inc. Openwave, логотип Openwave и UP SDK. являются торговыми марками корпорации Openwave Systems Inc. Все права соблюдены,) Если запрос был сделал имитатором MIDP-устройства Sun, в строке 37 вызывается метод sendJ2MECIientBesponse (строки 69-93). Чтобы доставить страницу index.txt клиенту J2ME, сервлет должен отправить страницу index.txt через поток, существующий между сервлетом и клиентом J2ME. В строке 74 б качестве MIME-типа устанавливается text/plain. В строке 75 вызывается метод getWriter
242 Глава 5 класса HttpServletResponse для получения объекта PrintWriter и отправки данных клиенту, В строках 78-81 создается объект BufferedReader, который прочитывает файл index.txt из каталога j2me, расположенного в контексте сервлета. В строках 86-89 осуществляется отправка каждой строки, содержащейся в буфере BufferedReader, клиенту. После этого имитатор Sun отображает страниду index.txt, как показано на рис. 5.8, м ьшш. [\тш JJUai* гШш ■*fM*:"-:V^M?* бЦ"*; ФШ- PIXO }/rtemeWtcrabmvser2.1 Я 1 i \ •иг шш>>шт И : ншШШж ■ 1^Чреяй*^И с? ^ S •w <ca cs7 ■С^^£^Ж£ЁЯН№ Рис. 5.7. Результат, формируемый сервлетом WelcomeServlet (index.html) для клиента i-mode. (Публикуется с разрешения компании Pixo, Inc ) О Lx-M с^оглЖЩШ Рис. 5,8. Результат, формируемый сервлетом WelcomeServlet (index.txt) для клиента J2ME. (Публикуется с разрешения корпорации Sun Microsystems, Inc.)
Разработка приложений для беспроводной связи на базе Java и J2ME 243 5.3. Обзор сервлета TipTestServlet В этом разделе мы обсудим, каким образом сервлет TipTestServlet (рис. 5.9) формирует и посылает вопросы и ответы теста Tip-Test каждому из клиентов. Каждое окно приветствия содержит ссылку на окно «инструкций» — info.html, info.wml или info.txt, в зависимости от типа клиента, — которое содержит справочную информацию, как пользоваться тестом. Окно инструкций содержит ссылку на сервлет TipTestServlet.1 Если пользователь обращается к сервлету TipTestServlet, а сервлет ранее не получал запросов от клиента, контейнер сервлета вызывает метод init (строки 30—67) для инициализации сервлета TipTestServlet. Этот метод выполняет операции, которые сервлету TipTestServlet нужно выполнить только один раз, например, загрузка драйвера JDBC, установка соединения с базой данных и реализация экземпляров объектов для создания XML-документов. В строках 36 -38 из параметра инициализации сервлета в документе web.xml (который рассматривался при установке Tomcat) извлекается имя класса для драйвера базы данных JDBC, а в строке 40 осуществляется загрузка этого драйвера. 1 // TipTestServlet.java 2 // Сервлет TipTestServlet отправляет допросы теста Tip Test клиентом. 3 package com.deitel.advjhtpl.wireles s; 4 5 // Набор базовых пакетов Java 6 import Java. i-o. *; 7 import java.sql.*; 8 import java.util.*; 9 10 // Пакеты расширений Java 11 import javax.servlet.*; 12 import j avax.servlet.http.*; 13 import javax.xml.parsers.*; 14 import javax.xml.transform.*; 15 import j avax.xml.transform.dom.*; 16 import javax.xml-transform.stream.*; 17 18 // импорт пакетов сторонних поставщиков 19 import org.w3c.dom.*; 20 import org.xml.sax.SAXException; 21 22 public class TipTestServlet extends HttpServlet { 23 24 private Connection connection; // соединение с базой данных 25 26 private OocumentBuilderFactory factory; 27 private TransformerFactory transformerFactory; 28 29 // инициализация сервлета 30 public void initO throws ServletExeeption 31 { 32 // загрузиа драйвера базы данных и реализация мастеров XML 33 try { 34 1 Клиенту не обязательно осуществлять доступ к сервлету TipTestServlet через сервлет WelcomeServlet. Пользователь может ввести UEL TipTestServlet в браузере и обойти «окно приветствия», которое генерирует сервлет WelcomeServlet.
244 Глава 5 35 // получение драйвера JDBC ив контейнера сервлетов 36 String jdtocDriver = 37 getServletConfigO .getlnitParameter< 38 "JDBCJDRIVER" }; 39 40 Class.forHame ( jdbcDriver ); // загрузка драйвера JDBC 41 42 // получение ORL базы данных ив контейнера сервлетов 43 String databaseUrl = 44 getServletConfigO.getlnitParameter( 45 "DATABASE_URL" ) ; 46 47 connection = DriverManager .getConnection ( databaseUrl ) ,- 48 49 // создание мастера Factory для формирования XML-документов 50 factory = DocujnentBuilderFactory .newlnstance () ; 51 52 // создание нового мастера TransformerFactory 53 transformerFactory = TransformerFactory.newlnstanceО; 54 55 } // конец блока try 56 57 // обработка исключения, если драйвер базы данных не существует 59 catch ( ClassNotFoundException classNotFoundException ) { 59 classNotFoundException.printStackTrace(); €0 } 61 62 // обработка исключения при установке соединения €3 catch ( SQLExceptioa sglException > { 64 sqlException.printStackTrace(); 65 } 66 67 ) // конец метода in.it 68 69 // ответ на запросы get 70 protected void doGet( HttpSeirvletRequest request, 71 HttpServletResponse response ) 72 throws ServletException, lOException 73 ( 74 // получение оператора Statement из базы данных и отправка вопроса теста lip-Test 75 try I 76 77 // SQL-запрос к базе данных 78 Statement statement = connection.createstatement(); 79 80 // получение информации ив базы данных через SQL-запрос 81 ResultSet resultSet = 82 statement.executeQuery< "SELECT * FROM tiplnfo" ); 83 84 // синтаксический разбор и отправка результирующего множества ResultSet клиенту 85 if ( resultSet != null } ( 86 87 // запрет кэширования вопросов теста клиентон 88 response.setHeader( "Cache-Control",
Рагработка приложений для беспроводной связи на базе Java и J2ME 245 89 "no-cache, must-revalidate" ); 90 response.setHeader( "Pragma", "no-cache" ); 91 92 sendTipTestQuestionf request, response, resultSet ); 93 } 94 95 statement.close(); // закрытие оператора Statement 96 } 97 98 // обработка исключения при выполнение оператора SQL 99 catch ( SQXiException sqlException ) { 100 sqlException.printSt&ckTraceО ; 101 ) 102 103 } // конец метода doGet 104 105 // ответ на запросы post 106 protected void doPost( HttpServletRequest request, 107 HttpServletSespon&e response ) 108 throws ServletException, IOException 109 { 110 // отправка результирующего множества ResultSet соответствующему клиенту 111 try { 112 113 // определение заголовка User-Agent 114 String usееAgent = request.getHeader( "User-Agent" ); 115 116 // если клиентом является Internet Explorer 117 if { userAgent.indexOf( 118 ClientUserAgentHeaders.IE ) 1= -1 ) { 119 120 Document document = 121 createXMLTipTestAnswer( request ); 122 123 // задание соответствующего типа содержимого Content-Type для клиента 124 response.setContentType( "text/html" ); 125 126 // отправка. XML-сонержииого после XSLT-трансформации 127 applyXSLT( "XHTML/XHTMLTipAnswer.xsl", document, 128 response ); 129 } 130 131 // если клиентом является WAP-браузер 132 else if ( userAgent.indexOf{ 133 ClientUserAgentHeadeSs.WM> ) '= -1 ) { 134 135 Document document = 136 createXMLTipTestAnswer[ request ) ; 137 138 // задание соответствующего типа содержимого Content-Type для клиента 139 response.setContentType( "text/vnd.wap.wnl" 1 ; 140 141 // отправка XML-содержимого после XSLT-трансформации
246 142 applyXSLT( "HAP/WAPTipAnswer.xsl", document, 143 response ); 144 } 145 146 // если клиентом является браузер i-mode 147 else if ( userAgent.indexOf( 148 ClientOserAgentHeaders.IMODE ) != -1 ) ( 149 150 Document document = 151 createXMLTipTestAnswer( request ); 152 153 // Задание соответствующего типа содержимого Content-Type для клиента 154 response.setContentType{ "text/html" ); 155 156 // отправка XML-содержимого после XSLT-трансформации 157 applyXSLT( "iMode/IMODETipAnswer.xsl", document, 158 response }; 159 > 160 161 // если клиентом является J2ME 162 else if ( userAgent.indexOf( 163 ClientUserAgentHeaders.J2ME ) != -1 ) 164 sendJ2M£Answer( request, response ); 165 166 } // конец блока try 167 168 // обработка исключения, если документ отсутствует (null) 169 catch ( NullPointerException nullPointerException ) { 170 nullPointerException.printStackTrace(); 171 ) 172 173 } П конец метода doPost 174 175 // отправка данных теста Tip-Test клиенту 176 private void sendTipTestQuestion( 177 HttpServletRequest request, HttpServletResponse response, 178 ResultSet resultSet > throws IOException 179 { 190 // отправка результирующего множества ResultSet соответствующему клиенту 181 try { 182 183 // определение Заголовка User-Agent 184 String userAgent = request.getHeader( "User-Agsnt" ); 185 186 // если клиентом является Internet Explorer 187 if ( userAgent.indexOf( 188 ClientOserAgentHeaders.IE )!=-!)( 189 190 Document document = 191 createXMLTipTestQuestion( resultSet, request, 192 request.getContextPath() + "/XHTML/images/", 193 ",gif" ); 194 195 // задание соответствующего типа содержимого
Разработка приложений для беспроводной связи на базе Java и J2ME 247 Content-Type для клиента 196 response.setContentType{ "text/html" ); 197 applyXSLT( "XHTML/XHTMLTipQuestion.xsl", document, 198 response ); 199 ) 200 201 // если клиентом является НАР-браузер 202 else if ( userAgent.indexOf( 203 ClientUserAgentHeaders.WAP ) != -1 ) { 204 205 Document document = 206 createXMLTipTestQoestiont resultSet, request, 207 request.getContextPathO + "/WAP/images/", 208 ".wbmp" ); 209 210 // задание соответствующего типа содержимого Content-Type для клиента 211 response.setContentType( "text/vnd.wap.wml" ); 212 applyxsLT( "WAP/WAPTipQuestion.xsl", document, 213 response ); 214 } 215 216 // если клиентом является браузер i-mode 217 else if ( userAgent.indexOf( 218 ClientUserAgentHeaders.IMODE ) != -1 ) { 219 220 Document document = 221 createXMI>TipTestQuestion( resultSet, request, 222 request.getContextPath[) + "/iMode/images/", 223 ".gif" ); 224 225 // Задание соответствующего типа содержимого Content-Type для клиента 226 response.setContentType ( "text/html" ); 227 applyXSLT( "iMode/lMODETipQuestion.xsl", document, 226 response );- 229 } 230 231 // если клиентом является J2KE 232 else if ( userAgent.indexOf( 233 ClientUserAgentHeaders.J2ME ) != -1 ) 234 sendJ2MEClientResponse( resultSet, request, 235 response ); 236 237 1 // конец блока try 238 239 // обработка исключения, если документ отсутствует (null) 240 catch ( NullPointerException nullPointerExceptlon ) { 241 nullPointerException.printStackTrace{); 242 } 243 244 } // конец метода sendTipTestQuestion 245 246 // отправка вопросов теста Tip Test клиенту Internet Explorer 247 private Document createXMLTipTestQuestion( 248 ResultSet resultSet, HttpServletRequest request,
248 Глава 5 249 String imagePrefix, String iroageSuftix ) 250 throws IOException 251 { 252 // преобразование множества ResultSet в двухмерный строковый массив 253 String reaultTable[][] = getResultTablet resultSet ); 254 255 // создание генератора случайных чисел 256 Random random = new Random( System.currentTimeMillisО )i 257 258 // создание 4 произвольно выбранных рубрик советов 259 int randomRow[] = getRandomlndices{ random ); 260 261 // произвольное определение верного индекса из 4 случайно выбранных индексов 262 int correctAnswer = Math.abs( random.nextlnt() ) % 2 63 randotnRow. length; 264 2 65 int correctRow = randomRowf correctAnswer ]; 266 267 // открытие нового сеанса 268 HttpSession session = request. getSessionO; 269 270 // сохранение правильного ответа в данных сеанса 271 session.setAttribute( "correctAnswer", 272 new Integer( correctAnswer ) ); 273 274 // сохранение правильного названия рубрики 275 session.setAttributef "correctTipMame", new String( 276 resultTable[ correctRow ][ 1 ] ) ); 277 278 // сохранение правильного описания рубрики 279 session.setAttribute( "correctTipDescription", new String( 2B0 resultTable[ correctRow ][ 2 I ) ); 281 282 // определение изображения, отправляемого клиенту 263 String imageName - imagePrefix + 284 resultTable[ correctRow ][ 3 ] + imageSuffix; 285 286 // создание XML-документа на основе случайным образом определенной информации 287 try { 288 289 // создание документа 290 DocumentBuilder builder = factory.newDocumentBuilder{); 291 Document document = builder.newDocument{); 292 293 // создание лоряевого элемента question 294 Element root = document.createElement( "question" ); 295 document.appendChild{ root ); 296 297 // добавление элемента image, указывающего на имя файла изображения 298 Element image = document.createElement( "image" }; 299 image.appendChild( 300 document.createTextNode( imageName } );
Разработка приложений для беспроводной связи на базе Java и J2ME 249 301 root.appendChild( image ); 302 303 // создание элемента choices дли хранения 4 элементов choice 304 Element choices = document.createElement( "choices" ); 305 306 // добавление 4 элементов, choice, представликютх выбор пользователя 307 for ( int i = 0; i < randomRow.length; i++ ) 308 { 309 // определение элементов choice из таблицы resultTable 310 Element choice = document.createElement[ "choice" ); 311 choice,appendChildI document,createTaxtHode( 312 resultTable[ randomRowf i ] ][ 4 ] ) ); 313 314 // установка элемента choice как правильного или неправильного ответа 315 Attr attribute = 316 document.createAttributet "correct'' ) ; 317 318 if ( i = correctAnswer ) 319 attribute.setValue< "true" ); 320 else 321 attribute.setValue ( "false" ); 322 323 // добавление элемента choice в элемент choices 324 choice.setAttributeNode( attribute ); 325 choices.appendChiId( choice ); 326 } 327 32B root.appendChild( choices ); 329 330 return document; 331 332 ) // конец блока try 333 334 // обработка исключения при построении документа 335 catch ( ParserConfigurationExeeption parserException ) { 336 parse rExcept ion.printStacMrace(); 337 } 338 339 return null; 340 341 } // конец метода createXMLTipTestQuestion 342 343 // отправка вопросов теста клиенту J2ME 344 private void sendJ2MEClientResponse{ ResultSet resultSet, 345 HttpServletRequest request, 346 HttpServletResponse response ) throws lOException 347 { 348 // преобразование множества ResultSet в двухмерный строковый массив 349 String resultTable[][] = getResultTable( resultSet ); 350 351 // создание генератора случайных чисел 352 Random random = new Kandom( System.currentTimeMillis0 ); 353
250 354 // создание 4 случайно выбранных рубрик советов 355 int randomRow[] = getRandomlndices( random ); 356 357 // произвольное определение правильного индекса из 4 случайно выбранных индексов 358 int correctAnswer = Math.abs{ random,nextlnt() ) % 359 randomRow.1ength ; 360 361 int correctRow = randomRowt correctAnswer ]; 362 363 // открытие прежнего сеанса 364 KttpSeasion session = request.getSession(); 365 366 // сохранение правильного ответа в данных сеанса 367 session.setAttribute{ "correctAnswer", 368 new Integer{ correctAnswer ) ); 369 370 // сохранение правильного названия рубрики в данных сеанса 371 session.setAttribute( "correctTipHame", new String( 372 resultTable[ correctRow ][ 1 ] ) ); 373 374 // сохранение правильного описания рубрики в данных сеанса 375 session.setAttribute( "correctTipDescription", new String{ 376 resultTable[ correctRow ][ 2 ] ) J; 377 378 // отправка клиенту J2ME имени файла изображения 37 9 String imageName = "/j2ir.e/images/" + 380 resultTable[ correctRow ][ 3 ] + ".png",- 381 382 response.setContentType( "text/plain" ); 383 PrintWriter out = response.getWriter{)/ 384 out.println( xmageName ); 385 386 // отправка вопроса теста клиенту J2ME 387 for ( int i = 0; i < randomRow. length,- i++ ) 388 out.println( resultTable! randomRow[ i ] ][ 4 ] ); 389 390 } // конец метода sendJ2MEClientResponse 391 392 // преобразование множества ResultSet в двухмерный строковый массив 393 private String[][] getResultTable( ResultSet resultSet ) 394 { 395 // создание таблицы строк для хранения результирующего множества ResultSet 396 String resultTable[][] = new String[ 7 ][ 5 ],- 397 398 for ( int i = 0; i < 7; i++ ) { 399 400 for ( int 3 = 0; j < 5; j++ ) 401 resultTable[ i ][ j ] = ""; 402 ) 403 404 // сохранение всех столбцов в таблице 405 try {
Разработка приложений для беспроводной связи на базе Java и J2ME 251 407 // для каждой строки в resultSet 408 for ( int tow = Q; resultSet.nextl)г row++ ) { 409 410 // для каждого столбца в resultSet 411 for ( int column = 0; col-шип < 5; column.*-*- ) \ 412 413 // сохранение элемента resultSet в таблице resultTable 414 resultTable[ row }[ column ] += 415 resultSet,getObject( column + 1 ); 416 ) 417 } 418 } 419 420 // обработка исключения, если сервлет не может получить объект ResultSet 421 catch ( SQLException sqlException ) { 422 sqlException.printstacKTraceO; 423 return null; 424 } 425 426 return resultTable; 427 428 ) // конец метода getResultTable 429 430 // получение 4 случайно сформированных индексов иэ таблицы resultTable 431 private intИ getRandomlndices( Random random ) 432 { 433 // создание списка, содержащего индексы строк для resultTabLe 434 int listl] = new int[ 7 ]; 435 436 for ( int i = 0; i < list.length; i++ ] 437 list[ i ] = I; 438 439 int randomRow[] = new int( 4 ]; 440 441 // выбор 4 случайно сформированных индексов иэ списка 442 for ( int i = 0; i < randomRow.length; i++ ) 443 randomRow[ i ] = getRandomRow( list, random ) ; 444 445 return randomRow; // возврат этих индексов 446 447 } // конец метода getRandomlndices 448 449 // получение прсивволыюпо элемента иэ списка и его аннулирование 450 private int getRandomRow( int list[]. Random random ) 451 { 452 // получение произвольного элемента из списка 453 int randomRow = Math.abs( random.nextlnt() ) % list.length; 454 455 while ( list[ randomRow ] < 0 ) 456 randomRow = Math.aba( random.nextlnt() ) % list.length; 457 45S liatt randomRow ] = -1; // аннулирование элемента 459 4 60 return randomRow;
252 Глава 5 461 462 > // конец метода getRandomRow 463 464 // применение таблицы стилей XSL к XML-документу 465 private void applyXSLT( String xslFile, 466 Document xmlDocument, RttpServletResponse response ) 467 throws IOException 468 { 469 // применение XSLT-трансформацки 470 try { 471 472 // открытие потока ввода InputStream для XSL-документа 473 InputStream xslStream = 474 getServletContextO.getResourceAsStream( xslFile ); 475 476 // создание потока StreamSource для XSLT-документа 477 Source xslSource « new StreamSource( xslStream ); ■178 4 79 // создание объекта DOMSource для исходного XML-документа 480 Source xmlSource = new DOMSouree( xmlDocument ); 481 482 // получение объекта PrintWriter для записи данных клиенту 483 PrintWriter output = response.getwriter(); 484 485 // создание объекта StreamRestilt для трансформации результата 486 Result result = new StreamResult( output ); 487 4BB // создание объекта Transformer для XSL-трансформации 489 Transformer transformer = 490 transformerFactory. newT ran a former ( xslSource ) ,- 491 492 // трансформация и доставка содержимого клиенту 493 transformer.transform( xmlSource, result ) ; 494 495 } // конец блока try 496 497 // обработка исключения при трансформации содержимого 498 catch ( TransformerException exception ) { 499 exception.printstackTrace(); 500 } 501 502 } // конец метода applyXSLT 503 504 // создание XML-документа, содержащего ответ на вопрос теста TipTest 505 private Document createXMLTipTestAnswer( 506 HttpServletRequest request ) throws IOException 507 { 508 // получение сеанса 509 HttpSession session = request.getSession(); 510 511 // сопоставление правильного ответа с ответом, полученным в ходе сеанса 512 Integer integer = 513 ( Integer ) session.getAttribute( "correctAnswer" );
Разработка приложений для беспроводной связи ма базе Java и J2M6 253 514 int correctftnswer = integer.Antvalue(); 515 516 // предоставление клиенту правильного названия рубрики советов и ее описания 517 String correctTipMame = 518 ( String ) session.getAttributef "correctTipName" ); 519 520 String correctTipDescfiption = 521 ( String ) session.getAttribute( 522 "correctTipDescription" ); 523 524 // получение выбора от пользователя 525 int selection = Integer.parselntt 526 request,getParameter( "userAnswer" ) ) ,- 527 529 String answer; 529 530 // определение, является ли выбор пользователя правильным 531 if < correctftnswer = selection ) 532 answer = "Correct"; 533 else 534 answer = "Incorrect"; &35 536 // получение ссылки на сервлет TipTestServlet 537 String servletName = request.getContextFathО + "/" + 538 getServletConfigQ .getServletName() ; 540 // создание XML-докумеита на основе случайным обрааои определенной информации 541 try { 542 543 // создание документа 544 DocumentBuilder builder = factory. newDocumentBuilder () ,- 545 Document document = builder-newDoOUment() ; 546 547 // создание корневого элемента question 548 Element root = document.createElement( "answer" ); 549 document.appendChild! root ); 550 551 // добавление элемента, информирующего клиента о правильном ответе 552 Element correct = document.createElement( "correct" ); 553 correct.appendChild{ 554 document.createTextNode( answer ) ); 555 root.appendChild( correct ) ; 556 557 // добалекие Элемента, содержащего название рубрики советов 558 Element name = 559 document.createElement( "CorrectTipName" ); 560 name.appendChild( 561 document.createTextNode( correctTipName > ); 562 root.appendChild( name ); 563 564 // добавление элемента, содержащего описание рубрики советов 565 Element description = 566 document.createElement( "correctTipDescription" );
2S4 Глава 5 567 description.appendChild( 568 document.createTextNode( correctTipDescription ) ); 569 root.appendChild( description ); 570 571 // добавление элемента, содержащего ссылку на сервлет TipTeatServlet 572 Element aervletLink = 573 document.cre&teElement{ "eervletHame" ); 574 8ervletLink.app*ndChild( 575 document.createTextNode{ servletName ) ); 576 root.appendChildt aervletLink ); 577 57В return document; 579 580 } // конец блока try 581 582 // обработка исключения при построении документа 583 catch ( ParserConfigurationException parserException ) { 584 parserException.printStackTrace(); 585 } 586 587 return null; 588 589 ] // Конец метода createXMLTipTestAftswer 590 591 // отправка ответа клиенту J2ME 592 private void sendJ2MEAnsver( HttpServletRequest request, 593 HttpServletResponse response ) throws IOException 594 { 595 // получение ответа клиента на вопрос теста 596 BufferedReader in = request.getReader(); 597 int selection = Integer.parselntf in.readLine{),trim() ); 598 599 // отправка клиенту J2ME текстовых данных 600 response. sefcContentType ( "text/plain" ) ,* 601 PrintWriter out = response.getWriter(); 602 603 // информирование клиента, является ли его ответ правильным или неправильным 604 HttpSession session = request.getSessionf); 605 606 // сопоставление правильного ответа с ответом, полученным в ходе сеанса 607 Integer integer = 608 ( Integer ) session.getAttribute( "correctAnswer" ); 609 int coxrectAnswer = integer.intValue(); 610 611 // отправка правильного названия рубрики советов и ее описания 612 String correctTipName = 613 ( String ) session.getAttribute( "correctTipName" ); 614 615 String correctTipDescription = 616 ( String ) session.getAttribute( 617 "correctTipDescription" ); 618 619 /J определение/ является ли ответ правильным
Разработка приложений для беспроводной связи на базе Java и J2ME 255 620 if ( selection = correctAnswer ) 621 out.println( "Correct" ); 622 else 623 out.println( "Incorrect" ); 624 625 // предоставление клиенту правильного названия рубрики советов и ее описания 626 out.println( correctTipName ); 627 out.println ( correctTipDeseription ); 628 629 ) // конец метода sendJ2MEAnswer 630 631 // метод, вызываемый при закрылки сервлета 632 public void destroy() 633 { 634 // закрытие соединения с базой данных 635 try { 636 connection.close О; 637 ) 638 639 // обработка исключения, если соединение не может быть Закрыто 640 catch ( SQLExeeption sqlException ) { 641 sqlException-printStackTiaceО ; 642 } 643 644 } // конец метода destroy 645 } Рис. 5.9. Сервлет TipTestServiet реализует основную логику работы теста и посылает вопросы теста Ttp-Tes1 клиентам Использование метода getlnitParameter в строке 37 дает возможность задавать внешнюю для сервлета базу данных, При этом можно менять используемую базу данных (например, вместо базы данных Cloudscape использовать базу данных mySQL), модифицируя элемент <param-value> в элементе <init-parara> документа web.xinl без повторной компиляции сервлета. В данном практическом примере этот элемент содержит <init-param> <param-name> JDBC_DRTVER </paxam-name> <param-value> COM.cloudscape.core.BmiJdbcDriver </param-value> </init-psram> В строках 43—45 из документа web.xml извлекается URL базы данных, а в строке 47 осуществляется соединение с ресурсом., задаваемым этим URL. Заметим, что закрытие этого соединения осуществляется методом destroy (строки 632-644). Если возникнет необходимость изменить местоположение базы данных, для этого нужно будет всего лишь модифицировать элемент <param-valu.e> в отдельном элементе <init-param> документа web.xml. В данном практическом примере этот элемент содержит <init-param> <param-name> DATABASE_ORL </param-name> <parasi-value>jdbc: cloudscape; rroi: tips </param-val,ue> </init-param>
2S6 Глава S В строках 50-53 создаются объекты, которые позднее будут использоваться для построения XML-документов (объекты Document), и объекты Transformer для применения XSLT-трансформаций. Далее в этом разделе будут рассмотрены пакеты Java, относящиеся к XML. Сервлет TipTestServlet вызывает метод doGet (строки 70-103) для обработки запроса get. В строке 78 из объекта соединения Connection, реализованного в методе init, создается объект оператора SQL Statement. В строках 81^82 в результате действий этого оператора извлекается результирующее множество ResultSet всех элементов базы данных. Как говорилось ранее, база данных (объект ResultSet) содержит семь строк и пять столбцов — всего 35 строковых объектов. В строках 88-90 задается заголовок ответа HttpServletResponse, чтобы клиент не кэширо- вал информацию, отправляемую сервлетом TipTestServlet. В строке 92 осуществляется вызов метода sendTipTestQuestion (строки 176-244) для реализации логики работы теста и отправки клиенту вопроса теста Tip-Test. В строке 184 из запроса HttpServletRequest извлекается заголовок User-Agent. В строках 187-235 определяется тип клиента, пославшего запрос, путем сопоставления фактического заголовка User-Agent с заголовками, определенными в интерфейсе ClientUser- AgentHeaders. Процесс определения типа клиента идентичен процессу, который имел место в сервлете Welcomes ervlet. До этого момента сервлет выполнялся одинаковым образом для всех клиентских запросов. Теперь же сервлет TipTestServlet начинает выполнять различные операции в зависимости от типа клиента, пославшего запрос. Эти операции почти идентичны для Internet Explorer, имитатора Openwave UP и браузера i-mode Pixo. Операции же, выполняемые сервлетом для клиента J2ME, отличаются от операций, выполняемых для трех других типов клиентов, поскольку клиент J2ME не может интерпретировать XML-документы без использования дополнительного специализированного программного обеспечения (хотя в будущем ситуация, вероятно, изменится). Далее мы рассмотрим поведение сервлета в случае поступления запроса от клиента каждого из поддерживаемых типов. Начнем мы с рассмотрения поведения сервлета, если запрос поступил от имитатора Openwave UP. Затем мы обсудим, как себя ведет сервлет, если запрос поступил от браузера i-mode Pixo. После этого ш рассмотрим поведение сервлета в случае поступления запроса от MIDP-устройства Sun. 5.3.1. Запрос от браузера Internet Explorer Если запрос был отправлен Internet Explorer, в строках 190-193 вызывается метод createXMLTipTestQuestion (строки 247-341), который создает и возвращает XML-документ (объект Document), содержащий вопрос теста Tip-Test. Интерфейс Document из пакета org.w3c.dom представляет собой узел верхнего уровня XML-документа, который обеспечивает доступ ко всем узлам документа. В строке 253 осуществляется вызов метода getResultTable (строки 393-428), который преобразует результирующее множество ResultSet в двухмерный массив строк. Этот метод используется для облегчения доступа к отдельным элементам результирующего множества ResultSet, организованным в виде массива. В строках 396-402 реализуется экземпляр этого массива, а в строках 408-417 массиву передается содержимое результирующего множества ResultSet. После того как метод getResuItTable возвращает строковый массив, в отроке 256 создается объект Random, который дает возможность сервлету TipTestServlet произвольным образом выбирать рубрику советов и возможные варианты ответа. В строке 259 ссылка на объект Random передается методу getRandomlndices (строки 431-447), который возвращает массив из четырех целых чисел. Каждое целое число имеет свой, случайно выбранный индекс, который соответствует руб-
Разработка приложений для беспроводной связи на базе Java и J2ME 257 рике советов из двухмерного строкового массива, формируемого методом getRe- sultTable. В строках 434—437 создается массив с именем list из семи целых чисел. Каждое целое число представляет собой индекс этого целого числа в массиве (т.е. list[0]=0, Hst[l]=l и т.д.), В строке 439 создается пустой массив из семи целых чисел, который соответствует четырем возможным ответам (названиям рубрик советов). В строках 442-443 осуществляется вызов метода getRandomRow (строки 450-462) для каждого элемента в массиве из четырех целых чисел. В строке 453 случайным образом выбирается число из массива, содержащего семь целых чисел. Если число уже было выбрано ранее (это индицируется значением —1), в строках 455-456 выбирается новое число. Если данное целое число ранее не было выбрано, в строке 458 осуществляется аннулирование элемента этого массива путем присвоения ему значения —1. Аннулирование этого элемента гарантирует, что этот метод getRandomRow не будет возвращать повторяющихся целых чисел. В строке 460 возвращается целое число. Массив randomRow содержит четыре различных целых числа, которые соответствуют рубрикам советов из таблицы resultTable. В строках 262—263 произвольно выбирается целое число из массива randomRow. Это целое число указывает на правильный ответ на вопрос теста. Фактически, это целое чиело соответствует строке в таблице resultTable, которая содержит информацию для правильного названия рубрики советов (строка 265). Сервлет TipTestServlet хранит эту информацию в объекте HttpSession (строка 268), чтобы при отправке клиентом сделанного пользователем выбора обратно сервлету TipTestServlet последний мог сравнить этот выбор с правильным ответом, хранящимся в объекте HttpSession. Объект HttpSession действует подобно cookies: он также может хранить пары имя/значение. Применительно к сеансу такие пары называются атрибутами. Атрибуты можно сохранить в объекте HttpSession с помощью метода setAttribute. В строках 271-280 осуществляется сохранение правильного ответа, названия рубрики и описания рубрики советов в объекте HttpSession. С учетом правильного ответа и параметров imagePrefix и imageSuffix метода createXMLTipTestQuestion, в строках 283-284 определяется надлежащее условное изображение (рисунок) рубрики, отправляемое клиенту. Итак, сервлет TipTestServlet сформировал вопрос теста Tip-Test (изображение и четыре возможных варианта ответа), который будет отправлен клиенту. Теперь поясним, как сервлет TipTestServlet размечает эти данные в виде XML-документа, применяет XSLT-трансформацию к этому документу, а затем отправляет полученный документ клиенту. В строках 290-328 выполняется разметка вопроса теста Tip-Test в виде XML- документа (объект Document), и предоставляются средства для создания и извлечения его узлов. В строке 290 создается объект DocumentBuilder с помощью мастера DocumentBuilderFactory, экземпляр которого реализуется в методе init. В строке 291 с помощью DocumentBuilder создается XML-документ. Класс Element представляет узел элемента в XML-документе- Объект Document создает элемент (объект Element) с помощью метода createElement. В строках 294-295 создается объект Element с именем question, который становится корневым элементом в документе. В строках 298-301 на основе верного условного изображения рубрики советов создается элемент (объект Element) с именем image. Этот элемент становится дочерним узлом элемента question. В строке 304 создается элемент с именем choices, который содержит четыре элемента choice, созданных в строках 307-326. Каждый элемент choice представляет собой одни из вариантов ответа ка вопрос теста. В строках 315-324 осуществляется включение атрибута, указывающего, является ли сделанный выбор правильным (correct) или неправильным (incorrect). Естественно, только один из атрибутов может указывать на верное значение (correct). В строке 325 осуществляется добавление элемен-
258 Глава 5 тов choice в элемент choices, а в строке 328 элемент choices добавляется в корневой элемент question. После того как метод createXMLTipTestQuestion возвратил XML-документ (объект Document), сервлет TipTestServlet должен применить XSLT-трансформацию к этому документу и отправить результат трансформации клиенту. В строке 124 в качестве ЩМЕ-ткпа ответа HttpServletResponse устанавливается text/html, поскольку XSLT-трансформация генерирует XHTML-документ. В строках 127-128 осуществляется вызов метода appIyXSLT (строки 465-502) для применения к XML-документу таблицы стилей XHTMLTipQuestion.xsl (ряс. 5.10). В строках 473-474 открывается поток ввода InputStream для файла XHTMLTipQuesti- on.xsl. В строке 480 создается объект DOMSource для XML-документа. В строке 486 создается объект Result для результата трансформации — в данном случае, XHTML-документа. Сервлет TipTestServlet использует объект Print Writer для создания объекта результата Result, чтобы отправлять результаты трансформации непосредственно клиенту. В етроках 489—490 создается объект Transformer с использованием XSL-таблицы StreamSource и мастера TransforraerFactory (экземпляр которого реализуется в методе init). Transformer — это объект, который применяет XSLT-трансформациго к XML-документу. В строке 493 осуществляется вызов метода transform класса Transformer для применения трансформации и получения XHTML-документ, В листинге на рис. 5.10 в строках 9—11 с помощью элемента xsl:output задается DTD и другие параметры вывода. В строке 15 задается, что элемент question является корневым элементом XML-доку мента, который будет подвергнут трансформации с помощью таблицы стилей XrlTMLTipQuestion.xsl. XHTML-документ начинается в строке 16 элементом html. Элемент body в строках 22-86 содержит описание рисунка для рубрики советов и четыре возможных ответа. В строках 27-29 отображается рисунок, ассоциированный с элементом image в XML-документе. В строках 40-83 создается форма с четырьмя возможными вариантами ответов. В строках 47-77 создаются четыре переключателя, ассоциированные с этими ответами. В строке 82 создается кнопка Submit, чтобы пользователь мог отправить сделанный выбор серверу. На рис. 5.11 представлено приложение Tip-Test в действии после применения сервлетом XSLT-трансформации к XML-документу. На рисунке слева показана фаза выбора ответа пользователем, а на рисунке справа — фаза отправки ответа пользователем. 1 <?xml version="1.0"?> 2 3 <!— XHTMLTipQuestion.xel --> 4 <!— таблица свилей XHTML --> 5 6 <xsl:stylesheet version = "1.0" 7 xmlneixsl = "http://www.w3.org/1999/XSL/Transform"> 8 9 <xsl:output method = "xml" omit-xml-declaration = "no" 10 doctype-system = "DTD/xhtmll-strict.dtd" 11 doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/> 12 13 <•-- задание корня XML-документа, —> 14 <>-- который указывает иа эту таблицу стилей --> 15 <xsl: template match = "<Juestion"> 16 <html xmlns="http://www.w3.org/1999/xhtml"> 17 18 19 <head> 20 <title>Tip Test</title>
Разработка приложений для беспроводной связи на базе Java и J2ME 259 21 </head> 22 23 <body> 24 25 <р> 26 27 <!— вывод изображения —> 28 <img name = "image" alt = "Tip Image" 29 src = "{image}"> 30 </img> 31 32 </p> 33 34 <p> 35 What is the name of the icon shown? 36 </p> 37 38 <p> 39 40 <!— создание формы с четырьмя переключателями —> 41 <form method = "poet" 42 action = "/advjhtpl/tiptest"> 43 44 <!— построение таблицы с вариантами выбора —> 45 <table> 46 <tr> 47 <td> 48 <inptit type = "radio" 49 name = "userAnswer" value = "0"> 50 </input> 51 <xsl:value-of select = 52 "choices/choice[1]"/> 53 </td> 54 <td> 55 <input type = "radio" 56 name = "userAnswer" value = "1"> 57 </input> 58 <xsl:value-of select = 59 "choices/choice[2]"/> 60 </td> 61 </tr> 62 <tr> 63 <td> 64 <input type = "radio" 65 name = "userAnswer" value = "2"> 66 </input> 67 <xsl:value-of select = 68 "choices/choice[3]"/> £9 </td> 70 <td> 71 <input type = "radio" 72 name = "userAnswer" value = "3"> 73 </input> 74 <xsl:value-of select = 75 "choices/choice[4]"/> 76 </td> 77 </tr>
260 Глава 5 78 79 80 81 82 83 84 85 86 87 </ 88 89 </table> <input type = </form> </p> </body> </html> </xsl:templates ksI:atylesheet> = "submit" value = "Submit"/> Рис. 5.10. Таблица стилей XHTMLTipQuestion.xsl для трансформации XML-документа с вопросом теста г XHTML-документ What is the name of the icon shown? r CPE rPERF rGPP &20KT BSf* zl 1ДВД««£^^ ■.-HkjHa»i What is the name of the icon shown? r CPE rpERF r OPP ffPORT ) :.-■ ШЩи^т#1£^Ш: к-щ Рис. 5.11, Отображение окна с вопросом теста Tip-Test в Internet Explorer Каждый переключатель в XHTML-документе содержит уникальное значение, чтобы при нажатии пользователем кнопку Submit Internet Explorer отправлял выбранное значение еервлету TipTestServlet (с помощью метода post). Сервлет TipTestServlet (рис. 5.9) вызывает метод doPost (строки 106-173) при получении данных (отправленных методом poet). В строках 117-129 распознается, что запрос поступил от Internet Explorer, и вызывается метод createXMLTipTestAnswer (строки 505—589) для определения, является ли ответ пользователя правильным. В строке 509 извлекается объект HttpSession, а в строках 512-522 из объекта HttpSession извлекается правильный ответ, название рубрики советов и ее описание, В строках 525-526 из объекта HttpServletRequest извлекается сделанный пользователем выбор. В строках 531 -534 выбор пользователя сопоставляется с ответом, чтобы определить, правильный ли ответ дал пользователь. В строках 537-538 определяется URL сервлета TipTestServlet, чтобы клиенты могли повторно соединиться с сервлетом TipTestServlet для получения следующего вопроса теста Tip-Test. В строках 544-576 осуществляется разметка содержимого объекта сеанса HttpSession и URL сервлета TipTestServlet как XML-документа на основе шаблона Document Builder. В строке 544 создается объект DocumentBuilder, а в строке 545 — XML-документ (объект Document). В строках 548-549 создается элемент (объект Element) answer и назначается корневым элементом документа. В строках
Разработка приложений для беспроводной связи на базе Java и J2ME 261 552-554 создается элемент correct, который хранит информацию, является ли ответ пользователя правильным или неправильным. В строке 555 элемент correct назначается дочерним элементом элемента answer. В строках 558—561 и в строках 565-568 создаются элементы correctTipName и correctTipDescription, которые хранят, соответственно, название правильной рубрики советов и ее описание. В строках 562 и 569 эти элементы назначаются дочерними элементами элемента answer. В строках 572-575 создается элемент servletName, который хранит URL сервлета TipTestServlet. В строке 576 этот элемент назначается дочерним элементом элемента answer. После того как метод createXMLTipTestAnswer возвращает XML-документ (строка 578), сервлет TipTestServlet должен применить XSLT-трансформацию к этому документу и отправить результат клиенту. В строке 124 устанавливается МШЕ-тип text/html, а в строках 127-128 вызывается метод apply XSLT для применения к XML-документу таблицы стилей XHTMLTipAnswer.xsl (рис. 5.12). 1 <?xml versAon="I.0"?> 2 3 <•— XHTMLTipAnswer.xsl —> 4 <!— таблица стилей XHTML —> 5 6 <xsl:stylesheet version = "1.0" 7 xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"> 8 9 <xsl:output method = "xml" omit-xml-declaration = "no" 10 doctyp^-system = "DTD/xhtJBll-strict.dtd" 11 doctype-public = "-//W3C//DTO XHTML 1,0 Strict//EN"/> 12 13 <!— задание корня XML-документа, —> 14 <!-- который указывает на эту таблицу стилей —> 15 <xsl:template match = "answer"> 16 <html xmlfts="nttp://www.w3.org/1999/xntml"> 17 18 <bead> 19 <title>Tip Test Answer</title> 20 </head> 21 22 <body> 23 24 <p> 25 <hl> 26 <xsl:value-of select = "correct"/> 27 </hl> 28 </p> 29 30 <p> 31 <h2>Tip Name</h2> 32 </p> 33 34 <p> 35 <h3> 36 <xsl:value-of select = "correctTipName"/> 37 </h3> 38 </p> 39 40 <p> 41 <h2>Tip Description</h2>
262 Глава 5 42 </р> 43 44 <р> 45 <ЬЗ> 46 <xsl:value-of 47 select = "coerectTipDeacription"/> 48 </h3> 49 </p> 50 51 <p> 52 <h2> 53 <a href="{servletName)">Next Tip</a> 54 </h2> 55 </p> 56 57 </body> 58 </htai> 59 </xsl: t«nplate> 60 </xsl: stylesheet> ^^ Рис. 5.12. Таблица стилей XHTMLTipAnswecxsl трансформирует ответ на вопрос теста Tip-Test из XML s XHTML-документ В листинге на рис. 5.12 в строках 9-11 с помощью элемента хsi:output задается DTD. В строке 15 [задается, что элемент answer является корневым элементом XML-документа, который будет подвергнут трансформации с помощью таблицы стилей XHTMLTipAnswer.xsl. XML-доку мент начинается в строке 16. В строке 26 используется элемент correct XML-документа для отображения, является ли выбранный пользователем ответ правильным. В строках 36 и 47 используются элементы correctTipName и correctTipDescription для отображения, соответственно, названия рубрики и ее описания для правильного ответа. В строке 53 используется элемент servIetName для предоставления ссылки на сервлет TipTestServlet, чтобы пользователь мог принять следующий вопрос теста Tip-Test. На рис. 5.13 показаны результаты ответа на вопрос теста Tip-Test в Internet Explorer. Tip Description Organizations that develop software must often S produce versions customized to a variety of - computers and operating systems. These tips offer j, suggestions to make your applications more portable. |s Рис. 5.13. Окно с результатом ответа на вопрос теста Tip-Test в Internet Explorer
Разработка приложений для беспроводной связи на базе Java и J2ME 263 5.3.2. Запрос от браузера WAP Если изначальный get-запрос сервлетуТ1рТе818епг1ей (рис. 5.9) был сделан имитатором Open wave UP, в строках 202-214 определяется, что обратившийся с запросом клиент — это WAP-клиент. В строках 205-208 осуществляется вызов метода createXMLTipTestQuestion для создания XML-документа, который содержит вопрос теста Tip-Test. Используя параметры для этого метода, нгы задаем, что рисунки для рубрик советов имеют формат wbmp и расположены в каталоге WAP/unages контекста сервлета. В строке 211 задается МГМЕ-тип text/vnd.wap.wml для создания WML-содержимого. В строках 212-:213 вызывается метод applyXSLT для применения таблицы стилей WAPTipQuestion.xsl {рис. 5.14) к XML-документу. 1 <?xml version="i.0"?> 2 3 <!— WAPTipQuestion.xsl --> 4 <!— таблица стилей для клиента WAP —> 5 6 <xsl:stylesheet 7 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 8 version="1.0"> 9 10 <xsl:output method = "xml" omit-xral-declaration = "no" 11 doctype-system = "http://www.wapforum.org/DTD/wml_l.1. xml" 12 doctype-publie = "-//WAPFORUM//DTD WML l.l//EN"/> 13 14 <!— задание корня XML-докумема, --> 15 <!-- указывайте го на эху таблицу стилей —> 16 <xsl:template match = "guestion"> 17 18 <wml> 19 <card id = "card!" title = "Tip Test"> 20 21 <do type = "accept" label = "OK"> 22 <go href = "#card2"/> 23 </do> 24 25 <p> 26 <img sre = "(image}" height = "55" width = "55" 27 alt = "Tip Image"/> 28 </p> 29 30 </card> 31 32 <card id = "card2" title = "Tip Test"> 33 <do type = "accept" label = "Submit"> 34 <go method = "post" href = "/advjhtpl/tiptest"> 35 <postfield name = "userAnswer" 36 value = "$(question)"/> 37 </go> 38 </do> 39 40 <P> 41 The tip shown on the previous screen is called: 42 </p> 43 44 <p>
264 Глава 5 45 <select name = "question" 46 iname =• "iquestion" lvalue = "1"> 47 46 <Option. value = "0"Xxsl:value-of 49 select = "choices/choice[1] "/X/option> 50 51 <option value = "l"Xxsl:value-of 52 select = "choices/choice[2] "/x/option> 53 54 «jption value = "2 "Xxel: value-of 55 select = "choices/choice [3] "/X/option> 56 57 «Sption value = "3"Xxsl: value-of 58 select = "choices/choice[4] "/X/option> 59 </seleet> 60 </p> 61 </card> 62 </wal> 63 </xsl: tenrplate> 64 </xsl: stylesheet> Рис. 5.14. Таблица стилей WAPTipQuestion.xsl трансформирует вопрос теста Tip-Test в формате XML в WML-документ В листинге на рис. 5.14 в строках 10-12 с помощью элемента xshoutput задается DTD. В строке 16 указывается, что элемент question является корневым элементом XML-докумеета, который будет подвергнут трансформации с помощью таблицы стилей WAPTipQuestion.xsl. XML-документ начинается в строке 18 элементом wnxl. В строках 19-30 осуществляется объявление первой карты, или страницы, которая хранит WML-cодержимое, отображаемое браузером. В строках 26-27 отображается рисунок, ассоциированный с элементом image XML-документа, Элемент do (строки 21—23) информирует имитатор, что вторую карту следует показать, когда пользователь нажимает кнопку ОК. В строках 52-61 объявляется вторая карта, на которой содержатся четыре возможных ответа. При помощи элементов choice в элементе choices, в строках 45—59 создается список, который содержит варианты ответов. Элемент do (строки 33—38) указывает имитатору, что сделанный пользователем выбор следует отправить в сервлет TipTestServlet, когда пользователь нажимает кнопку Submit. На рис. 5.] 5 показало якран тее.тн Tip-Test после применения сервлетом TipTestServlet XSLT-трансформации к XML-документу. На рисунке слева показано условное изображение для рубрики советов, на рисунке справа показан выбор ответа пользователем. Каждый пункт в списке для выбора содержит уникальное значение. Когда пользователь нажимает кнопку Submit, имитатор UP отправляет (с помощью метода post) выбранное значение сервлету TipTestServlet. Сервлет TipTestServlet (рис, 5.9) вызывает метод doPost, когда получает отправленные методом post данные. В строках 132—144 определяется, что запрос поступил от имитатора Openwave UP. В строках 135—136 осуществляется вызов метода createXMLTipTestAnswer для создания XML-документа, который информирует, является ли ответ пользователя правильным, содержит название рубрики советов и ее описание для правильного ответа, а также URL сервлета TipTestServlet. В строке 139 устанавливается MIME-тип text/vnd.wap.wrnl для создания WML-содержимого, а в строках 157-158 вызывается метод applyXSLT для применения к XML-документу таблицы стилей WAPTipAnswer.xsl (рис. 5.16).
Разработка приложений для беспроводной связи на базе Java и J2ME 265 Рис. 5.15. Экран имитатора Openwave UP с вопросом теста Tip-Test (Изображение UP.SDK предоставлено компанией Openwave Systems Inc. Openwave, логотип Openwave и UP.SDK являются торговыми марками корпорации Openwave Systems Inc. Все права соблюдены.) В строках 10—12 листинга на рис. 5.16 с помощью элемента xsl:output задается DTD. В строке 16 задается, что элемент answer является корневым элементом в XML-документе, который будет подвергнут трансформации с помощью таблицы стилей WAPTipAnswer.xsl. WML-документ начинается в строке 18 элементом wml. В строках 20-48 осуществляется объявление карты (экрана с ответом) в составе этого WML-документа. В строке 29 используется элемент correct WML-доку- мента для отображения, является ли выбранный пользователем ответ правильным. В строках 37 и 45 элементы correctTipName и correctTipDescription используются, соответственно, для отображения названия рубрики советов и ее описания для правильного ответа. Используя элемент servletName в XML-документе, элемент do (строки 22-26) предоставляет ссылку на сервлет TipTestServlet, чтобы пользователь мог получить следующий вопрос теста Tip-Test. На рис. 5.17 показан экран с ответом на вопрос теста Tip-Test в имитаторе Openwave UP. 1 <?xml version="l.0"?> 2 3 <!— WAPTipAnswer.xsl --> 4 <!— таблица стилей для клиента WAP --> 5 6 <xsl:stylesheet 7 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="l.0"> 8 9 10 11 12 13 <xsl:output method = "xml" omit-xml-declaration = "no" doctype-system = "http://www.wapforum.org/DTD/wml_l.1.xml" doctype-public = "-//WApFORUMZ/dtd wml 1.1//en"/>
266 Глава 5 14 <<— задание корня XML-документа, —> 15 <!-- укавыааюцщ'о на эту таблицу стилей --> 16 <xsl:template natch = "answer"> 17 18 <wml> 19 20 <card id = "cardl" title = "Tip Test Answer"> 21 22 <do type = "accept" label = MOK"> 23 <go method = "get" 24 href = "/advjhtpl/tiptest"> 25 </go> 26 </do> 27 28 <p> 29 <xsl:value-of select = "correct"/> 30 </p> 31 32 <p> 33 Tip Name 34 </p> 35 36 <p> 37 <xsl:value-of select = "eorreetTipName"/> 38 </p> 39 40 <p> 41 Tip Description 42 </p> 43 44 <p> 45 <xel:value-of select ■ "correctTipOescription"/> 46 </p> 47 48 </card> 49 50 </wml> 51 </xsl:template> 52 </xsl:3tylesheet> Рис. 5.16. Таблица стилей WAPTtpAnswer.xsl трансформирует ответ в WML-документ 5.3.3. Запрос от браузера i-mode Pixo Если изначальный get-залрос поступил сервлету TipTestServlet (рис. 5.9) от браузера Pixo, в строках 217-229 определяется, что обратившийся с запросом клиент — это клиент i-mode. В строках 220-223 осуществляется вызов метода createXMLTipTestQuestion для создания XML-документа, который содержит вопрос теста Tip-Test. Используя параметры для этого метода, мы задаем, что рисунки для обозначения рубрик советов имеют формат gif и расположены в каталоге iMode/images контекста сервлета. В строке 226 устанавливается МШЕ-тип text/html для создания cHTML-содержимого. В строках 227-228 осуществляется вызов метода applyXSLT для применения к XML-документу таблицы стилей IMODETipQuestion.xsl (рис. 5.18).
Разработка приложений для беспроводной связи на базе Java и J2ME 267 Рис. S.17. Экран имитатора Openwave UP с ответом на вопрос теста Tip-Test. (Изображение UP.SDK предоставлено компанией Openwave Systems Inc. Openwave, логотипы Openwave и UP.SDK являются торговыми марками корпорации Openwave Systems Inc. Все права соблюдены.) 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0"?> <!— IMODETipQuestion.xsl —> <!-- таблица стилей для клиента i-mode --> <xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"> <xsl:output method = "html" omit-xml-declaration = "yes" doctype-public = "-//W3C//DTD Compact HTML 1.0 Draft//EN"/> <!— задание корня XML-документа, —> <!— указывающего на эту таблицу стилей --> <xsl:template match = "question"> <html> <head> <title>Tip Test</title> </tiead> <body> <P> <!— вывод изображения —> <img name = "image" height = "40" width = "40" alt - "Tip Image" arc = "{1даде}">
268 Глава 5 29 </img> 30 </р> 31 32 <р> 33 What is the name of the icon shoim? 34 </p> 35 36 <p> 37 38 <!— создание формы с четырьмя переключателями --> 39 <form method = "post" 40 action = "/advjhtpl/tiptest"> 41 42 <!— построение таблицы с вариантами выбора --> 43 <table> 44 <tr> 45 <td> 46 <input type = "radio" 47 name = "uaerAnswer" value = "0"> 48 </input> 49 <xsl:value-of select = 50 "choices/choice[1]"/> 51 </td> 52 53 <td> 54 <input type = "radio" 55 name = "userAnswer" value = "1"> 56 </input> 57 <xsl:value-of select = 58 "choioea/ehoic*[2]"/> 59 </td> 60 </tr> 61 62 <tr> 63 <td> 64 <input type = "radio" 65 name = "userAnswer" value = "2"> 66 </input> 67 <xsl:value-of select = 68 -choices/choice[3]"/> 69 </td> 70 71 <td> 72 <input type = "radio" 73 name = "userAnswer" value = "3"> 74 </input> 75 <xsl:value-of select = 76 "choices/choice[4]"/> 77 </td> 78 </tr> 79 </table> 80 81 <input type = "submit" value = "Submit"/> 82 </form> 83 </p> 84
Разработка приложений для беспроводной связи на базе Java и J2ME 269 85 </body> 86 </html> 87 </xs1:template> 88 </xsl.-gtyleshaat> Рис. 5. в формат .18. Таблица стилей IMODETipQuestion.Xsl трансформирует вопрос теста Tip-Test иате XML в с HTML-доку мент В строках 9-10 листинга на рис. 5.18 с помощью элемента xshoutput задается DTD. В строке 14 задается, что элемент question является корневым элементом XML-доку мента, который будет подвергнут трансформации с помощью таблицы стилей IMODETipQiiestion.xsl. с HTML-документ начинается в строке 15 элементом html. Элемент body, описываемый в строках 21-85, содержит графическое изображение для рубрики советов и четыре возможных варианта ответа. В строках 26-29 осуществляется вывод изображения, ассоциированного с элементом image XML-документа. В строках 39-82 создается форма с четырьмя вариантами ответа. В строках 46-76 создаются четыре переключателя, ассоциированные с этими ответами. В строке 81 создается кнопка Snbmit, чтобы пользователь мог отправить сделанный им выбор. На рис. 5.19 показан тест Tip-Test в действии после применения сервлетом TipTestServlet XSLT-трансформации к XML-документу. На рисунке слева показан экран при выборе ответа пользователем, а на рисунке справа показан экран перед отправкой ответа пользователем. и>1.>1*теш№г«1Ж»аВ1Щ? Рис. 5.19. Экран браузера i-rnode Pixo с вопросом теста Tip-Test. (Публикуется с разрешения компании Pixo, Inc.) Каждый переключатель в cHTML-документе содержит уникальное значение, чтобы при нажатии пользователем кнопки Submit браузер Pixo отправлял (с помощью метода post) выбранное значение сервлету TipTestServlet. Сервлет TipTestServlet вызывает метод doPost, когда получает отправленные методом post данные. В строках 147-159 определяется, что запрос был послан браузером Pixo. В строках 150-151 осуществляется вызов метода createXMLTipTestAnswer для создания XML-документа, хранящего информацию, является ли ответ пользовате-
270 Глава 5 ля правильным, название рубрики советов и ее описание, а также URL сервлета TipTestServlet. В строке 154 задается МГМЕ-тип text/html для создания cHTML содержимого, а в строках 157-158 осуществляется вызов метода appIyXSLT дяя применения к XML-документу таблицы стилей IMODETipAnswer.xsl {рис. 5.20). В строках 9-10 листинга на рис. 5.20 с помощью элемента xsl:output создается DTD. В строке 14 задается, что элемент answer является корневым элементом XML-документа, который будет подвергнут трансформации с использованием таблицы стилей IMODETipAnswer.xsl. cHTML-документ начинается в строке 15 элементом html. В строке 25 используется элемент correct XML-документа для отображения, является ли сделанный пользователем выбор правильным. В строках 35 и 45-46 элементы correctTipName и correctTipDescription используются, соответственно, для отображения названия рубрики советов и ее описания для правильного ответа. В строке 52 с помощью элемента servletName предоставляется ссылка на еервлет TipTestServlet, чтобы пользователь мог принять следующий вопрос теста Tip-Test. На рис. 5.21 показан экран с ответом на вопрос теста Tip-Test в браузере i-mode Pixo. 1 <?хм1 version="1.0"?> 2 3<!-~ IMODETipAnswer.xsl —> 4 <!— таблица стилей для клиента i-mode —> 5 6 <xsl:stylesheet version = "1.0" 7 xntlns:xsl = "http://www.w3.org/1999/XSL/Transform"> 8 9 <xsl:output method =* "html" omit-xml-declaration = "yes" 10 doetype-public = "-//W3C//DTD Compact HTML 1.0 Draft//EN"/> 11 12 <!— задание корня XML-документа, —> 13 <!— который указывает на эту таблицу стилей —> 14 <xsl:template match = "answer"> 15 <html> 16 17 <head> 18 <title>Tip Test Answer</title> 19 </head> 20 21 <body> 22 23 <p> 24 <hl> 25 <xsl;value-of select = "correct"/> 26 </hl> 27 </p> 28 29 <p> 30 <h2>Tip Name</b2> 31 </p> 32 33 <p> 34 <h3> 35 <xsl:value-of select = "correctTipName"/> 36 </h3> 37 </p> 38 39 <p>
Разработка приложений для беспроводной связи на базе Java и J2ME 271 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 </body> 57 </html> 58 </xsl:template> 59 </xsl:stylesheet> <h2>Tip Description</h2> </p> <P> <h3> <xsl:value-of select = "correetTipDescciption"/> </h3> </p> <P> <h2> <a href="{servletName}">Next Tip</a> </h2> </p> Рис. 5.20. Таблица стилей IMODETipAnswer.xsl трансформирует ответ на вопрос теста Tip-Test в формате XML г cHTML-документ "pixo AltMMtMfcmtmmM-2.1 Рис. 5.21. Экран браузера i-mode Pixo с ответом на вопрос теста Tip-Test. {Публикуется с разрешения компании Pixo, Inc.) 5.3.4. Запрос от клиента J2ME Сервлет TipTestServlet ведет себя по-другому, если get-запрос был отправлен клиентом J2ME. Сервлет TipTestServlet не использует XML для клиента J2ME, поскольку на сегодняшний день клиент J2ME не способен интерпретировать данные XML без использования специализированного программного обеспечения. Как заявлено корпорацией Sun Microsystems, «пока еще не ясно, как в будущем будут развиваться взаимоотношения Java — XML, но уже можно сказать, что в WAP 2.0 в качестве языка разметки будет использоваться XHTML... В не столь
272 Глава 5 далеком будущем появятся тысячи, а затем и миллионы бытовых устройств, использующих XHTML.»1 Поскольку для клиентов J2ME XML не используется, сервлет TipTestServlet не отправляет размеченные данные нашему клиенту J2ME. Можно было бы использовать XSLT для генерирования обычного текста, но мы не стали этого делать по двум причинам. Во-первых, обычный текст не является правильно построенным (well-formed) и ее отвечает основному предназначению XSLT-трансформации: создавать правильно построенные документы. Во-вторых, результирующее множество ResultSet в сервлете TipTestServlet уже содержит обычный текст, поэтому преобразование текста в XML-документ с последующим обратным его преобразованием представляется излишним. В строках 232-235 сервлета TipTestServlet (рис. 5.9) определяется, что запрос был отправлен клиентом J2ME, и вызывается метод seadJ2MEClientResponse (строки 344.-390). В строке 349 осуществляется вызов метода get ResultTable для преобразования результирующего множества ResultSet в двухмерный массив строк resultTable, чтобы иметь быстрый доступ к содержимому базы данных. В строке 352 создается объект Random, который дает возможность сервлету TipTestServlet случайным образом выбирать рисунок для рубрики советов и вопросы теста для отображения. В строке 355 объект Random передается методу getRandomlndices, который возвращает массив из четырех целых чисел. Каждое из этих чисел представляет собой уникальный, случайным образом выбранный индекс, который соответствует рубрике советов в массиве resultTable. В строках 358-359 иа массива randomRow произвольным образом выбирается целое число. Это целое число представляет собой «правильный» ответ на вопрос теста. Фактически, это число соответствует строке в таблице resultTable) хранящей информацию о правильном ответе. С учетом правильного ответа в строке 361 определяется строка в таблице resultTable, которая содержит название рубрики советов для правильного ответа и ее описание. Сервлет TipTestServlet хранит эту информацию б объекте HttpSession (строка 364), чтобы в случае, если клиент J2ME отправит сделанный пользователем выбор обратно на сервер, сервлет TipTestServlet мог проверить ответ пользователя, сравнивая его с правильным ответом, хранящемся в объекте HttpSession. В строках 367-376 правильный ответ, название рубрики советов и ее описание сохраняются в объекте HttpSession. С учетом правильного ответа в строках 379-380 определяется изображение, которое следует отправить клиенту J2ME. В строке 382 задается MIME-тип text/plain, поскольку клиенты J2ME интерпретируют обычный текст. В строке 383 создается объект Print Writer, с помощью которого сервлет TipTestServlet отправляет данные клиенту J2ME. В строке 384 отправляется имя рисунка для рубрики советов, а в строках 387-388 клиенту отправляется четыре сокращенных названия для вариантов ответов. В разделе 5.4.3 подробно разъясняется, как клиент J2ME отображает информацию теста Tip-Test и обменивается данными с сервлетом TipTestServlet и со своими собственными модулями. На рис. 5.22 показан образец выходных данных после того, как сервлет TipTestServlet отправил вопрос теста Tip-Test клиенту J2ME. На рисунке слева показано изображение для рубрики советов, а на рисунке справа — выбор огвега пользователем. Когда клиент отправляет сделанный пользователем выбор, сервлет TipTestServlet вызывает метод doPost. В строках 162-164 определяется, что запрос поступил от клиента J2ME, и осуществляется вызов метода sendJ2MEAnswer (строки 592-629). В строках 596-597 используется объект BufferedReader для чтения сделанного пользователем выбора. В строке 600 задается MIME-тип text/plain, a Day, В., «Developing Wireless Applications using Java™ 2 Platform, Micro Edition (J2 ME™) *, developer. Java. sun. со m/de ve 1 ope r/p rod uc ts / wi rel es s / gets tart /artic les/wireles- sdev/wirelessdev.pdf, June 2001.
Разработка приложений для беспроводной связи на базе Java и J2ME 273 Рис. 5.22. Экран клиента J2ME с вопросом теста Tip-Test (Публикуется с разрешения корпорации Sun Microsystems, Inc.) в строке 601 получается объект Print Writer, с помощью которого сервлет TipTest- Servlet осуществляет отправку содержимого. В строке 604 извлекается объект HttpSession, который хранит правильный ответ, название рубрики советов к описание. В строках 607-617 эта информация извлекается из объекта HttpSession. В строках 620-623 выбор пользователя соиоставляется с правильным ответом и определяется, является ли предоставленный пользователем ответ верным. В строках 626-627 посредством объекта PrintWriter клиенту отправляется правильное название рубрики советов и ее описание. На рис. 5.23 показан экран с ответом на вопрос теста в эмуляторе MIDP-устройства Sun. На рисунке слева показан экран, который содержит правильное название рубрики советов и ее описание. Рис. 5.23. Экран клиента J2ME с ответом на вопрос теста Tip-Test. (Публикуется с разрешения корпорации Sun Microsystems, Inc.)
274 Глава 5 Если пользователь осуществит прокрутку содержимого этого экрана вниз, он увидит картинку, показанную справа, на которой представлена оставшаяся часть описания рубрики советов. 5.4. Java 2 Micro Edition Этот раздел содержит ознакомительную информацию по Java 2 Micro Edition (J2ME™): платформе, которая дает возможность разработчикам создавать приложения для различных бытовых устройств, таких как телевизионные игровые приставки, Web-терминалы, встроенные системы, мобильные телефоны и пейджеры. Мы рассмотрим технологии Connected Limited Device Configuration (CLDC) и Mobile Information Device Profile (MIDP), которые предоставляют разработчикам интерфейсы прикладного программирования для написания ^МЕ-приложений — мидлетпов (MIDlet) — и развертывания их на мобильных устройствах различных типов. Затем мы поговорим о жизненном цикле мидлета и поясним, каким образом J2ME используется в нашем практическом примере. 5.4.1. Connected Limited Device Configuration (CLDC) Connected Limited Device Configuration (CLDC) представляет собой набор интерфейсов прикладного программирования, который дает возможность разработчикам создавать приложения для устройств, имеющих ограниченные ресурсы (т.е. ограниченный размер экрана, памяти, мощность и скорости передачи). В состав CLDC J2ME входит виртуальная машина — интерпретатор, который выполняет приложения — и набор классов, которые разработчики могут использовать для создания и выполнения программ на устройствах с ограниченными ресурсами. Виртуальная машина KVM, используемая в CLDC, выполняет приложения J2ME (подобно тому, как виртуальная машина JVM выполняет приложения J2SE). Буква "К" в аббревиатуре KVM обозначает "kilo", поскольку приложения J2ME занимают достаточно небольшой объем памяти, измеряемый в килобайтах. CLDC J2ME содержит пакеты java.io, java.lang, java.utii, которые разработчики используют для выполнения таких стандартных операций, как создание примитивных типов данных, использование простых структур данных, а также отправка и получение данных из сети. Эти пакеты являются подмножеством пакетов J2SE java.io, java.Ung и java.utii, поэтому пакеты CLDC J2ME не содержат всех классов, имеющихся в пакетах J2SE. В таблице на рис. 5.24 описаны пакеты java.io, javaJang и java.utii J2ME. Для экономии места в эту таблицу не были включены исключения и ошибки, присущие каждому из классов. Полный список классов CLDC J2ME можно найти по адресу java.sun.com/j2me/docs/pdf/cJdcapi.pdf. Типичная ошибка программирования 5.1 Попытка использовать пакеты J2SE в KVM приведет к ошибке компиляции, поскольку виртуальная машина KVM не может обслуживать большое количество классов по причине ограниченности ресурсов KVM. Одна из проблем программирования в J2ME состоит в том, что интерфейс прикладного программирования не содержит ряда типов данных и классов, которые разработчики, как правило, «имеют под рукой» в других платформах Java. Например, в J2ME нет операций с плавающей запятой, операций с сериализуемыми объектами, с группами программных потоков, а также отсутствует интерфейс JNI (Java Native Interface). По мере развития технологий для беспроводных устройств, вполне вероятно, что в последующих версиях J2ME эти возможности будут поддерживаться.
Разработка приложений для беспроводной связи на базе Java и J2ME 275 Классы Интерфейсы Классы java.io Datalnput DataOutput ByteArrayInputS tre am ByteArrayOutputStream DatalnputStream DataOutputStream InputStream InputstreamReader Outputs tream OutputStreamReader PrintStream Reader Writer java.lang Runnable Boolean Byte Character Class Integer Long Math Object Runtime Shott String String Buffer System Thread Throwable java.util 1 Enumeration Calendar Data Hashtable Random Stack Timer Timer Task Timer Zone Vector Рис. 5.24. Пакеты java.io, java.lang и java.util J2ME 5.4.2. Mobile Information Device Profile (MIDP) Mobile Information Device Profile (MIDP) — это набор интерфейсов прикладного программирования, который дает возможность разработчикам реализовывать специфичные для мобильных устройств действия, такие как создание пользовательских интерфейсов, локальное хранение информации и определение жизненных циклов клиентских МГОР-приложений (мидлетов). Устройства, которые выполняют приложения с помощью MIDP, называются MI DP-устройствами. К таким устройствам откосятся сотовые телефоны и пейджеры. В состав MIDP входят пакеты javax.microedition.lcdui, javax.microedition.io, javax.microedition.rms и javax.microedition.midlet. Пакет javax.microedition.lcdui содержит классы, которые дают возможность разработчикам создавать пользовательские интерфейсы для мидлетов, пакет javax.microedition.io дает возможность осуществлять сетевые взаимодействия между мидлетами и другими системами, пакет javax.microedition.rms содержит классы, которые дает возможность локально хранить информацию, а пакет javax.microedition.midlet определяет жизненный цикл мидлетов. В таблице на рис. 5.25 представлены пакеты MIDP javax.microedition.lcdui и javax.microeditioii.io. В таблице на рис. 5.26 представлены пакеты MIDP javax.microedition.rms и javax.microedition.midlet. Для экономии места в эти таблицы не были включены исключения и ошибки, присущие этим классам. Полный список классов MIDP J2ME можно найти по адресу Java.eun.oom/prodijcts/midp/midp-virelessapps-wp.pdf Чтобы выполнить приложение MIDP, MIDP-устройство должно иметь монохромный дисплей с разрешением не менее 96x54 пикселов, иметь двунаправленное подключение к беспроводной сети, некое устройство ввода (например, ми- нй-клавиатура или чувствительный экран), память объемом не менее 128 килобайт для классов CLDC/MIDP и не менее 32 килобайт для KVM. Мидлет будет работать на любом устройстве, которое отвечает этим требованиям.
276 Глава 5 Классы Интерфейсы Классы javax.microedition.kdui Choice CommandLis tener ItemLi stener Alert MettType Canvas ChoiceGroup Command DataField Display Diaplayable Font Form Gauge Graphics Image Imageltem Item List Screen Screen Item TextBox TextField Ticker javax.microedition.io j Connection ContentConnection | Datagram j DatagramConnection HttpConnection InputConnection OutputConnecfcion StreamConnection S treSmConneсtionNoti£ier Connector Рис. 5.25. Пакеты MID3 Javax.mfcroedition.fcdui и javax.microedition.io Классы Интерфейсы Классы javax.microedrtion.rms RecordComparator RecordEnumeration RecordFilter RecordListener Recordstore javax.rnicroedition.midlet | MIDlet | Рис. 5.26. Пакеты MIDP javax.microedition.rms и javax.microedition.midlet 5.4.3. Обзор мидлета TipTestMIDIet В разделе 5.3.4 обсуждалось, как сервлет отправляет данные клиенту J2ME, который представлен мидлетом. Как мы уже говорили, мидлеты не могут интерпретировать XML-документы без использования специализированных программных продуктов и должны получать всю информацию через потоки или объекты PrintReader. E этом разделе мы более подробно остановимся на мидлетах, рассмотрим жизненный цикл мидлета и познакомимся с мидлетом TipTestMIDlet, который используется в нашем практическом примере.
Разработка приложений для беспроводкой связи на базе Java и J2ME 277 Мидлет — это приложение для мобильного информационного устройства Mobile Information Device, которое выполняется на МГОР-устройстве. Название «мидлет» было принято по аналогии с названиями ♦апплет» и «сервлет», поскольку эти приложения обладают схожими характеристиками: например, всем им присущ определенный жизненный цикл, и они занимают различные состояния в процессе выполнения программы. Кроме того, разработчик не вызывает явным образом конструктор для объектов этих классов (Applet, HttpServlet и MIDlet), чтобы реализовать экземпляры этих объектов. В разделе 2.2.1 говорилось, что сервлет загружает в память контейнер сервлетов (обычно в ответ на первый запрос, принимаемый сервлетом). Мидлеты загружаются схожим образом. Разработчики MIDP хранят группу мидлетов в jar-файле, который называется комплектом мидлетов, на сервере. МШР-устройство содержит программу управления приложением (application management software — AMS), которая загружает комплект мидлетов с сервера, открывает его, а затем запускает указываемый пользователем мидлет на MIDP- устройстве. AMS использует файл дескриптора приложения для загрузки приложения с мидлетами. Этот файл имеет расширение .jad и содержит такую информацию, как список мидлетов в комплекте мидлетов, размер комплекта мидлетов и его URL, имя каждого из мидлетов, имя поставщика и номер версии, а также профиль и конфигурацию МГОР-устройства. AMS использует эту информацию, чтобы обеспечить выполнение приложения на данном MIDP-устройстве. Как интегрированная среда разработки Wireless Toolkit J2ME, так и Forte, создают этот файл при создании нового комплекта мидлетов (см. раздел 5.5). Приведенный ниже код демонстрирует структуру файла дескриптора приложения для мидлета TipTestMIDlet. MIDlet-1: TipTestMIDlet, TipTestMIDlet.png, com,deltel.advjhtpl.wireless.TipTestMIDlet MIDlet-Jar-Size: 9577 MIDlet-Jar-URL: TipTestMIDlet.jar MIDlet-Nama: TipTestMIDlet MIDlet-Vendor: Sun Microsystems MIDlet-Version: 1.0 MicroEdition-Configuration: CLDC-1.0 MieroEditiou-Profile: MIDP-1.0 Мидлет TipTestMIDlet представлен на рис. 5.27. Прежде чем начать рассматривать, как мидлет TipTestMIDlet извлекает информацию с сервера, нужно уяснить жизненный цикл мидлета. Каждый мидлет должен расширять класс MIDlet (строка 13) из пакета javax.microedition.midJet (строка 9). Жизненный цикл начинается, когда AMS вызывает конструктор мидлета (строки 43-75) для запуска мидлета. После этого мидлет переходит в состояние паузы и не может принимать ввод пользователя или отображать содержимое экрана, созданное разработчиком. По завершении работы конструктора AMS вызывает метод startApp (строки 78-82), который переводит мидлет в активное состояние, разрешая ему отображать содержимое и принимать ввод пользователя. После этого мидлет ожидает ввода от пользователя или иного уведомления от AMS. Если AMS вызывает метод pauseApp (строка 85), мидлет возвращается в состояние паузы. Если мидлет находится в состоянии паузы, AMS должен вызвать метод startApp, чтобы дать возможность мидлету вновь перейти в активное состояние. Если AMS вызывает метод destroy- Арр (строка 88) для очистки памяти устройства перед загрузкой другого приложения, выполнение мидлета заканчивается. Методы startApp, pauseApp и dest- royApp являются абстрактными методами класса MIDlet, поэтому каждый подкласс класса MIDlet должен переопределять эти методы. В строке 6 импортируется пакет ввода/вывода CLDC J2ME, который дает возможность мидлету TipTestMIDlet отправлять и принимать данные от сервлетов.
278 Глава 5 В строках 9-11 импортируются пакеты MIDP, которые определяют жизненный цикл мидлета, создают пользовательские интерфейсы и осуществляют сетевые взаимодействия. В J2ME пользовательские интерфейсы делятся на API нижнего уровня и API верхнего уровня. Низкоуровневые API дают разработчикам возможность встраивать в программы графические рисунке и фигуры, точно задавая ах координаты в пикселах, а также предоставляют средства анимации для приложений, таких как игры. Высокоуровневые API пользовательского интерфейса позволяют разработчикам встраивать в программы (например, в приложения для электронной коммерции) и базовые пользовательские интерфейсы текстовые поля, списки, формы и изображения. 1 // TipTestMlDlet.java 2 // Этот нидлет получает вопросы теста Tip-Test от сервлета 3 package com,deitel.advjhtpl.wireless,■ 4 5 // Подмножество J2ME пакетов Java 6 import java.io.*; 7 8 // Пакеты J2ME 9 import javax. microedition.midlet.*; 10 import javax.microedition.ledui.*; 11 import javax.microedition,io.*; 12 13 public class TipTestMlDlet extends MIDlet { 14 15 private Display display; // менеджер дисплея 16 17 // экраны, отображаемые пользователям IB private List mainScreen; 19 private List welcomeScreen; 20 private Form infoScreen; 21 private Form tipScreen; 22 private Form answerScreen; 23 24 // действия для программных кнопок 25 private Command selectCommand; 2 б private Command nextCommand; 27 private Command backCommand; 2B 29 private static final String servletBaseURL = 30 "http://localhost:80fi0/advjbtpl/"; 31 32 private static final String welcomeServletName = "welcome"; 33 34 // определение сервлета приветствия и сервлета, выполнякжцего тест 35 private String tipTestServletName; Зв 37 private static final String velcomeServletURL = 38 servletBaseURL + welcomeServletName; 39 40 private String sessionID; 41 42 // инициализация дисплея и основного экрана 43 public TipTestMlDlet() 44 { 45 // создание команд для программных кнопок
Разработка приложений для беспроводной связи на базе Java и J2ME 279 Л6 eelectCommand = new Command( "Select", Command.OK, D ); 47 nextCommand = new Command( "Next Tip", Command.OK, 0 ); 48 backCommand = new Command( "Back", Command.BACK, 1 ); 49 50 // создание основного экрана, дающего возможность соединиться с сервлетом приветствия 51 mainScreen = new List( "TipTestHIDlet", List.IMPLICIT }; 52 mainScreen.addCommand( seleetCoramand ); 53 54 // разрешение доступа к экрану mainScreen через программную кнопку 55 mainScreen.setCoantandListener( 56 new CommandListener() { 57 58 // вызывается, когда пользователь нажимает программную кнопку 59 public void commandftetion( 60 Command command, Displayable displayable ) 61 { 62 // получение данных от сервлета приветствия 63 String data - getServerData{ welcomeServlatURL ); 64 65 // создание экрана приветствия на основе данных иэ сервлета 66 display.satCurrent( createWelcomeScreen( data ) ); & ) 68 69 } // конец анонимного внутреннего класса 70 ); 71 12 // получение соответствующего объекта Display для устройства 73 display = Display.getDisplay( this ); 74 75 } // конец конструктора TipTeatMIDlet 76 77 // начало нидлета MIDlet 78 public void startAppO 79 { 80 // настройка изображения основного экрана 81 display.setCurrent( mainScreen ) ; 82 } 83 84 // пауза в выполнении кидлета MIDlet 85 public void pauseApp() {} 86 87 // закрытие мидлета MIDlet 88 public void destroyApp( boolean unconditional ) {} 89 90 // создание экрана приветствия для теста 91 private Screen createWelcomeScreen{ String data ) 92 { 93 String listf] = pareeData( data, ';' >; 94 95 // создание экрана, приветствующего пользователя 96 walcomeScreen = new List( list[ 0 }, List.IMPLICIT }; 97
280 Глава 98 welcomeScreen.append( "Take TipTest", null ); 99 welcomeScreen.addCommand ( selectConnnand ); 100 welcomeScreen.addCommand{ backCommand ); 101 102 // получение URL информационной страницы 103 final String url = new String< list[ 1 ].toCharArray{) ); 104 105 // разрешение доступа к экрану welcomeScreen черев программную кнопку 106 welcomeScreen.setCommandListener{ 107 new ComraandListenerO { 108 109 // вызывается, когда пользователь нажимает программную кнопку 110 public void commandAction( 111 Command command, Displayable displayable ) 112 t 113 // если нажатой является программная кнопка SELECT 114 if ( command.getCommandType() == Command.OK ) { 115 116 // получение дата» со статической страницы 117 String data = 118 getServerData ( servletBaseURL + url ) ; 119 120 // отображение этих данных 121 display.setCurrent( 122 createInformationscreen( data ) ); 123 } 124 1Z5 // если нажатой является программная кнопка BACK 126 else if ( command.getCommandType() — 127 Command.BACK ) { 128 display.setCurrent( mainScreen ); 129 } 130 131 ) // конец метода commandAction 132 133 } // конец анонимного внутреннего класса 134 ) ; 135 136 return welcomeScreen; 137 138 } // конец метода createWelcomeScreen 139 140 // создание экрана, показывающего, с какими сервлетани можно установить соединение 141 private Screen createInformationScreen( String data ) 142 { 143 String list[] =parseData{ data, ';' ); 144 145 // создание формы, отображающей доступные сервлеты 146 infoScreen = new Form( "Information" ); 147 148 // создание элемента Stringltem, предоставляющего указания 149 Stringltem infoTitle = new Stringltem( list[ 0 ], null ); 150 infoScreen.append( infoTitle );
Разработка приложений для беспроводной связи на базе Java и J2ME 281 151 152 iI создание группы ChoiceGloup, позволяющей пользователе выбрать сервлет 153 final ChoiceGroup choices = new ChoiceGroup( "", 154 ChoiceGroup.EXCLUSIVE ); 155 choices.append( list[ 1 ], null ); 156 157 // добавление группы ChoiceGroup в форку Form 158 infoScreen.append( choices ); 159 160 infoScreen.addCommand{ selectCommand ); 161 infoScreen. addCommand ( bacJcCofflmand ) ; 162 163 // предоставление возможности доступа к атоку экрану через программную кнопку 164 infoScreen.setCommandListener( 165 new CommatidListener () { 166 167 // впаивается, хорда пользователь нажимает программную кнопку 168 public void commandAction( 169 Command command, Displayable displayable ) 170 ( 171 // если нажатой является программная кнопка SELECT 172 if ( command.getCommandType() == Command.OK ) { 173 174 // выбор пользователя, с каким сервлетом установить соединение 175 int selectedlndex = choices .getselectedlndex () ; 176 177 tipTestServletName = 178 choices.getString( selectedlndex ); 179 180 // соединение с сервлетом и получение данных 181 String data = getServerData( servletBaaetTRL + 182 tipTestServletName ); 183 184 // отображение следующего экрана в соответствии с данными 185 display.setCurrent( createTipTestScreen( 156 servletBaseURL + data } ); 187 } 188 189 // если нажатой является программная кнопка BACK 190 ■ else if ( command.getCommandType() == Command.BACK ) 191 display.setCurrent{ welcomeSereen }; 192 193 } // конец метода commandAction 194 195 } // конец, анонимного внутреннего класса 196 ) ; 197 198 return infoScreen; 199 200 } // конец метода createlnformationScreen 201
282 Глава 5 202 // создание экрана для отображения вопроса теста 203 private Screen createTipTestScreen{ String data ) 204 { 205 // синтаксический анализ данных, полученных с сервера 206 String list[) = parseData{ data, '\n' )■ 207 208 // создание новой формы для отображения вопроса теста 209 tipScreen = new Form( "Tip Test" ); 210 211 // создание изображения на основе данных, полученных с сервера 212 Image serverlmage = getServerImage( list[ 0 ] ); 213 214 // добавление изображения в форму 215 if ( serverlmage != null ) 216 tipScreen.append( serverlmage ); 217 218 String choiceList[] = new String[ 4 J; 219 220 // формирование из полученных данных списка для выбора ChoiceGroup 221 for ( int i = 0; i < choicelist.length; i++ ) 222 choiceListf i } = listf 1 + 1 ); 223 224 // создание группы ChoiceGroup, позволяжщей пользователю сообщить о своем выборе 225 final ChoiceGroup choices ■= new ChoiceGroup( "Tip Test", 226 ChoiceGroup.EXCLUSIVE, choicebist, null ); 227 228 // добавление группы ChoiceGroup в форму 22 9 tipScreen.append( choices ); 230 231 tipScreen.addCommand( selectCommand ); 232 233 // разрешение доступа к этому экрану через программную кнопку 234 tipScreen.setCommandXistener( 235 new CoramandListener() { 236 237 // вызывается, когда пользователь нажимает программную кнопку 238 public void coromandAction( 239 Command command, Displayable displayable ) 240 { 241 // отправка выбора пользователя сервлету 242 int selection = choices.getSelectedlndex(); 243 244 String result = postDataf selection ); 245 246 // отображение результатов 247 display.setCurrent( 248 createAnswerScreen( result ) ); 249 } 250 251 } // конец анонимного внутреннего класса 252 ) ; 253 254 return tipScreen;
Разработка приложений для беспроводной связи на базе Java и J2ME 283 255 256 } // конец метода createTipTestScreen 257 25S // создание экрана для отображения ответа на вопрос теста и результатов теста 259 private Screen createAnswerScreen{ String data ) 260 { 261 // синтаксический анализ данных, полученных с сервера 262 String list[] = parseData( data, '\n' )■ 263 264 // создание новой формы для отображения ответов 265 answerScreen = new Form( list[ 0 ] ); 266 267 // создание элемента Stringltem, отображажщего название рубрики 268 Stringltem tipNameltem = 269 new Stringltem( "Tip Name:\n", list[ 1 J ); 270 271 // создание элемента Stringltem, отображающего описание рубрики 272 Stringltem tipDescriptionltem = 273 new Stringltem( "\nTip Description:\n", list[ 2 ] ); 274 275 // добавление элементов Stringltem в форму 276 answerScreen.append( tipNameltem ); 277 answerScreen.append( tipDescriptionltem ); 278 279 answerScreen.addCommand( nextCpmmand ); 280 281 // разрешение доступа к этому экрану через программную кнопку 282 answerScreen.setCommandListener( 283 new CommandListener(> { 284 285 // впаивается, когда пользователь нажимает программную кнопку 286 public void commanditetion ( 287 Command command, Displayable displayable ) 288 { 289 // получение следующего вопроса теста 290 String data = getServerData( servletBaseURL + 291 tipTestServletKame ); 292 293 // отображение следующего вопроса теста 294 display.setCurrentt createTipTestScreenI 295 servletBaseURL + data ) ); 296 \ 297 298 } // конец анонимного внутреннего класса 299 ) ; 300 301 return answerScreen; 302 303 } // конец метода createAnswerScreen 304 305 // отправка сервлету сделанного пользователем выбора 306 private String postData( int selection ) 307 ( 308 // соединение с сервлетон и отправка данных
284 Глава 5 309 try { 310 311 // соединение с сервером, пославшим заголовок User-Agent 312 HttpConnection httpConnection = 313 ( HttpConnection ) Connector.open( 314 servletBaseORL + tipTestServletName, 315 Connector.READ_HRITE ); 316 31 "J setUserAgentHeader( httpConnection ) ; 316 319 // отправка идентификатора сеанса seesionID, если таковой имеется 320 if ( seesionID != null ) 321 httpConnection.setRequestProperty( 322 "cookie", sessionID ); 323 324 // информирование сервлета о тиле запроса (post) 325 httpConnection.setRequestMethod( HttpConnection.POST ); 326 327 // открытие потока вывода для сервлета 32S DataOutputStream out = 329 httpConnection.openDataOutputStream(); 330 331 // отправка сделанного пользователем выбора 332 out.writeUTP( Integer.toString( selection ) ); 333 out. flush () ,- 334 335 // получение результатов от сервлета 336 String data = getOata( httpConnection ); 337 338 httpConnection.close<); // закрытие ооединеиия 339 340 return data; 341 342 j // конец блока try 343 344 // выполняется, если мидлет MIDlet не может открыть HTTP-соединение 345 catch ( IOException ioException ) { 346 ioException,printStackTraoe(> ; 347 J 348 349 return null; 350 351 } // кохец метода postData 352 353 // разбор строки на составлякюрте подстроки и помещение их в массив 354 private String[] parseData( String data, char delimiter ) 355 i 356 int newLines =0; 357 358 // определение количества разделителей s строке 359 for ( int i = 0; i < data.length 0 ; i++ ) 360 361 // увеличение количества разделителей на 1 362 if ( data.charAt< i ) = delimiter )
Разработка приложений для беспроводной связи на базе Java и J2ME 285 363 newLines++; 364 365 // создание нового строкового массива 366 String listj] = new String[ newLines ]; 367 368 int oldHewLinelndex = 0; 369 int currentNewLinelndex,- 370 371 // сохранение составляющих строк в массиве с использованием разделителей 372 for ( int i = 0; i < newLines; i++ > { 373 374 // определение индекса местоположения разделителя 375 currentMewLineindex = 376 data.indexOf( delimiter, oldNewLinelndex ); 377 378 // извлечение строки, ограниченной разделителями 379 list[ i ] = data.substring( oldNewLinelndex, 380 currentNewLinelndex - 1 ); 381 382 oldNewLinelndex = currentNewLinelndex + 1; 383 ) 384 385 return list; 386 387 } // конец метода parseData 388 389 // соединение с сервером и получение данных 390 private String getServerData( String aervertJrl ) 391 { 392 // соединение с сервером, получение данных 393 try К 394 395 // соединение с сервером, отправившим Заголовок User-Agent 396 HttpConnection httpConnection = 397 ( HttpConnection ) Connector.open( serverUrl ); 398 setUserAgentHeader( httpConnection ); 399 400 // отправка серверу идентификатора сеанса sessionID 401 if ( sessionID != null ) 402 httpConnection.setReguestProperty{ 403 "cookie", sessionID ); 404 405 // получение идентификатора сеанса sessionID от сервера 406 String sessionlDHeaderField = 407 httpConnection.getHeaderField( "Set-cookie" ); 408 409 // сохранение идентификатора sessionID, извлеченного из cookie 410 if ( sessionlDHeaderField ?= null ) ( 411 int index = sessionlDHeaderField.indexO£( ';' ); 412 sessionID = 413 sessionlDHeaderField.substring! °< index ); 414 } 415 416 // получение данных с сервера
286 Глава 5 417 String data = getData( httpConnection ); 416 419 httpConnection.close(); // закрытие соединения 420 421 return data; 422 423 } // конец блока try 424 425 // обработка исключения при взаимодействии с HTTP-сервером 426 catch ( lOExCaption ioException ) { 427 ioException.printStackTrace(); 428 } 429 430 return nvill; 431 432 } // конец метода getServerData 433 434 // загрузка изображения с сервера 435 private Image getServerlmage{ String imageUrl ) 436 { 437 // загрузка изображения 438 try { 439 440 // открытие соединения с сервером 441 HttpConnection httpConnection = 442 ( HttpConnection ) Connector.open( imageUrl ); 443 444 int connectionSize = ( int } httpConnection.getLength(); 445 byte imageBytes(] = new bytef connectionsize ]; 446 447 // чтение изображения с сервера 448 Inputstream input = httpConnection.openInputStream(}; 449 input.read( imageBytes }; 450 451 // создание изображения кз объекта. iraageBytes 4 52 return 453 Image.ereatelmage{ imageBytea, 0, connectionSize ); 454 ) 455 456 // обработка исключения, если объект InputStream не может осуществить ввод 457 catch ( IOException ioException ) { 458 ioException.printStaeklrace(); 459 } 460 461 return null; 462 463 ) // конец метода getServerlmage 464 465 // получение заголовка User-Agent для идентификации клиента сервлетом 466 private void setUserAgentHeader< 467 HttpConnection httpConnection ) 468 { 469 // задание заголовка User-Agent 470 try {
Разработка приложений для беспроводной связи на базе Java и J2ME 287 471 472 // использование свойств profile/configuration заголовка User-Agent 473 String userAgentHeader = "Profile=" + 474 System.getProperty( "microedition.profiles" ) + 475 " Configurations" + 476 System.getProperty ( "microedition,configuration" ); 477 478 // задание заголовка 479 httpConnection.setRequestProperty( 480 "User-Agent1', userAgentHeader ) ; 431 } 482 483 // обработка исключения при получении свойства запроса 4в4 catch ( IoException ioException ) { 4 8 5 ioException.printstackTrace<); 486 1 487 4 88 } // конец метода setUserAgentHeader 489 490 // открытие потока ввода DatalnputStream для приема данных 491 private String getData( HttpConnection httpConnection ) 492 throws ioException 493 { 494 String data = ""; // сохранение данных 495 496 // открытие потока ввода для соединения 497 DatalnputStream datalnputstream = 498 new DatalnputStream ( 499 httpConnection.openInputstrearn() ); 500 501 int inputCharacter = datalnputstream. read {) ; 502 503 // чтение всех данных 504 while ( inputCharacter != -1 ) { 505 data = data + ( char > inputCharacter; 506 inputCharacter = datalnputstream.read(); 507 } 508 509 datalnputstream.close(}; // закрытие потока 510 511 return data; 512 513 } // конец метода getData 514 ) „^ Рис. 5.27. Мидлет TipTestMIDIet загружает тест Tip-Test из сервлета TipTestServlet На рис. 5.28 показан фрагмент API пользовательского интерфейса J2ME.Каждый класс в составе API изображен в виде прямоугольника. Классы, имена которых выделены курсивом, является абстрактным, а стрелки обозначают отношения наследования (стрелка указывает на суперкласс). В API пользовательского интерфейса J2ME абстрактный суперкласс Display able представляет содержимое, которое MIDP-устройство может отобразить на экране. Абстрактные суперклассы Screen и Canvas наследуют классу Dis playable и представляют, соответственно, высокоуровневое и низкоуровневое отображаемое содержимое. Классы Alert,
288 Глава 5 Form, TextBox и List являются конкретными подклассами класса Screen. Извещение (класс Alert) представляет собой экран (класс Screen), который мидлет отображает на короткое время перед отображением другого экрана. Форма (класс Form) объединяет текстовые поля, изображения и группы элементов, выбираемых пользователем. Текстовое поле (класс TextBox) дает возможность пользователю вводить и редактировать текст. Список (класс List) представляет собой перечень строк (String), из которого пользователь может сделать выбор с помощью клавиатуры МШР-устройства. В нашем практическом примере для отображения информации на экране используются классы Form и List. Класс Canvas не содержит каких-либо подклассов. Чтобы использовать класс Canvas, нужно сначала создать конкретный класс, который расширяет класс Canvas, а затем переопределить его метод paint для рисования графики на «холсте». В нашем практическом примере используются только классы высокоуровневого пользовательского интерфейса, поэтому мы не будем касаться использования класса Canvas. javax.mlcroedition.lcdui.DIsplayable Рис. 5.28. Иерархия классов API пользовательского интерфейса J2ME Совет по переносимости программ 5.1 Низкоуровневый API пользовательского интерфейса J2ME позволяет разработчикам создавать визуально более привлекательные экраны, чем те, которые могут быть созданы с помощью высокоуровневого API. Однако применение низкоуровневых API не гарантирует единообразную компоновку элементов на экранах устройств, имеющих различные размеры экрана. Высокоуровневые API обеспечивают более согласованную компоновку для различных устройств. В строке 15 класса TipTestMIDlet объявляется экземпляр класса Display, который действует в качестве менеджера дисплея для мидлета. Мидлет должен содержать один и только один экземпляр класса Display, чтобы осуществлять отображение любого объекта типа DIsplayable. Класс Display является примером паттерна проектирования Singleton, который обеспечинает, что система будет содержать один и только один объект класса, т.е. после реализации системой экземпляра этого объекта программе не будет разрешено создавать дополнительные объекты этого
Разработка приложений для беспроводной связи на базе Java и J2ME 289 класса. Поскольку MIDP -у с тройство имеет всего один экран, мидлет должен содержать только один менеджер дисплея для отображения содержимого на этом экране, и, следовательно, в каждом мидлете может существовать только один объект типа Display. Статический метод getDisplay класса Display возвращает ссылку на единственный в системе объект Display (этот объект Display также называется еди ничным объектом). Если объект Display был создан, последующие вызовы метода getDisplay просто возвращают одну и ту же ссылку на единственный объект Display. Объект Display гарантирует, что мидлетом TipTestMIDlet за один раз будет отображаться только один экран (подкласс класса Displayable). В строках 18-22 объявляется пять экранов (объекты Screen) для мидлета TipTestMIDlet. Мидлет TipTestMroiet содержит список List (строка 18) для представления основного экрана, который содержит ссылку на сервлет "WelcomeServlet, список List (строка 19) для отображения текста из файла index.txt, форму Form (строка 20) для отображения текста из файла info.txt (который содержит ссылку на сервлет TipTest- Servlet), форму Form (строка 21) для отображения вопросов теста Tip-Test и форму Form (строка 22) для отображения ответов на вопросы теста Tip-Test. Экраны Screen также поддерживают программные кнопки: кнопки, которые обычно располагаются на беспроводных устройствах непосредственно иод дисплеем (но над клавиатурой). На рис. 5.8 курсор мыши наведен на правую программную кнопку, которая подсвечена. Слово Select на экране над правой программной кнопкой указывает, что при нажатии пользователем этой программной кнопки будет выбран выделенный элемент из списка List, а после этого на дисплей будет вы веден другой экран. J2ME предоставляет мидлетам эту функциональную возможность посредством объектов Command, инкапсулирующих действие, которое будет выполняться объектом, принимающим объект Command. В строках 25-27 мидлета TipTestMIDlet объявляются три объекта типа Command: select Command, nextCommand и backCommand. Объект selectCommand, как мы вскоре увидим, дает возможность пользователю выбирать на экране элементы из списка List. Объект nextCommand дает возможность пользователю при воспроизведении теста Tip-Test принимать следующий вопрос с вариантами названий рубрик еоветов. Объект backCommand позволяет пользователю просмотреть предыдущий экран. В строках 46-48 реализуются экземпляры этих объектов. Первым параметром конструктора Command является имя, или надпись, которая будет отображаться на экране над программной кнопкой. Второй параметр представляет собой константу, которая задает, каким образом мидлет будет реагировать на нажатие пользователем программной кнопки. Например, константа Command.0K указывает, что пользователь предоставил некоторые входные данные (через текстовое поле или через выбор элемента списка). Константа Command.BACK указывает, что пользователю нужно просмотреть предыдущий экран. Логика, которая управляет поведением мидлета для команд каждого типа, реализована программно. Третий параметр задает, над какой программной кнопкой будет помещена надпись. При реализации ряда объектов Command объект Command с наименьшим номером будет иметь надпись, расположенную справа от программной кнопки в окне эмулятора МГОР-устройства Sun. Для объекта, ассоциированного со следующим наименьшим номером, устройство Sun поместит надпись над левой программной кнопкой. В соответствии с командами в строках 46-48, устройство помещает надпись «Select» над правой программной кнопкой, надпись «Next Tip» над правой программной кнопкой и надпись «Back» над левой программной кнопкой.
290 Глава 5 Ш Совет по переносимости программ 5.2 Схема нумерации команд Command может быть различной для разных устройств. Например, в эмуляторе Sun текст команды Command no умолчанию помещается над правой программной кнопкой. В других уст- ройствах текст может располагаться над левой программной кнопкой. В строке 51 реализуется список с именем mainScreen, который предоставляет ссылку на сервлет Welcomeservlet. Первый параметр, передаваемый объекту List, представляет собой имя списка — этот текст появляется в верхней части экрана MIDP-устройства. Второй параметр представляет собой константу, указывающую на тип реализуемого списка. Тип списка определяет, каким образом пользователь может перемещаться по списку с помощью клавиатуры. Этот параметр может иметь одно из трех значений: List.IMPLICIT, List.EXCLUSIVE и List.MULTIPLE. Константа LaetJMPLlCIT задает, что текущий элемент, на котором находится фокус в списке, представляет собой выбор пользователя, т.е. пользователь изменяет свой выбор при прокрутке списка. Константа List.EXCLUSIVE требует, чтобы пользователь нажимал центральную программную кнопку для указания на выбираемый элемент списка, после чего следует ему нажать правую программную кнопку для завершения выбора. Однако при этом перед окончательным выбором (но после того, как выбираемый элемент помечен), пользователь может осуществлять прокрутку списка. Константа List.MULTIPLE дает пользователю возможность выбирать сразу несколько элементов в списке. В строке 52 команда selectCommand добавляется в экран main Screen, чтобы пользователь видел надпись "Select" над правой программной кнопкой. В строках 55-69 объекту mainScreen разрешается прослушивать события от команды select- Command путем создания нового объекта CommandListener, Когда пользователь нажимает программную кнопку, объект selectCommand вызывает метод сот- mandAction (строки 59-67). Этот метод принимает в качестве параметров объект Command, ассоциированный с последней нажатой программной кнопкой, и объект Display able, над которым это действие выполняется. Логика работы метода сот- mandAction будет разъяснена ниже. Оператор в етроке 73 получает объект менеджера дисплея для устройства. Когда конструктор возвращает управление, AMS вызывает метод startApp, который предписывает мидлету TipTestMIDlet принять ввод пользователя и отобразить содержимое экранов. В строке 81 с использованием менеджера дисплея в качестве текущего экрана устанавливается экран mainScreen. На рис. 5.29 показан результат выполнения этой операции. Теперь мидлет TipTestMIDlet ожидает ввода от пользователя. Единственные события, зарегистрированные для TipTestMIDlet, связаны с правой программной кнопкой. Когда пользователь нажимает эту кнопку, объект selectCommand вызывает метод commandAction любого зарегистрированного слушателя Command- Listener. В строке 63 URL сервлета WelcomeScrvlet передается методу get- ServerData (строки 390-432), который связывается с сервером и принимает данные. В строках 396—397 с использованием параметра URL открывается соединение HttpConnection. В строке 398 осуществляется вызов метода setUserAgentHeader (строки 466-488), чтобы сервер мог идентифицировать, какой клиент отправил запрос. В отличие от Internet Explorer, браузеров Openwave UP и Pbto, клиент J2ME не имеет стандартного заголовка User-Agent, поэтому необходимо определить собственный заголовок и сохранить его в интерфейсе ClientUserAgentHeaders, чтобы сервлеты могли распознать мидлет TipTestMIDlet как клиента J2ME. В строках 473-476 с помощью информации о профиле и конфигурации TipTestMIDlet создается заголовок User-Agent. В строках 479-480 этот заголовок назначается запросу HttpConnection,
Разработка приложений для беспроводной связи на базе Java и J2ME 291 Рис. 5.29. Основной экран мидлета TipTestMIDIet (Публикуется с разрешения корпорации Sun Microsystems, Inc.) Нам также необходимы средства для отслеживания данных этого сеанса, чтобы сервлет мог хранить информацию о состоянии, например, правильный ответ, в промежутке между сеансами. В строках 406-407 осуществляется сохранение поля заголовка Set-cookie соединения HttpConnection, которое предоставляет информацию об этом сеансе, в строке sessionlD (строка 40 листинга). Заголовок Set-cookie содержит информацию, разделяемую точками с запятой. Сервер хранит строку идентификатора сеанса перед первой точкой с запятой. Идентификатор сеанса извлекается в строках 401-403. При следующем соединении мидлета TipTestMIDIet с сервером операторы в строках 401-403 отправляют эту информацию, чтобы идентифицировать сеанс. В строке 417 осуществляется вызов метода get Data (строки 491-513), чтобы принять данные с сервера. В строках 497—499 открывается поток ввода Datalnput- Stream для чтения формируемых сервером данных. В строках 501—507 эти данные записываются в строку, после чего эта строка возвращается. На данный момент мидлет TipTestMIDIet установил соединение с сервлетом WelcomeServlet и получил данные — текст из файла index.txt — которые представляют собой экран приветствия. Вто следующие данные: eLearning Deitel Programming Tips ;j2me/in£o.txt ; В строке 66 листинга эта строка передается в качестве параметра методу crate WelcomeScreen (строки 91-138). В строке 93 осуществляется вызов метода parseData (строки 354-387), который выполняет синтаксический разбор данных и помещает их в массив строк, чтобы можно было осуществить доступ к отдельным строкам. Метод parseData действует аналогично классу java.util.StringTokenizer J2SE, который отсутствует в пакете java.ntil J2ME (по причине ограниченности ресурсов MIDP-устройства). Мы используем точку с запятой в качестве разделителя при синтаксическом анализе данных, чтобы метод parseData возвращал строковый массив из двух элементов, который содержит текст «eLearning Deitel Programming Tips» (название экрана) и *j2me/info.txt» (ссылка на каталог приложения Tip-Test). В строке 96 создается экран WelcomeScreen с использованием первого элемента в строковом массиве в качестве имени списка. В строке 98 в список добавляется строка «Take TipTest», информирующая пользователя, что ему
292 Глава 5 следует загрузить тест Tip Test. Экран приветствия мидлета TipTestMIDlet показан на рис. 5.30. В строках 99-134 объект welcomeScreen регистрируется в качестве слушателя CommandListener для событий операций selectCommand и backCommand. Когда пользователь нажимает программную кнопку, один из объектов, либо select- Command, либо backCommand, в зависимости от того, какая кнопка нажата, вызывает метод commandAction (строки 110-131). Объект Command является примером командного паттерна проектирования Command Design Pattern. В J2ME объект Command может содержать различные команды или инструкции, такие как «показать следующий экран», «показать предыдущий экран» или «выйти из приложения». Эти команды могут прослушиваться несколькими объектами типа Displayable в системе: разработчики MIDP-приложений программируют операции, которые каждый из объектов Displayable выполняет при получении команд. Например, мы регистрируем объект welcomeScreen в качестве слушателя для объектов selectCommand и backCommand. Когда каждый из объектов Screen получает событие Command, метод commandAction использует метод getCommandType события Command для определения, имеет ли команда тип Command.OK («показать следующий экран»), или тип Command.BACK («показать предыдущий экран»), Мидлет TipTestMIDlet разработан таким образом, чтобы он предпринимал действия в соответствии с тийом полученной команды. Если пользователь нажал левую программную кнопку (Back), в строке 128 устанавливается дисплей (объект Display) для отображения экрана mainScreen. Если пользователь нажал правую программную кнопку (Select), в строках 117—122 отображается информационный экран. В строках 117-118 вызывается метод getServerData, который осуществляет соединение с сервером и получает текст из файла j2me/info.txt как строку In this exercise, we will test your knowledge of the Oeitel programming tips ;tiptest ; В строках 121-122 эта строка передается в качестве параметра методу create- InformationScreen (строки 141-200), который формирует экран, предоставляющий справочную информацию об использовании теста Tip-Test. В строке 143 осуществляется вызов метода parseData для синтаксического разбора сформированных сервером данных и помещения их в двухэлементный строковый массив. Первый элемент строкового массива представляет собой название создаваемого экрана, а второй элемент — ссылку на сервлет TipTestServlet. В строке 146 создается форма infoScreen. Форма использует компоненты Stringltem, содержащие строки, для отображения нескольких строк текста. В списке List может быть отображена только одна строка текста и, следовательно, только часть первого элемента строкового массива. Форма Form используется, чтобы отобразить первый элемент строкового массива целиком. В строках 149-150 элемент Stringltem добавляется в форму infoScreen с использованием первого элемента строкового массива. В строках 153-158 создается объект ChoiceGroup, представляющий собой группу элементов, из которых пользователь может выбирать. Этот объект создается с использованием второго элемента строкового массива, чтобы мидлет TipTestMIDlet мог связываться с сервлетом TipTestServlet. Обратите внимание, что в строке 154 группа ChoiceGroup объявляется как EXCLUSIVE, т.е. пользователь должен явно указать, какой элемент выбрать. В нашем примере имеется только один элемент для выбора (tiptest), поэтому этот элемент является выбранным по умолчанию. Информационный экран мидлета TipTestMIDlet показан на рис. 5.31. Типичная ошибка программирования 5.2 Группы ChoiceGroup могут объявляться либо как EXCLUSIVE, либо как MULTIPLE. Объявление группы ChoiceGroup как INCLUSIVE приведет к возбуждению исключения IllegalArgumentException.
Разработка приложений для беспроводной связи на базе Java и J2ME 293 Рис. 5.30. Экран приветствия, формируемый мидлетом TipTestMIDlet. (Публикуется с разрешения Sun Microsystems, inc.) Рис. 5.31. Информационный экран, формируемый мидлетом TipTestMIDlet. (Публикуется с разрешении Sun Microsystems, Inc.) В строках 160-196 объект infoScreen регистрируется в качестве слушателя CommandListener для событий, связанных с операциями selectCommand и back- Command. Когда пользователь нажимает программную кнопку, один из объектов, selectCommand или backCommand, в зависимости от того, какую кнопку нажал пользователь, вызывает метод comznandAction (строки 168-193). Если пользователь нажал левую программную кнопку (Back), в строке 191 устанавливается объект типа Display для отображения экрана w«lcnmeSereen. Если пользователь нажал правую программную кнопку (Select), в строках 175-186 отображается экран
294 Глава 5 с вопросом теста. В строке 175 определяется, какой элемент в группе ChoiceGroup выбран. В строках 177-178 этому элементу назначается ссылка на сервлет TipTest- Servlet. В строках 181-182 осуществляется вызов метода getServIetData, который осуществляет соединение с сервлетом TipTestServlet и принимает вопрос теста Tip Test. Сервлет TipTestServlet случайным образом генерирует информацию каждый раз, когда мидлет TipTestMIDlet устанавливает соединение; данные, которые генерирует сервлет TipTestServlet, имеют следующий формат: http://loealhost:8080/advjhtpl/j2rae/png/portability.png PERF CPE TAD PORT В строках 185—186 осуществляется вызов метода create'Mpl'estScreen (строки 203-256) для отображения вопроса теста Tip-Test. В строке 206 вызывается метод parseData для синтаксического разбора вопроса теста Tip-Test и помещения его в пятиэлементный строковый массив. Первый элемент содержит имя файла изображения, размещенного на сервере. Остальные четыре элемента содержат четыре аббревиатуры рубрик советов, из которых пользователь должен выбрать правильный ответ. Форма Form создается, чтобы нметь возможность отобразить изображение Image и группу с вариантами выбора ChoiceGroup, поскольку никакие другие подклассы класса Display able не обеспечивают такую возможность, В строке 209 создается новая форма tipScreen для отображения вопроса теста Tip-Test. В строке 212 первый элемент строкового массива передается методу getServer- Image (строки 435-463), который создает объект Image из файла изображения, хранящегося на сервере. В строках 441-442 создается соединение HttpConnection с сервером, в строках 448-449 эти данные записываются в поток ввода Input- Stream, а в строках 452-453 осуществляется возврат изображения Image из потока ввода Inputs tre am. В строках 215-216 изображение Image добавляется в экран tipScreen. Теперь мы должны представить четыре возможных ответа, ассоциированных С этим изображением, в виде группы ChoiceGroup. В строках 218-222 создается строковый массив для хранения всех четырех ответов. В строках 225-226 реализуется экземпляр объекта ChoiceGroup, а в строке 229 группа ChoiceGroup добавляется в экран tipScreen. Обратите внимание, что конструктор ChoiceGroup принимает четыре параметра (в отлячие от конструктора ChoiceGroup в методе createlnformation- Screen, который принимает два параметра). Первый параметр представляет собой имя группы для выбора ChoiceGronp, которую мы назвали «Tip Test». Второй параметр объявляет тип этой группы как EXCLUSIVE, поэтому пользователь должен подтвердить свой выбор, прежде чем продолжить выполнение. Третий параметр представляет собой строковый массив, который содержит все возможные ответы. Четвертый параметр представляет собой массив изображений Image, ассоциированных с элементами в строковом массиве. На экране устройства каждое из изображений, содержащихся в массиве Image, помещается рядом с соответствующей строкой из массива String. В данном случае в качестве этого параметра передается null, поскольку нам не нужно выводить изображения рядом с каждым из четырех вариантов ответа. Экран tipScreen представлен на рис, 5.32. На рисунке слева показано изображение Image, для которого пользователь должен выбрать правильное название рубрики советов. Если пользователь осуществит прокрутку этого экрана вниз (рисунок справа), он увидит четыре возможных варианта ответа. Пользователь должен нажать центральную программную кнопку, чтобы подтвердить свой выбор.
Разработка приложений для беспроводной связи на базе Java и J2ME 295 Рис. 5.32. Экран с вопросом теста Tip-Test, формируемый мидлетом TipTestMIDlet В строках 231-251 объект tipScreen регистрируется в качестве слушателя CommandListener для событий операции selectCommand. Когда пользователь нажимает кнопку Select, объект selectCommand вызывает метод commandAction (строки 238-249). В строке 242 определяется, какой элемент в группе ChoiceGroup был выбран пользователем. В строке 244 сделанный пользователем выбор передается методу postData (строки 306-351), который отправляет этот выбор сервлету TipTestServlet. В строках 312-315 осуществляется соединение с сервлетом TipTestServlet и указывается, что соединение является двунаправленным, т.е. мид- лет TipTestMIDlet может как отправлять данные сервлету TipTestServlet, так и принимать от него данные. В строке 317 задается заголовок User-Agent, чтобы сервлет TipTestServlet мог распознать мидлет TipTestMIDlet как клиента J2ME. В строках 320-322 осуществляется отправка идентификатора сеанса, который был сохранен с помощью метода getServerData, чтобы идентифицировать сеанс. В строке 325 задается, что сервлег TipTestServlet будет принимать от мидлета TipTestMIDlet данные, отправленные с помощью метода post. В строках 328-329 открывается поток вывода DataOutputStream, через который в строке 322 выбор пользователя отправляется сервлету TipTestServlet. Как нам известно из раздела 5.3.4, после получения этих данных сервлет TipTestServlet посылает клиенту J2ME правильный ответ. В строке 336 осуществляется вызов метода getData для получения строковых данных с сервера. Эти данные имеют следующий вид: Correct Portability Tip Organizations that develop software must often produce versions customized to a variety of computers and operating systems. These tips offer suggestions to make your applications more portable В строке 338 закрывается соединение между мидлетом TipTestMIDlet И сервлетом TipTestServlet, а в строке 340 возвращаются данные, которые содержат правильный ответ. В строках 247-248 эти строковые данные передаются методу createAnswerScreen (строки 259-303), который создает экран (объект Screen), отображающий ответ. В строке 262 осуществляется вызов метода parseData для синтаксического разбора данных и помещения их в трехэлементный строковый
296 Глава 5 массив. Первый элемент указывает, является ли ответ пользователя правильным или неправильным. Второй и третий элементы содержат, соответственно, правильное название рубрики советов и ее описание. В строке 265 реализуется форма answerScreen путем передачи конструктору первого элемента строкового массива. Форма используется для отображения полного описания рубрики. В строках 268-273 реализуются два объекта Stringltem для хранения названия рубрики советов и ее описания. В строках 276-277 эти элементы Stringltem добавляются в экран answerScreen. Экран answerScreen представлен на рис. 5.33. На рисунке слева показан экран, содержащий правильное название рубрики советов и ее описание. Если пользователь осуществит прокрутку этого экрана вниа, он увидит остальную часть описания (см. рисунок справа). В строках 279-299 объект tipScreen регистрируется в качестве слушателя commandListeuer для событий операции nextCommand. Поведение объекта nextCommand идентично поведению объекта selectCommand; однако, поскольку мы не можем изменить надпись для объекта selectCommand на «Next Tip», нам приходится реализовывать другой объект Command, чтобы над правой программной кнопкой отображался текст «Next Tip». Когда пользователь нажимает правую программную кнопку, объект nextCommand вызывает метод commandAction (строки 286 296), который вызывает метод сге- ateTipTestScreea для формирования следующего вопроса теста Tip-Test: пользователи могут выполнять тест Tip-Test сколь угодно долго. Рис. 5.33. Экран с ответом на вопрос теста, формируемый мидлетом TipTestMIDIet (Публикуется с разрешения Sun Microsystems, Inc.) На этом завершается рассмотрение практического примера приложения для беспроводной связи, реализованного средствами Java и Java 2 Micro Edition. В этом разделе мы создали трехуровневую архитектура, в которой сервлет TipTest- Servlet (средний уровень) размечает случайным образом сгенерированный вопрос теста Tip Test как XML-документ, применяет XSLT-трансформацию к XML-документу, а затем отправляет полученный документ клиентам. Затем мы познакомились с J2ME, обсудив технологии CLDC и MIDP, которые составляют основу интерфейса прикладного программирования J2ME для создания приложений, выполняющихся на мобильных устройствах. Мы изучили жизненный цикл мидлета
Разработка приложений для беспроводной связи на базе Java и J2ME 297 и выяснили, как создавать новый мидлет из класса Mlplet. Мы создали МГОР-при- ложение TipTestMlDlet, а затем обсудили, каким образом оно извлекает данные из сервлетов Welcomeservlet и TipTestServlet. Мы также рассмотрели, как мидлет TipTestMlDlet использует эти данные для построения пользовательских интерфейсов, и каким образом, используя объекты Command, мидлет TipTestMlDlet дает возможность пользователю переходить от одного экрана Screen к другому. В главе 6 будет рассмотрена технология Enterprise JavaBeans (EJB), которая предоставляет модель для построения бизнес-логики в корпоративных приложениях Java. 5.5. Инструкции по установке В этом разделе приводятся инструкции по установке программного обеспечения, используемого в примере. Настройка Web-сервера, Для выполнения этого практического примера требуется Web-сервер, поддерживающий технологию сервлетов. Мы рекомендуем использовать сервер Apache Tomcat Server. Состав каталогов для установки Tomcat описан в разделе 2.3.1. Установив Tomcat, скопируйте содержимое каталога advjhtpl из комплекта примеров к данной главе в каталоге Tomcat в вашей системе. В каталоге advjhtpl имеются четыре подкаталога — iMode, j2me, XHTML, WAP — в которых хранится содержимое, посылаемое сервлетами клиентам соответствующих типов. Далее необходимо скопировать содержимое, приведенное на рис. 5.34, которое представляет собой содержимое элемента webapp документа wml.xml (этот документ имеется в комплекте примеров к данной главе). Файл wml.xml должен располагаться в каталоге WEB-INF Tomcat. Например, в нашей системе файл web.xml находится в каталоге С •. \ jakacta- tomcat- 3.2.2\webapps\adv}htpl\W£B-lHF\ 1 <!— Определения сервлетов —> 2 <servlet> 3 <s#rvlet-naae>wel.come</servlet-name> 4 5 <description> 6 A servlet that returns a "Welcome" screen through 7 ад HTTP get request 8 </description> 9 10 <servlet-class> 11 com.deitel.advjhtpl.wireless.WelcomeServlet 12 </servlet-class> 13 </servlet> 14 15 <servlet> 16 <servlet-name>tiptest</servlet-name> 17 18 <description> 19 A servlet access a database to generate tests for 20 Deitel programming tips 21 </description> 22 23 <i.nit-param> 24 <param-name>DATABASE_URL</param~name>
298 Глава 5 25 <param-value> jdbc; cloudscape: nil: tips</param-value> 26 </init-param> 27 28 <init-para«n> 29 <param-name>JDBC_DRIVER</param-name> 30 <param-value> 31 COM.eloudseape.core.FmiJdbcDriver 32 </param-value> 33 </init-paea«> 34 35 <servlet-class> 36 com.deitel.advjhtpl.wireless.TipTestServlet 37 </servlet-class> 38 </servlet> 3» 40 <!— Карты сервлетов --> 41 <servlet-mapping> 42 <servlet-name>welcome</3ervlet-name> 43 <utl-pattern>/welcune</url-pattern> 44 </servlet-mapping> 45 46 <servlet-mapping> 47 <servlet-name>tiptest</3ervlet-name> 4 8 <ur1-pattern>/tiptest</ur1-pattern> 49 </servlet-mapping> 50 51 <mime-mapping> <!-- Формат WML —> 52 <extension>wml</extension> 53 <miuie-type>text/vnd.wap.wml</iiLLme-type> 54 </mime-mapping> 55 56 <miiae-mapping> <!-- Формат Wireless Bitmap —> 57 <extension>wbmp</extension> 58 <mjjae-type>image/vnd.wap.wbnrp</m±me-type> 59 </mime-mapping> Рис. 5.34. Дескриптор развертывания для выполнения сере летав WelcomeServlet и TipTestServlet В листинге на рис. 5.34 в строках 2-13 описывается сервлет WelcomeServlet, a в строках 15-38 — сервлет TipTestServlet. В строках 23-33 объявляются два элемента init-param, которые дают нам возможность менять используемую сервлетом TipTestServlet базу данных без модификации файла TipTestServlet.java. В строках 41-49 двум сервлетам ставятся в соответствие определенные URL. В строках 51 59 осуществляется настройка Tomcat для обработки WML-содержимого с надлежащими МШЕ-типами. Если не указать эти MIME-типы, WAP-клиенты не смогут получать содержимое. Последний этап настройки Tomcat дли выполнения практического примера состоит в задании классов, которые Tomcat использует для выполнения сервлетов. Создайте структуру каталогов com/deitel/advjhtpl/wireless в каталоге WEB-INF/ classes. Например, в нашей системе структура каталогов будет следующей: С:\Jakarta-tomcat-3.2.2\webapps\advjhtpl\WEB-INF\classes\com\ deitel\advjhtplWireless Скопируйте файлы WelcomeServlet.class и TipTestServlet.class в этот каталог.
Разработка приложений для беспроводной связи на базе Java и J2ME 299 Настройка базы данных Для этого практического примера также требуется база данных, из которой сервлет может извлекать информацию о рубриках советов. Мы рекомендуем использовать базу данных Cloudscape. Скопируйте файл tips.sql и каталог tips из каталога примеров к этой главе в каталог frameworks/RmiJdbc/Ъш, в котором в вашей системе установлен Cloudscape. Например, в нашей системе Cloudscape установлен в каталоге c:\cloudscape_3.6, поэтому файл tips.sql и каталог tips должны содержаться в каталоге C:\cloiidscape_3.6\frameworKs\RmiJdbc\bm\. Далее, скопируйте файлы cloudscape.jar и RmiJdbe.jar (они имеются в комплекте примеров к этой главе) в каталог W ЕВ -INF/lib Tomcat. Например, в нашей системе это будет каталог С:\jakarta-tomcat-З.2.2\webapps\advjhtpl\WEB-INF\lib\ Поместив файлы cloudscape.jar и RmiJdbe.jar в этот каталог, вы дадите возможность сервлету TipTcstServlet соединяться с базой данных Cloudscape я извлекать из нее информацию. Установка и настройка J2ME Wireless Toolkit Чтобы воспользоваться MIDP-устройством Sun для разработки МЮР-приложе- ний, нужно применить набор инструментальных средств J2ME Wireless Toolkit. Этот инструментарий можно загрузить с сайта java.sun.com/product3/j2mewtoolkit/ На момент написания этой книги для загрузки была доступна версия 1.0.2. В процессе установки можно указывать, хотите ли вы интегрировать J2ME Wireless Toolkit с Forte (это облегчает разработку MIDP-приложений в Forte), либо будете выполнять инструментарий как автономное приложение. Мы рекомендуем интегрировать инструментарий в Forte. Бели вы интегрировали инструментарий, создайте структуру каталогов com/ deitel/advjhtpl/wireless в каталоге разработки Forte в вашей системе. Например, в нашей системе каталогом разработки Forte является c:\forte4j\development, поэтому мы создаем структуру каталогов С:\forte43\Development\com\deitel\advjhtpl\wirelessS Далее, скопируйте файлы TipTestMIDlet.java и TipTestMIDlet.jar из комплекта примеров к этой главе в данный каталог. Раскрыв эту структуру каталогов в Forte, вы должны увидеть TipTestMIDlet.Java TipTestMIDlet.jar Файл TipTestMIDlet.jar представляет собой комплект мидлетов, который содержит класс TipTestMIDlet- Чтобы выполнить класс TipTestMIDlet, щелкните правой кнопкой мыши на значке TipTestMIDlet.jar, а затем выберите операцию выполнения. Если вы предполагаете использовать инструментарий Wireless Toolkit (или если вы установили инструментарий как автономное приложение), сначала откройте Wireless Toolkit, выбрав KToolbar в каталоге, в который вы хотите установить инструментарий. Далее» нажмите кнопку New Project. R текстовом поле Project Name введите TipTestMIDlet В текстовом поле MIDlet Class Name введите com.deitel.advjhtpl.wireless.TiptestMIDlet затем нажмите кнопку create Project. В появившемся окне Settings нажмите кнопку ОК. Далее, скопируйте файл TipTestMIDlet.java из комплекта примеров
300 Глава 5 к этой главе в подкаталог apps/TipTestMIDIet/src каталога, в котором установлен инструментарий. Например, в нашей системе мы установили инструментарий в каталог C:\J2mewtk, поэтому следует скопировать файл TipTestMIDlet.java в каталог С: \J2mewtJc\apps\TipTestMIDlet\srcS Вернитесь в окно Wireless Toolkit и нажмите кнопку Build. После завершения компиляции и предварительной проверки мидлета TipTestMIDlet нажмите кнопку Ran, чтобы выполнить мидлет TipTestMIDlet. Memo Device, расположенное в правой части окна ипструмектария, дает возможность выбирать, на каком устройстве выполнять мидлет TipTestMIDlet. Хотя устройством, предлагаемым по умолчанию, является эмулятор МШР-устройства Sun с экраном, позволяющим воспроизводить оттенки серого, вы можете выбрать для выполнения мидлета TipTestMIDlet и другие устройства, такие как RIM Blackberry-957™ и Motorola i85s™. Другие типы клиентов В таблице на рис. 5.35 приведены адреса сайтов, с которых можно загрузить браузеры, используемые в данном практическом примере. Браузер Microsoft Internet Explorer Имитатор Open wave UP Pixo Internet Microbrowser URL www.microsoft.com/downloads/search.asp? developer.openwave.com/download/license_j41.html www.pixo.com/products/products 001.htm Рис. 5,35. URL браузеров, используемых в практическом примере 5.6. Ресурсы в Internet и во Всемирной паутине www.Java.sun.com/j2me С этого сайта можно загрузить пакет Sun Java 2 Micro Edition. www,onjava.com/pub/a/onjava/2001/03/OB/J2me.html Этот сайт содержит статью, посвященную J2ME. www.jguru.com/faq/home.jsp?topic=J2ME На этом сайте имеется FAQ (часто задаваемые вопросы) по J2ME. www.wireless da vne t.com/channe1s/j ava/features/j 2ma_http.phtrol Этот сайт затрагивает проблемы сетевого программирования с использованием устройств, поддерживающих J2ME. www. eri-cgiguere. com/micro java/Eallacies. html Этот сайт содержит перечень некоторых характерных заблуждений относительно J2ME. www.motorola.com/Java Этот сайт посвящен вопросам интеграции J2ME в новые устройства беспроводной связи Motorola. www.internetnews.com/wd-news/article/О, ,10_5ЭЭ091, 00.html На этом сайте можно найти информацию, посвященную интеграции J2ME в карманные компьютеры Palm. www.mot.com/Java/devices.html На этом сайте приведен список имеющихся на рынке беспроводных устройств, которые поддерживают технологию J2ME,
Разработка приложений для беспроводной связи на базе Java и J2ME 301 www.nttdocomo.com/i/index.html Это Web-сайт кампании NTT DoCoMo — создателя технологии i-roode. www.anywhereyougo.com/ imode/index .po На этом сайте представлены новости, относящиеся к технологии i-mode. www.i-modesales.com Этот сайт предоставляет информацию о моделях телефонов и сервисах, использующих технологию i-mode. www.wep.com Этот сайч содержит новости и информацию о новых разработках в сфере W АР -технологий. www.wapforum.org/ На этом сайте обсуждаются преимущества, которые обеспечивает технология WAP. Резюме • Данный практический пример представляет собой построенное по трехуровневой архитектуре приложение для проведения теста (Tip Test) с несколькими вариантами ответов, которое дает возможность пользователям проверить знания условных обозначений рубрик советов по программированию, используемых в книге. • Разработчики могут использовать технологию Java для создания приложений для беспроводной связи и серверных приложений, которые также могут создаваться с помощью технологии ASP. • Информационный уровень представлен базой данных. Средний уровень представлен двумя сервлетами — WelcomeServlet и TipTestServlet — которые генерируют содержимое для клиентов различного типа. Клиентский уровень представлен клиентами четырех типов: Internet Explorer, WAP, i-mode и J2ME. Клиент каждого типа воспроизводит содержимое по-своему. • Microsoft Internet Explorer воспринимает XHTML-содержимое. • Имитатор Openwave UP — это WAP-клиент, который воспринимает WML-содержимое. • Pixo Internet Microbrowser — это клиент i-mode, который воспринимает сНТМЬ-содержи- мое. • Эмулятор MIDP-устройства Sun отображает клиента J2ME, который воспринимает содержимое в виде обычного текста. • J2ME™ (Java™ 2 Micro Edition) — это новая платформа Java, созданная Sun для разработки приложений для различных бытовых устройств, таких как телевизионные игровые приставки, Web-терминалы, встроенные системы, мобильные телефоны и пейджеры. • MIDP (Mobile Information Device Profile) представляет собой набор интерфейсов прикладного программирования, который позволяет разработчикам решать задачи, характерные для мобильных устройств, такие как создание пользовательских интерфейсов, локальное хранение информации и сетевые взаимодействия. • Устройства, которые выполняют приложения для MIDP, называются MIDP-устройствами (например, сотовые телефоны или пейджеры). ■ Клиенты взаимодействуют с сервлетами, направляя сервлетам запросы get и post. Если клиент отправляет сервлету (объект HttpServlet) get-запрос, запрос обрабатывается методом doGet. Бели клиент отправляет сервлету post-запрос, запрос обрабатывается методом doPost. • Все клиенты имеют уникальный заголовок User-Agent, который содержит информацию о типе клиента, запросившего данные с сервера. • Метод getlnitParameter интерфейса HttpServlet дает возможность указывать на информацию (например, местонахождение базы данных или драйверов), объявленную в документе web.xml. Для изменения этой информации достаточно модифицировать элемент <param-value> в элементе <init-param> документа web.xml. • Объекты org.w3c.dom.Element представляют элементы, являющиеся узлами XML-документов. • Для получения различного содержимого для клиента различного типа следует применить к XML-документу XSLT-трансформапию, Клиент каждого типа воспроизводит это содержимое соответствующим образом.
302 Глава 5 • Для клиента J2ME XML не использовался, поскольку на момент написания этой книги Л2МЕ не поддерживал ХМТ. бея применения специализированного программного обеспечения на базе XML. • J2ME использует технологии Connected Limited Device Configuration. (CLDC) и Mobile Information Device Profile (MIDP), которые предоставляют разработчикам интерфейсы прикладного программирования для написания приложений J2ME и развертывания их на мобильных устройствах различных типов. • CLDC — это набор интерфейсов прикладного программирования, который дает возможность разработчикам создавать приложения для устройств с ограниченными ресурсами, т.е. с ограниченным размером экрана, объемом памяти, мощностью и скоростью передачи. » В настоящий момент CLDC не содержит ряда возможностей, которые обычно «имеют под рукой» разработчики на других платформах Java, например, операции с плавающей запятой, сериалиэуемые объекты и группы программных потоков. • MIDP представляет собой набор интерфейсов прикладного программирования, который дает возможность разработчикам реализовывать специфичные для мобильных устройств функции, такие как создание пользовательских интерфейсов, локальное хранение данных и определение жизненных циклов приложений для МЩР-клиеятОБ. • Мидлет — это разновидность приложения для МГОР-клиеитов. Все мидлеты расширяют класс javax.microedition.midlet.MIDlet. • Разработчики мыдле-гоэ хранят несколько мидлетов в jar-файле, называемом комплектом мидлетов, на сервере. • Программа управления приложением (Application Management Software — AMS), размещенная на МШР-устройстве, загружает комплект мидлетов с сервера, открывает комплект мидлетов, а затем запускает указанный пользователем мидлет на МШР-устройстве. • Жизненный цикл мидлета определяется методами startApp, pauseApp и destroyApp. • В J2ME API пользовательского интерфейса делятся на высокоуровневые и низкоуровневые. Низкоуровневые АРЕ позволяют разработчикам добавлять в приложения графику и анимацию, в то время как высокоуровневые API пользовательского интерфейса дают возможность разработчикам добавлять в приложения текстовые поля, списки и формы. • Класс Displayable представляет содержимое, которое MIDP-устройство может отобразить на экране, Классы Screen (высокоуровневые) и Canvas (низкоуровневые) расширяют класс Displayable. • Класс Display действует в качестве менеджера мидлета. В мидлете может иметься только один объект Display. • J2ME предоставляет поддержку программных кнопок в мвдлете посредством объектов Command, которые инкапсулируют действие, выполняемое объектом, принимающим объект Command. • Два высокоуровневых класса пользовательского интерфейса — List и Form — расширяют класс Screen, • Форма (объект Form) может содержать группы элементов (объект ChoiceGroup), из которых пользователь может делать выбор. Терминология application management software (AMS) — программа управления приложением cHTML (компактный вариант HTML) class Canvas, класс class ChoiceGronp, класс class Command, класс class CommandListener, класс class Display, класс class Displayable, класс class DocamentBuilder, класс class DocamentBiiilderFactory, класс class DOMSource, класс class Element, класс class Form, класс class HttpServletResponse, класс class HttpServletRequest, класс class HttpSession, класс class luputStream, класс class List, класс class Screen, класс class Stream Source, класс class Stringrtem, класс class TextBox, класс class Transfomer, класс class TransfomerFactory, класс client — клиент CLDC packages java.io, java.lang, java.util, пакеты CLDC Connected Limited Device Configuration (CLDC)
Разработка приложений для беспроводной связи на базе Java и J2ME 303 database — база данных Extensible Hyper Text Markup Language (XHTML) get and post data — методы get и past отправки данных i-mode information tier — информационный уровень Java 2 Micro Edition (J2ME) J2ME high-level user-interface API — высокоуровневый API пользовательского интерфейса J2ME J2ME low-level user-interface API — низкоуровневый API пользовательского интерфейса J2ME Java application descriptor file — файл дескриптора приложения Java KVM, виртуальная машина Openwave UP simulator — имитатор Openwave UP method commandAction of class CommandListener — метод commandAction класса CommandListener method doGet of class HttpServlet — метод doGet класса HttpServlet method doPost of Ыаяя HttpServlet — метод doPost класса HttpServlet method destroyApp of class MIDlet — метод destroyApp класса MIDlet method pa use App of class MIDlet — метод panseApp класса MIDlet method startApp of class MIDlet — метод startApp класса MIDlet Упражнения для самоконтроля 5.1. Заполните пропуски в следующих предложениях: a) Трехуровневая архитектура обычно состоит из уровня, уровня и __ уровня. b) Сервлеты в нашем практическом примере генерируют -содержимое для Internet Explorer, -содержимое для WAP-клиента, -содержимое для клиента i-mode и содержимое в виде ___ для клиента J2ME. c) Сервлет TipTestServlet использует объект для хранения правильного названия рубрики советов и ее описания. d) CLDC — сокращение от _. e) MIDP — сокращение от . f) Разработчики могут упаковывать несколько мидлетов в jar-файл под названием g) Файл дескриптора приложения имеет расширение . h) Жизненный цикл мидлета определяется методами , и . i) Класс из пакета javax.microedition.ledui представляет содержимое, которое MIDP-устройство может отображать на экране, j) Класс javax.microedition.Display является примером паттерна проектирования 5.2. Ответьте, является ли каждое из приведенных ниже высказываний истпинным или ложным. Если высказывание ложно, объясните, почему. а) Объект HttpServlet вызывает метод doGet при получении запроса get и метод doPost при получении запроса post (истинно/ложно). middle tier — средний уровень MIDlet — мидлет MEDlet lifecycle — жизненный цикл мидле- та MIDlet suite — комплект мидлетов MIDP application — MIDP-приложение MIDP devices — MIDP-устройства MIDP package javax.microeditionЛо, аакет MIDP MIDP package javax.microeditlon.ledni, пакет MIDP MIDP package javax.microedition.nns, пакет MIDP mobile device — мобильное устройство Mobile Information Device Profile (MIDP) namespace — пространство имен Pixo Internet Microorowser — микробраузер Pixo server — сервер Sun MlDP-device emulator — эмулятор MIDP-устройства Sun three-tier architecture — трехуровневая архитектура User-Agent header — заголовок User-Agent servlet — сервлет Wireless Application Protocol (WAP), протокол Wireless Markup Language (WML), язык разметки XML XML Document — XML-документ (объект Document) XSLT
304 Глава 5 b) Заголовок User-Agent содержит информацию о сервере (истинно/ложно). c) Статический метод newDocumentBuilder класса DocnmentBuilder создает новый объект DocnmentBuilder (истинно/ложио). d) В данном практическом примере сервлет TipTestServlet использует XSLT для трансформации XML-документов теста Tip-Test а правильное содержимое для всех типов клиентов (истинно/ложно). e) В состав CLDC J2ME входят пакеты java.io, Java.Jang и java.net (истинно/ложно). f) Программа управления приложением загружает мидлет н MIDP-устройство (истинно/ложно). g) Файл дескриптора приложения задает такую информацию, как конфигурация MIDP-устройстаа и ими мидлега (истшшо/ложио). h) Классы Alert. Form, Screen и List являются конкретными классами пакета высокоуровневого пользовательского интерфейса MIDP (истинно/ложно). 1) Объект javax.microedition.ledui.Command инкапсулирует действие, которое будет выполняться объектом, принимающим объект Command (истинно/ложно), j) Поле заголовка Set-Cookie сеанса HttpSession содержит информацию о сеансе (истинно/ложно). Ответы на упражнения для самоконтроля 5.1. а) клиентского (верхнего), среднего, информационного (нижнего), о) XHTML, WAP, cHTML, обычного текста, с) HttpSession. d) Connected Limited Device Configuration, e) Mobile Information Device Profile, f) коплект мидлетов. g) .jad. h) startApp, panseApp, destroyApp. i) Displayable. j) Singleton. 5.2. а) Истинно, b) Ложно. Заголовок User-Agent содержит информацию о типе клиента, запросившего данные с сервера, с) Ложно. Новый объект DocnmentBnilder создает статический метод newDocumentBuilder класса DocumentBuilderFactory. Класс Docu- mentBnilder создает новые объекты Document, d) Ложно. Сервлет TipTestServlet использует XSLT для трансформации XML-документов теста Tip-Test н правильное содержимое только для клиентов Internet Explorer, WAP и i-mode. e) Ложно. В состан CLDC J2ME входят пакеты java.io, java.lang и java.util. f) Истинно, g) Истинно, h) Ложно. Классы Alert, Form, TextBos и List являются конкретными классами пакета высокоуровневого пользовательского интерфейса. Класс Screen является абстрактным классом, который расширяют указанные выше классы, i) Истинно, j) Истинно. Упражнения 5.3. Чтобы повысить степень управляемости и расширяемости сервлета TipTestServlet, мы храним имя драйвера и URL базы данных в элементе <init-param> в документе web.xral. Назовите 3 другие константы, которые можно хранить в элементе <init-param> для сервлетов WelcomeServlet и TipTestServlet. 5.4. Расширьте список возможных нариантов ответов на вопрос теста Tip-Test с четырех до пяти. 5.5. Метод getResultTable класса TipTestServlet создает двухмерный строковый массив для представления результирующего множества. Однако в связи с тем, что размер этого массива является жестко заданным (семь строк на пять столбцов), этот массив не сможет хранить дополнительные строки, если мы добавим а базу данных еще одну рубрику советов. Модифицируйте сералет TipTestServlet, чтобы ои мог работать с любым числом рубрик советов из базы данных. Создайте прокручиваемое (scrollable) результирующее множество ResultSet, чтобы иметь аозможность согласованно задавать количество строк в таблице ResultSet и размер строкового массива. 5.6. В строках 29-38 класса TipTestMlDlet задается URL базы сервлетов и URL сервлета WelcomeServlet. Подобный подход может создать проблемы, если администратор сети изменит какой-либо из этих URL. Исходя из подобных соображений, желательно, чтобы файл дескриптора приложения содержал базовый URL сервлетов, и чтобы пользователь MIDP-устройетва задавал URL сервлета WelcomeServlet с клавиатуры MlDP-устройства.
Разработка приложений для беспроводной связи на базе Java и J2ME 305 a) Запишите код для базового URL сервлетов (http://localhost:8080/advjhtpl/) в файл дескриптора приложения, введя SerVlet-ORL:http://localhost:80eO/advjhtpl/ в конец jad-файла мидлета TipTestMIDlet. В теле мидлета TipTestMIDlet используйте метод getAppProperty класса MIDlet, возвращающий URL сералета. (Метод getAppProperty принимает в качестве параметра строку С именем тега свойства, например, " Servlet-URL.", и возвращает строку, ассоциированную с этим свойствам.) b) Предоставьте пользователю возможность задавать URL сервлета TipTestServIet- Модифицируйте конструктор класса TipTestMIDlet, чтобы создавать основной экран mainScreen (строка 51) как объект TextBox: подкласс класса Screen, который позволяет пользователю вводить текст. Конструктор TextBox принимает четыре параметра: 1) строку, представляющую заголовок текстового ноля TextBox; 2) строку, представляющую начальное содержимое текстового поля TextBox; 3) целое число, представляющее максимальное количество символов, которые могут содержаться в текстовом поле TextBox; 4) целое число в виде ограничителя, задающего требуемый формат вво^ да; например, если вы хотите, чтобы пользователь ввел номер телефона, в качестве ограничителя следует указать TextField.FHQNENUMBER. Нам яужно, чтобы пользователь вводил URL, поэтому в качестве ограничителя следует указать TextField.URL. Используйте метод getStrinjf класса TextBox, возвращающий содержимое текстового поля (введенное пользователем), чтобы сохранить URL сервлета WelcomeServlet, когда пользователь нажимает программную кнопку Select. 5.7. Модифицируйте мидлет TipTestMIDlet, чтобы дать возможность пользователю также осуществлять выход из приложения из экрана ответа на вопрос теста Tip-Test, а не только принимать следующий вопрос теста. Создайте объект Command, который использует константу Command.EXIT, и зарегистрируйте answerSereen в качестве слушателя для этого объекта Command. (Подсказка. Переопределите метод destroyApp, чтобы вызывать метод notifj'Destroy класса javax.microedition.inidlet.MrDlet.) Литература Leng, Y. and Zhu, J., Wireless Java™ Programming with J2ME. SAMS. Indiana; 2001. Giguere, E., Java 2 Micro Edition; Professional Developer's Guide. John Wiley & Sons. 2000. Knudsen, J. Wireless Java: Developing with Java 2, Micro Edition. Apress, California; 2001. Kroll, M. And Haustein, S., Java 2 Micro Edition (J2ME) Application Development. SAMS. Indiana; 2001. Morrison, M. Sams Teach Yourself Wireless Java with J2ME in 21 Days. SAMS. Indiana; 2001. Riggs, R., Taivalsaari, A., and VatldenBrink, M,, Programming Wireless Devices with Java"* 2 Platform, Micro Edition. Addison-Wesley. Boston; 2001.
6 Сеансовые компоненты EJB и распределенные транзакции Цели • Получить представление о EJB ' как о компонентах бизнес-логики. • Узнать о преимуществах и недостатках сеансовых компонентов EJB с состоянием и без состояния. • Понять роль JNDI в корпоративных приложениях Java. • Получить представление о распределенных транзакциях. • Узнать о преимуществах и недостатках транзакций, управляемых контейнером, и транзакций, управляемых компонентом. Только в дороге начинаешь ценить домашний уют и острее чувствовать радость оттого, что ты снова дома. Генри Дэвяд Торо Молодость была бы идеальной порой жизни, если, бы наступала чуть попозже. Герберт Генри Эскит Мы не можем порождать события. Наше дело — разумно усовершенствовать их. Сэмюэл Адаме
зов Глава 6 _ ■■ .--уая* ■--.■■кг- •■" --:э-ь- ЬбЙ., i Введение^! Е.1.£Удаленнь№ -..! *> 6;2.1.^Удал5нныи интерфейс - -■■*"■ -*■- : ^Ь * J--» ■» 6:3. Сеансовые|компонёнты ■ Т£ ГДШ^. ...i ""."'., .ь43.1\>Хеансовыё компоненты EJB ссостояни '"" Sr' 4-3.2;''Развертывант сеаi1сзды)£кампоне1itc ■^ 6.33. Сеансовые компоненты"EJB'без.сосгоя ":.6.4. ,Е]В-тоаНзакцииЧ;* ..> >'%„-«.-■■.-$,;■' fr' -':'гЙ-" ~- I -i .-Я4"*-'J4E •й"ч5г»У'1"' ' f'»***1!. -a ■?■•-■- 'Sib" ' -»*.1"- -■* ' . ' .'3 . .. D.d-2- Р»Шши»иий mauMKi luSV'unnaiinauuBu из ппмив ? 6.1. Введение В предыдущих главах мы познакомились с применением серьлетов Java и серверных страниц JavaServer Pages для реализации бизнес-логики и логики представления данных в многоуровневых приложениях. В этой главе мы познакомимся с компонентами Enterprise JavaBeans (EJB), которые предоставляют компонентную модель для построения бизнес-логики п корпоративных приложениях Java. В данной главе будут рассмотрены сеансовые EJB в двух формах: с состоянием и без состояния. Мы также познакомимся с поддержкой EJB-компонентами распределенных транзакций, которые помогают обеспечить целостность данных на серверах баз данных и серверах приложений. Изучив эту главу, вы сможете разрабатывать сеансовые EJB-компоненты с состоянием и без состояния. Вы также сможете создавать EJB-компоненты, которые используют средства поддержки распределенных транзакций J2EE для атомарного обновления данных при наличии нескольких баз данных. 6.2. Обзор технологии EJB Каждый компонент EJB состоит из удаленного интерфейса, собственного интерфейса и реализации EJB-компонента. Удаленный интерфейс определяет бизнес-методы, которые клиент EJB может вызывать. Собственный («домашний») интерфейс предоставляет методы create для создания новых экземпляров EJB, ме-
Сеансовые компоненты EJB и распределенные транзакции 309 годы поиска (finder) для нахождения экземпляров EJB и методы remove для удаления экземпляров EJB. Реализация EJB-компонента определяет бизнес-методы, объявленные в удаленном интерфейсе, и методы создания, удаления и поиска собственного интерфейса. Контейнер EJB предоставляет окружение выполнения EJB-компонента и средства управления его жизненным циклом. Спецификация J2EE определяет шесть ролей для программистов, реализующих корпоративные системы. Каждая из ролей ответственна за создание определенной части распределенного приложения. Поставщик компонентов EJB реализует классы Java для EJB. Сборщик приложения формирует компоненты приложения из EJB, реализованных поставщиком EJB-компонентов. Администратор развертывания принимает компоненты приложения, предоставленные сборщиком приложения, и развертывает приложение в контейнере EJB, обеспечивая при этом соблюдение всех зависимостей. Поставщик сервера EJB и поставщик контейнера EJB реализуют сервер приложений, предназначенный для развертывания приложений J2EE. Сервер приложений обычно содержит контейнер EJB и контейнер сервлетов и предоставляет сервисы, такие как служба каталогов JNDI, пулы соединений с базами данных, средства интеграции с распределенными системами и средства управления ресурсами. Один и тот же разработчик или группа разработчиков при конструировании и развертывании распределенного приложения могут выступать одновременно в нескольких ролях. Для получения более подробной информации о различных ролях при построении приложений J2EE обратитесь к списку ресурсов в разделе 6.5. 6.2.1. Удаленный интерфейс Удаленный интерфейс EJB объявляет бизнес-методы, которые клиент EJB-компонента может вызывать. Удаленный интерфейс должен расширять интерфейс javax.ejb.EJBObject. Контейнер EJB генерирует класс, который реализует удаленный интерфейс. Этот сгенерированный класс реализует методы интерфейса EJBObject и делегирует вызовы бизнес-методов реализации EJB-компонента (см, раздел 6.2.3). В таблице на рис. 6.1 описаны методы интерфейса EJBObject. В каждом методе удаленного интерфейса должно быть объявлено, что он возбуждает исключение Remote Exception. Каждый метод также может генерировать исключения, специфичные для приложения, например, IHegalArgnmentExcepti- оп, если предоставленный параметр не отвечает определенному критерию. 6.2.2. Собственный интерфейс Собственный («домашний») интерфейс EJB объявляет методы для создания, удаления и поиска экземпляров EJB. Собственный интерфейс должен расширять интерфейс javax.ejb.EJBHome. Контейнер EJB обеспечивает реализацию собственного интерфейса. В зависимости от типа EJB-компонента (т.е. сеансовый компонент или компонент-сущность) контейнер будет вызывать методы реализации EJB, которые соответствуют методам создания (create), удаления (remove) и поиска (finder) собственного интерфейса. Методы поиска дают возможность клиентам обнаруживать определенный экземпляр EJB-компонента. В таблице на рис. 6.2 описаны методы интерфейса EJBHome. Метод getEJBHomu getHandle Описание Воззрзщает интерфейс EJBHome длр объекта EJBObject. Воззрзщэет ссылку Handle для объекта EJBObject. Handle представляет собой неишрнну<г), се реализуемую (Serial liable) ссылку на объект EJBObject.
310 Глава 6 Метод getPrimaryKey isIdentical remove Описание Возвращает первичный ключ объекта EJBObject, если EJBObject является компонентом -сущностью EJB. Возвращает булево значение, указывающее, идентичен ли параметр EJBObject текущему объекту EJBObject. Удаляет объект EJBObject. Рис. 6.1. Методы интерфейса javax.ejb.EJBObject Метод getEJBMetaData getHoraeHandle reiBove '■ ■■"■ " ■ =*^— Описание Возвращает объект EJBMetaData, который предоставляет информацию о EJ8-компоненте, например, класс его собственного интерфейса. а также информирует, является пи он сеансовым EJ В -компонентом. Возвращает ссылку Handle для интерфейса EJBHome. Удаляет объект EJBObject, идентифицируемый по заданной ссылке Handle или объекту первичного ключа. Рис. 6,2. Методы интерфейса javax.ejb.EJBHome 6.2.3. Реализация EJB Реализация EJB-компонента определяет бизнес-методы, объявленные в удаленном интерфейсе EJB, и методы создания, удаления и поиска, объявленные в собственном интерфейсе EJB. Сеансовые EJB также должны реалиаовывать интерфейс javax.ejb.SessionBean. Подробнее интерфейс SessionBean мы обсудим в разделе 6.3. 6.2.4. Контейнер EJB Контейнер EJB управляет взаимодействиями с клиентом EJB-компонента, вызовами методов, транзакциями, безопасностью, исключениями и т.д. Клиент EJB-компонента не взаимодействует с EJB напрямую. Клиенты осуществляют доступ к контейнеру EJB для получения удаленных ссылок на экземпляры EJB-компонента. Когда клиент вызывает бизнес-метод EJB-кошгонента, вызов сначала поступает контейнеру EJB, который затем делегирует вызов бизнес-метода реализации EJB-компонента. Контейнер EJB также управляет жизненным циклом своих EJB-компонентов. Контейнеры EJB обычно организуют пулы экземпляров EJB-компонентов для повышения производительности. Имея пул неактивных экземпляров EJB-компонентов, контейнер EJB может увеличить производительность, избегая затрат, связанных с созданием новых экземпляров EJB-компонентов для каждого клиентского запроса. Контейнер EJB просто активирует экземпляр из пула и выполняет необходимую инициализацию. Контейнер EJB также может создавать новые экземпляры EJB-компонентов и удалять имеющиеся экземпляры. Кроме того, контейнер EJB предоставляет расширенные сервисы для EJR-сущностей (см. главу 7). 6.3. Сеансовые компоненты Экземпляр сеансового компонента EJB выполняет обработку бизнес-логики для конкретного клиента. Сеансовые EJB-компоненты могут манипулировать данными в базе данных, но, в отличие от EJB-сущностей (глава 7), сеансовые EJB-комяоненты
Сеансовые компоненты EJB и распределенные транзакции 311 не являются сохраняемыми (персистентными) и не представляют информацию из базы данных непосредственно. Экземпляры сеансовых EJB-компоненгов теряются, если имеет место сбой в контейнере EJB. Существует два типа сеансовых компонентов EJB: с состоянием (stateful) и без состояния (stateless). В разделе 6.3.1 рассказывается о сеансовых компонентах EJB с состоянием, а в разделе 6.3.3 — о сеансовых компонентах EJB без состояния. В разделе 6.3.2 обсуждаются проблемы развертывания сеансовых компонентов EJB средствами эталонной реализации J2EE 1.2.1. 6.3.1. Сеансовые компоненты EJB с состоянием Сеансовые компоненты EJB с состоянием сохраняют информацию о состоянии между вызовами бизнес-методов. Например, сеансовый EJB-компонент с состоянием хранит информацию о содержимом магазинной тележки покупателя, пока покупатель делает покупки в режиме онлайн. Сеансовые компоненты EJB с состоянием предоставляют бизнес-методы для добавлении и удаления товаров из магазинной тележки. Каждый раз, когда покупатель добавляет товар в магазинную тележку, информация о товаре, такая как его цена и количество, будут сохранены в сеансовом компоненте EJB с состоянием. Бели покупатель покидает сайт или прекращает сеанс каким-либо другим способом, информация о содержимом магазинной тележки теряется. Интерфейс InterestCalcalator (рис. 6.3) представляет собой удаленный интерфейс для сеансового компонента EJB с состоянием, который вычисляет простой процент. Методы getPrincipal (строки 15-16), set Interest Rate (строки 19-20) и setTerm (строки 23-24) устанавливают значения основной суммы (капитала), процентной ставки и срока хранения, необходимые для расчета простых процентов. Метод getBalance (строка 27) вычисляет общий баланс (остаток на счету) после начисления процентов для заданного срока хранения. Метод getlnterestEarned (строка 30) вычисляет сумму начисленных процентов. Клиенты EJB-компонента InterestCalculator могут вызывать только те методы, которые объявлены в удаленном интерфейсе InterestCalcalator. Контейнер EJB для EJB-компонента InterestCalculator будет создавать класс, который реализует удаленный интерфейс InterestCalcalator, включая методы, объявленные в интерфейсе javax.ejo.EJBObject. При вызове клиентом метода удаленного интерфейса InterestCalcalator контейнер EJB вызывает соответствующий метод в реализации EJB-компонента InterestCalculatorEJB (рис. 6.5). При вызове клиентом метода, объявленного в интерфейсе javax.ejb.EJBObject, контейнер вызывает соответствующий метод класса, сгенерированного контейнером EJB. 1 // InterestCalculator.Java 2 // InterestCalculator - удаленный интерфейс для 3 // EJB-компонента InterestCalculator. 4 package com.deitel.advjhtpl.ejb.session.stateful.ejb; 5 6 // Базовые библиотеки Java 7 import java.rmi.RemoteException; 8 9 // Стандартные расширение Java 10 import javax.ejb.EJBObject; 11 12 public interface InterestCalculator extends EJBObject { 13 14 // задание основной сукны 15 public void setPrincipal( double amount ) 16 throws RemoteException;
312 Глава 6 17 18 // задание процентной ставки 19 public void setlnterestRate( double rate ) 20 throws RemoteExcaption; 21 22 // задание срока хранения в годах 23 public void setTerm{ int years ) 24 throws RemoteException; 25 26 // получение остатка на счету (баланса) 27 public double getBalance0 throws RemoteException; 28 29 // получение суммы начисленных процентов 30 public double getlnterestEarnedt) throws RemoteException; 31) Рис. 6.З. Удаленный интерфейс InterestCalculator для вычисления простого процента Интерфейс InterestCalculatorHome (рис. 6.4) представляет собой собственный интерфейс для EJB-компонента InterestCalculator. Интерфейс InterestCalculatorHome предоставляет метод create (строки 15-16) для создания экземпляров EJB-компонента InterestCalculator. Когда клиент вызывает метод create интерфейса InterestCalculatorHome, контейнер EJB вызывает метод ejbcreate класса InterestCalculatorEJB (рис. 6.5). Собственный интерфейс может объявлять нуль или более методов create. Например, мы можем объявить дополнительный метод create, принимающий параметр типа double, который инициализирует основную сумму (principal), используемую для вычисления простого процента. Класс InterestCalculatorEJB (рис. 6.5) реализует бизнес-методы, объявленные в удаленном интерфейсе InterestCalculator. В строке 12 InterestCalculator реализует интерфейс SessionBean. Это свидетельствует о том, что InterestCalculatorEJB представляет собой сеансовый компонент EJB. В строках 17-19 объявляются переменные, которые хранят состояние компонента EJB между вызовами бизнес-методов EJB-компонента. В информацию о состоянии входит основная сумма principal, величина процентов interestRate и срок хранения term. Метод setPrincipal (строки 22-25) устанавливает основную сумму principal и сохраняет значение в переменной состояния principal. Метод setlnterestRate (строки 28-31) устанавливает переменную состояния interestRate для вычисления процентов. Метод setTerm (строки 34-37) устанавливает срок хранения term, для которого будет начисляться процент. Метод getBalance (строки 40-44) использует формулу а = р (1 + к)" где р — основная сумма (капитал) г — процент годовых (например, 05 для 5%) п — количество лет а — сумма депозита на конец n-го года. для вычисления баланса (т.е. суммы депозита). Метод getlnterestEarned (строки 47-50) вычисляет сумму процентов, вычитая основную сумму principal из балансовой суммы, вычисляемой методом getBalance. 1// InterestCalculatorHome.Java 2 // InterestCalculatorHome - собственный интерфейс для 3 // EJB-компонента InterestCalculator. 4 package com.deitel.advjhtpl.ejb.session.stateful.ejb; 5
Сеансовые компоненты EJB и распределенные транзакции 313 6 // Базовые библиотеки Java 7 import 5ava.rmi.RemoteException; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public interface InterestCalculatorHome extends EJBHome { 13 14 // создание EJB-компокента InterestCalculator 15 public InterestCalculator created throws RemoteEKC^ption, 16 CreateException; 17 } Рис. 6.4. Собственный интерфейс InterestCalculatorHome для создания EJB-компонентов InterestCalculator 1 // InterestCalculatorEJB.Java 2 // InterestCalculator - сеансовый компонент EJB с 3 // состоянием для вычисления простого процента. 4 package com.deitel.advjhtpl.ejb.session.stateful.ejb; 5 6 // Базовые библиотеки Java 7 import java.util.*; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public class InterestCalculatorEJB Implements SessionBean { 13 14 private SessionContext sessionContext; 15 16 // переменные состояния 17 private double principal; 18 private double interestRate; 19 private int term; 20 21 // задание основной суммы 22 public void setPrincipal( double amount ) 23 24 25 26 27 // задание процентной ставки 28 public void setlnterestRate( double rate ) 29 30 31 32 33 // задание срока хранения в годах 34 public void setTerm( int years ) 35 36 37 38 39 // получение баланса спета principal = amount; interestRate = rate; term = years;
314 Глава 6 40 public double getBalance{) 41 { 42 // вычисление простых процентов 43 return principal * Math.powf 1.0 + interestRate, term ); 44 } 45 46 // получение суммы начисленных процентов 47 public double getIntereetEarned() 4B { 49 return getBalance() - principal; 50 } 51 52 // задание контекста SessionContext 53 public void setSeasionContext( SessionContext context ) 54 { 55 SessionContext = context; 56 } 57 58 // создание экземпляра InterestCalculator 59 public void e]bCreate() {) 60 61 // удаление экземпляра InterestCalculator 62 public void ejbRemove() {) 63 64 // пассивация экземпляра InterestCalculator 65 public void ejbPassivate() {} 66 67 // активация экземпляра InterestCalculator 68 public void ejbActivate(> {} 69 j Рис. 6.5. Реализация InterestCalculatorEJB удаленного интерфейса InterestCalculator Метод set SessionContext (строки 53-56) представляет собой метод обратного вызова, определенный в интерфейсе SessionBean. Контейнер EJB вызывает метод setSessionContext после создания экземпляра компонента EJB. Интерфейс SessionContext расширяет интерфейс EJBContext, который определяет методы для получения информации о контейнере EJB. Типичная ошибка программирования 6.1 Возврат ссылки как указателя this из метода или передача ссылки методу через указатель this для EJB-компонента не допускается. Для получения ссылки на текущий объект следует использовать метод getEJB- Object интерфейса SessionContext или EntityContext. Когда клиент вызывает метод create в собственном интерфейсе, контейнер EJB вызывает метод ejbCreate (строка 59). Реализация EJB-компонента должна предоставить метод ejbCreate для каждого метода create, объявленного в собственном интерфейсе. Методы ejbCreate должны иметь такое же количество и такие же типы параметров, что и соответствующие методы create. Методы ejbCreate также не должны ничего возвращать (void). Например, если интерфейс InterestCalcu- latorHome определяет метод create, который принимает параметр типа double для суммы principal, реализация EJB должна определять метод ejbCreate, который принимает параметр типа double. EJB-компонент InterestCalculator имеет пустую реализацию метода ejbCreate, поскольку для этого EJB-компонента никакой инициализации не требуется.
Сеансовые компоненты EJB и распределенные транзакции 315 Контейнер EJB вызывает метод ejbRemove (строка 62) в ответ на вызов метода remove в собственном интерфейсе. Контейнер EJB также может вызывать метод ejbRemove, если сеанс прекращает свое существование по причине длительного бездействия. Метод ejbRemove должен освобождать ресурсы, используемые EJB-компонентом. Контейнер EJB вызывает метод ejbPassivate (строка 65), если контейнер определяет, что EJB-компонент больше не нужно хранить в памяти. Алгоритм, который контейнер EJB использует, чтобы определить, когда следует пассивировать компонент EJB, зависит от особенностей конкретного сервера приложений. Многие серверы приложений применяют политику замещения компонента с наиболее давним использованием (least recently used policy )t в соответствии с которой пассивируются EJB-компоненты, к которым за последнее время не было обращений. Когда контейнер EJB пассивирует EJB-компонент, контейнер сериализует состояние EJB-компонента и удаляет EJB-компонент из памяти. Контейнер EJB вызывает метод ejbActivate (строка 68) для восстановления экземпляра EJB-компонента, который контейнер ранее пассивировал. Контейнер EJB активирует экземпляр EJB-компонента, если клиент, ассоциированный с этим экземпляром EJB-компонента, вызывает его бизнес-метод. Контейнер EJB прочитывает информацию о состоянии, которую он сохранил во время пассивации, и восстанавливает экземпляр EJB-компонента в памяти. Общая методическая рекомендация 6.1 Используйте ключевое слово transient, чтобы отмстить переменные экземпляра, которые контейнер EJB не должен сохранять и восстанавливать при активации и пассивации. InterestCaleulatorClient (рис. 6,6) — приложение, которое использует EJB-компонент IntcrestCalculator для вычисления простого процента. В строке 25 объявляется ссылка на компонент IntcrestCalculator, который приложение InterestCaleulatorClient использует для вычисления простого процента. Метод createlnterestCalculator (строки 71-108) создает экземпляр EJB-компонента InterestCalculator, который будет использоваться в приложении. Клиентское приложение должно использовать каталог JNDI для поиска собственного интерфейса для EJB-компонента. В строке 77 создается интерфейс InitialContext, который представляет собой интерфейс в каталоге JNDI. Интерфейс InitialContext предоставляет контекст идентификации, который устанавливает отношения между именами (например, «InterestCalculator») и объектами, такими как EJB-компо- ненты. В строках 80-81 используется метод lookup класса InitialContext для извлечения удаленной ссылки типа Object на интерфейс IntcrestCalculator. Параметр типа String, передаваемый методу lookup, представляет собой имя, которое выделено для EJB-компонента в каталоге JNDI. 1 // InterestCaleulatorClient.java 2 // InterestCaleulatorClient - GUI для взаимодействия с 3 // EJB-компокентои IntcrestCalculator. 4 package com.deitel.advjhtpl.ejb.session.stateful.client; 5 6 // Базовые библиотеки Java 7 import java.awt.*; 8 import java.awt.event.*; 9 iinport j ava. rmi. * ; 10 import j ava.text.*; 11 import java.util.*; 12 13 // Стандартные расширения Java
Глава 14 import javax.sving.*; 15 import javax. nni.*; 16 import iavax.naming.*; 17 import javax.ejb.*; 18 19 // Библиотеки Deitel 20 import com.dei tel.advjhtpl.ejb.session.etateful.ejb.*; 21 22 public class ItiterestCalculatorClient extends JFrame { 23 24 // удаленная ссылка InterestCalculator 25 private InterestCalculator calculator; 26 27 private JTextField principalTextField; 28 private JTextField rateTextField; 29 private JTextField termTextField; 30 private JTextField balanceTextField,- 31 private JTextField interestEarnedTextField; 32 33 // конструктор InterestCalculatorClient 34 public InterestCalculatorClient{) 35 { 36 super{ "Stateful Session EJB Example" }; 37 38 // создание объекта InterestCalculator для вычисления процентов 39 createlnterestCalculator(); 40 41 // создание поля JTextField для ввода основной суммы 42 createPcincipalTextFieldO; 43 44 // создание поля JTextField для ввода процентной ставки 45 createRateTextField[); 46 47 // создание поля JTextField для ввода срока хранения 48 createTennTextFieldO ; 49 50 // создание нерадахтируемых полей JTextField•для 51 // отображения баланса и начисленных процентов 52 balanceTextField = new JTextField{ 10 ); 53 balanceTextField.setEditable( false ); 54 55 interestEarnedTextField = new JTextField( 10 ); 56 interestEarnedTextField.setEditablet false ); 57 58 I) размещение компонентов GUI 59 layoutGOIO; 60 61 // добавление слушателя WindowListener для удаления 62 // экземпляре^ EJB, когда пользователь -Закрывает окно 63 addHindowListener( getWindowListener() ); 64 65 setSize( 425, 200 J; 66 setvisible( true ); 67 68 } // конец конструктора interestCalculatorClient
Сеансовые компоненты EJB и распределенные транзакции 317 70 // создание] экземпляра InterestCalculator 71 public void creat.elnterestCalcula.tor () 72 { 73 Ii поиск интерфейса. interestCalculatorHome и создание 74 // BJB-компонента InterestCalculator 75 try \ 76 77 InitialContext initialContext = new InitialСontext(); 78 79 // поиск EJB-компонента InterestCalculator 80 Object homeObject = 81 initialContext.lookup( "InterestCalculator" ); 82 83 // получение интерфейса InterestCalculatorHome 84 InterestCalculatorHome calculatorHome = 85 ( InterestCalculatorHome ) 86 PortableRemoteObject.narrow( homeObject, 87 InterestCalculatorHome.class ); 88 89 // создание экземпляра EJB-компокента InterestCalculator 90 calculator = calculatorHome.create(); 91 92 } // конец оператора try 93 94 // обработка исключения, если ЕОВ~компонент IntarestCalculator не найден 95 catch ( NamingException namingException ) { 96 namingException.printStacxTrace(); 97 } 98 99 // обработка исключения при создании EJB-жомпонента InterestCalculator 100 catch { RemoteException remoteException ) { 101 remoteException.printStackTrace(); 102 } 103 104 // обработка исключения при создании EJB-компонента Intere stCalculator 105 catch ( CreateException createException ) { 106 createException.printstackTraca(); 107 } 108 } // конец метода createinterestCalculator 109 110 // создание поля JTextField для ввода основной суммы 111 public void createFrincipalTextFieldO 112 { 113 principalTextField = new JTextField( 10 }; 114 115 principalTextField.addActionListener( 116 new ActionListener{) { 117 118 public void actionPerformed( ActionBvent event ) 119 { 120 // задание основной суммы в InterestCalculator 121 try { 122 double principal = Double.parseDouble(
318 Глава 6 123 principalTextField.getText() ); 124 125 calculator.setPrincipal( principal ); 126 } 127 128 // обработка исключения яри задании основной суммы 129 catch ( RemoteException remoteException ) { 130 remoteException.printStackTrace(); 131 } 132 133 // обработка при неверном формате суммы 134 catch ( HumberForm&tException 135 nuraberFormatExeeption ) { 136 miiuberFormatException.printStackTrace () ; 137 } 138 } 139 ) 140 ); // конец метода addActionListener 141 } // коней метода cxeatePrincipalTextField 142 143 // создание поля JTextField для ввода процентной ставки 144 public void createRateTextField() 145 { 146 rateTextField = пен JTextField{ 10 }; 147 148 rateTextField.addActionListener( 149 new Actionalstener() { 150 151 public void actionPerformed( ActionEvent event } 152 { 153 // задание процентной ставки в InterestCalculator 154 try { 155 double rate = Double.parseDouble( 156 rateTextField.getText() ); 157 158 // преобразование формата в процентный 159 calculator.setlnterestRate( rate / 100.0 ); 160 > 161 162 // обработка исключения при задании процентной ставки 163 catch ( RemoteException remoteException ) { 164 remoteException.printStackTrace(); 165 } 166 ) 167 } 168' ) ; // конец метода addActionListener 169 } // конец метода createRateTextField 170 171 // создание поля JTextField для ввода срока хранения 172 public void createTermTextField0 173 { 174 termTextField = new JTextField( 10 ); 175 176 termTextField.addActionListener[ 177 new ActionListener() { 178
Сеансовые компоненты EJB и распределенные транзакции 319 179 public void actionFerformed( ActionEvent event ) 1B0 { 181 // задание срока хранения в InterestCalculator 182 try { 1S3 int term = Integer.parselnt( 184 termTextField.getTextO ); 185 186 calculator,setTenn( term 1; 187 } 188 189 // обработка исключения при задании срока хранения 190 catch ( RemoteException remoteException ) { 191 remoteException.printStackTrace(); 192 } 193 } 194 } 195 ); // конец addActionListener 196 } // конец метода getTermTextField 197 198 // получение кнопки JButton для запуска вычислений 199 public JButton getCalculateButtonO 200 ( 201 JButton calculateButton = new JButton( "Calculate" ); 202 203 calculateButton.addActionListener( 204 new ActionListener() { 205 206 public void actionFerformed( ActionEvent event ) 207 { 208 // использование InterestCalculator для вычисления процентов 209 try { 210 211 // получение баланса и начисленных процентов 212 double balance = calculator. getBalanceO; 213 double interest = 214 calculator,getinterestEarned(}; 215 216 NumberFormat dollarFormatter = 217 NrnnberFormat.getCurrencylnstance( 218 Locale.US ); 219 220 balanceTextField.setText( 221 dollarFormatter.format( balance ) ); 222 223 interestEarnedTextField.setText( 224 dollarFormatter.format< interest ) ); 225 } 226 227 // обработка исключения при вычислении процентов 228 catch ( RemoteException remoteException ) { 229 remoteException.prii\t£tacfcTrace(>; 230 } 231 } // конец метода actionPerformed 232 } 233 ) ; // конец addActionListener
320 Глава 6 234 235 return calculateButton; 236 237 ) // конец метода getCalculateButton 238 239 // размещение компонетов GUI в окне JFrame 240 public void. layoutGUI () 241 { 242 Container contentPane = getContentPane(); 243 244 // размещение компонентов пользовательского интерфейса 245 JPanel inputPanel a new JPanel[ new GridLayout( 5, 2 ) ); 246 247 inputPanel.add( new JLabel( "Principal" ) ); 248 inputPanel.add( principalTextField ); 249 250 inputPanel.add( new JLabel( "Interest Bate (%)" ) ); 251 inputPanel.add( rateTextField ); 252 253 inputPanel.add( new JLabel( "Term (years)" > ); 254 inputPanel.add( ten&TextField ); 255 256 inputPanel.add( new JLabel( "Balance" ) ); 257 inputPanel,add( balancaTextField ); 258 259 inputPanel.add( new JLabel( "Interest Earned" ) ); 260 inputPane.add{ interestEarnedTextField ); 1 261 262 // добавление панели inputPanel в панель contentPane 263 contentPane.add( inputPanel, BorderLayout.CENTER ),- 264 265 // создание панели JPanel для кнопки calculateButton 266 JPanel controlPanel = new JPanel( new FlowLayoutO ); 267 controlPanel.add( g/etCalculateButton () }: 268 contentPane.add( controlPanel, BorderLayout.SOOTH ); 269 } 270 271 // получение слушателя WindowListener для выхода Из приложения 272 public WindowListener gotWindowListener{) 273 { 274 return new WindowAdapter() ( 275 276 public void windowClosing( WindowEvent event ) 277 { 278 // проверка, не есть ли значение null 279 if ( calculator.equals( null ) ) { 280 System.exit( -1 ); 281 ) 282 283 else { 284 // удаление экземпляра InterestCalculator 285 try { 286 calculator.remove(); 287 } 28S
Сеансовые компоненты EJB и распределенные транзакции 321 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 > } // обработка исключения при удалении InterestCalculatox catch { RemoveException removeException ) { removeExceptiort.printStackTrace (> ; System.exit( -1 ); } // обработка исключения при удалении InterestCalculator catch ( RemoteExceptlon remoteException ) { remoteExeeption.printStaclcTrace () ; System.exit( -1 ); } System,exit( 0 ); }; > // конец метода getWindowListener // выполнение приложения public static void main( String[] arga ) { new InterestCalculatorClient(); Рис. 6.6. Приложение InterestCalculatorClient для взаимодействия с LIB-компонентом InterestCakulator В строках 84-87 используется метод narrow класса PortabteRemateObjeet для преобразования удаленной ссылки в удаленную ссылку типа InterestCalculatorHome. Это стандартный метод для приведения удаленной ссылки к надлежащему типу интерфейса при использовании технологии RMI — ПОР. RMI — ПОР дает возможность объектам RMI взаимодействовать с компонентами CORBA, которые обмениваются информацией через протокол Internet Inter-Orb Protocol (ПОР). CORBA представляет собой независимую от языка программирования инфраструктуру для построения распределенных систем. Чтобы обеспечить взаимную совместимость между EJB-компонентами и компонентами CORBA, компоненты EJB используют технологию RMI — ПОР.1 Технологии ССЖВА и KMI — ПОР рассматриваются в книге «Технологии программирования на Java 2. Книга 2*. — Прим. ред.
322 Глава 6 В строке 90 вызывается метод create интерфейса InterestCalculatorHome для создания нового экземпляра EJB-компонента InterestCalculator. Этот экземпляр EJB-компонента InterestCalculator является эксклюзивным для клиента, который его создал, поскольку он представляет собой сеансовый компонент EJB. Экземпляр EJB-компонента InterestCalculator будет хранить информацию о состоянии, поскольку он является сеансовым компонентом EJB с состоянием. Метод create возвращает удаленную ссылку на вновь созданный экземпляр EJB-компонента InterestCalculator. В строке 90 эта удаленная ссылка присваивается переменной экземпляра calculator. Экземпляры класса InterestCalculatorCHent могут использовать эту удаленную ссылку для вызова бизнес-метода, определенного в удаленном интерфейсе InterestCalculator. В строках 95-97 перехватывается исключение NamingException, которое указывает на наличие проблемы при доступе к каталогу JNDI. Если контекст InitialContext не может быть создан, конструктор InitialContext возбуждает исключение NamingException. Метод lookup также возбуждает исключение Na- mingException, если имя, передаваемое в качестве параметра, не может быть найдена в каталоге JNDI. В строках 100—102 перехватывается исключение RemoteException. Если а процессе коммуникационного взаимодействия с контейнером EJB возникает ошибка, метод create возбуждает исключение RemoteException. В строках 105-107 перехватывается исключение CreateException. Метод create возбуждает исключение CreateException, если имеет место ошибка при создании EJB-компонента. Метод createPrincipalTextField (строки 111-141) создает текстовое поле JTextField для ввода пользователем основной суммы principal. Анонимный внутренний класс ActionListener (строки 116-139) использует статический метод parseDouble класса Double для получения величины principal, введенной пользователем (строки 122-123). В строке 125 устанавливается величина principal в EJB-компоненте InterestCalculator путем вызова метод setPrincipal. Заметим, что объекты JTextField генерируют события ActionEvent, когда пользователь нажимает клавишу Enter в поле JTextField. Следовательно, пользователь должен нажимать кнопку Enter после ввода каждого фрагмента данных, чтобы принудить метод actionPerformed интерфейса ActionListener вызывать соответствующий бизнес-метод интерфейса InterestCalculator. Метод createRateTextField (строки 144-169) создает текстовое поле JTextField для ввода процентной ставки. В строках 149-167 создается анонимный внутренний класс ActionListener для синтаксического анализа данных, введенных пользователем (строки 155-156), и вызывается метод setlnterestRate интерфейса InterestCalculator для установки процентной ставки (строка 159). Метод createTennText Field (строки 172-196) создает текстовое поле JTextField для ввода срока хранения (б годах) вклада, для которого будет вычисляться процент. Анонимный внутренний класс ActionListener (строки 177-194) осуществляет синтаксический анализ введенных пользователем данных (строки 183-184) и устанавливает срок хранения в объекте InterestCalculator, вызывая метод setTerm (строка 186) удаленного интерфейса InterestCalculator. Метод getCalcnlatorButton (строки 99-237) создают кнопку JButton, которая при щелчке на ней мышью вызывает методы getBalance и getlnterestEarned интерфейса InterestCalculator. В строках 220-224 устанавливается текст в полях balanceTextField и interestEarnedTextField для отображения вычисленных результатов пользователю. Конструктор InterestCalculatorCHent (строки 34-68) вызывает метод createln- terestCalculator для создания нового экземпляра EJB-компонента InterestCalculator (строка 39). В строках 42-56 создаются компоненты пользовательского интерфейса приложения, и вызывается метод layoutGUI (строка 59), который созда-
Сеансовые компоненты EJB и распределенные транзакции 323 ет надпись JLabel для каждого текстового поля JTextField и осуществляет размещение компонентов (строки 240-269). Метод getWindowListener (строки 272-305) создает анонимный внутренний класс window Adapter, который предоставляет метод windowClosing для выполнения очистки, когда пользователь закрывает окно приложения. В строке 279 проверяется, не равно ли null значение calculator. Если значение есть null, программа прекращает работу и сообщает; код ошибки (строка 280). В противно» случае в строке 286 вызывается метод remove удаленного интерфейса IsterestCalcnlator для удаления экземпляра EJB-компонента, который используется приложением. Если при удалении экземпляра компонента EJB возникает ошибка, в строках 290-293 перехватывается исключение RemoteException. Если имеются ошибка при коммуникационном взаимодействии с контейнером EJB, в строках 296-299 перехватывается исключение RemoteException. 6.3.2. Развертывание сеансовых компонентов EJB Компоненты Enterprise JavaBeans исполняются в контексте контейнера EJB, который является главной составляющей сервера приложений, совместимого с J2EE. В этом разделе подробно описываются действия, необходимые для развертывания сеансового EJB-компонента InterestCalcuiator на эталонной реализации Java 2 Enterprise Edition версии 1.2.1 от Sun Microsystems. Если вы еще не установили и не настроили пакет J2EE SDK, обратитесь к инструкциям по установке. Создайте новое распределенное приложение, выбрав New Application из меню File (рис. 6.7). Задайте имя для EAR-файла и имя приложения в диалоговом окне и щелкните на ОК (рис. 6.8). Рис. 6.7. Создание нового приложения с помощью средства развертывания Application Deployment Tool
324 Глава 6 Рис. 6.8. Задание EAR-файла для нового приложения Выберите New Enterprise Bean из меню File, чтобы начать развертывание EJB-компонеета (рис. 6.9). Щелкните на Next, укажите имя JAR Display Name и щелкните на Add, чтобы добавить файлы классов для EJB-компонента InterestCalculator (рис. 6.10). Рис, 6.9. Создание нового компонента Enterprise Bean Задайте корень RootDirectory для классов, которые относятся к EJB-компоненту InterestCalculator (рис. 6.11). Классы для InterestCalculator содержатся в пакете com.deitel.advjhtpl.ejb.session.stateful.ejb, поэтому выберите в качестве корня каталог, который содержит каталог com. Выберите файлы классов для Interest- Calculator — InterestCalcnlator.class, InterestCalculatorHoine.class и Interest- CalculatorEJB.class — и щелкните на Add, а затем на ОК. После добавления файлов классов они появятся в списке Contents в окне EJB JAR (рис. 6.12). Щелкните на Next, чтобы начать добавление файлов классов в JAR-архив EJB-компонента. Задайте класс Enterprise Bean Class, собственный интерфейс Home Interface и удаленный интерфейс Remote Interface, выбрав соответствующие классы из раскрывающихся списков (рис. 6.13). Задайте имя Enterprise Bean Display Name, которое будет отображаться в инструментальном средстве развертывания Application Deployment Tool, и выберите тип Bean Туре. EJB-компонент InterestCalculator представляет собой сеансовый компонент с состоянием, поэтому выберите опции Session и Stateful и щелкните на кнопке Next (рис. 6.14). При развертывании компонента MathTooIEJB укажите Stateless (с состоянием) вместо Stateful (без состояния) для типа компонента Bean Type.
Сеансовые компоненты EJB и распределенные транзакции |'<8аа. f -щах'"]. ''p-'frfo':':]-; Рис. 6.10. Добавление классов для EJ В- компонента InterestCaiculator f Easistelkit •-□client f Oeib ош«$ Q inlersstCalcul aim. lava Q InterestCaleulalorEJB.isva 0 rffiasWattliattrtiqrTie.ciass' 0 nlarsstcaltuiamrHome iava ffilinitabisAd^Kl; Г—^—1Ы ■ 44, &■■■ '-;'"!'.*it:""*«yff^* ИИНИИЧ Рис. 6.11. Выбор классов для EJB-компонента InterestCaiculator, подлежащих добавлению
326 Глава 6 Рис. 6.12. Результат добзвлрния классов дпя компонрнта FJB InterestCalculator „ци^.м.'м iftei атэд & >ч* s* js&tv vviim siifajgm ■■ наг гл jae yi y'lmnifc м р?гУ:-^ЭДЮщ^Щ| Рис. 6.13. Задание класса Enterprise Bean Class для LIB-компонента InterestCalculator Установите режим контейнерного управления транзакциями Container-Managed Transactions в диалоговом окне Transaction Management (рис. 6.15). Для каждого метода Method задайте Supports в качестве типа транзакции и щелкните на кнопке Next. Подробнее о транзакциях будет рассказано в разделе 6.4.
Сеансовые компоненты EJB и распределенные транзакции 327 Рис. 6.14. Задание классов для EJB-компонента InterestCalculator и типа Stateful (с состоянием) для сеансового (Session) компонента Рис. 6.15. Задание опции контейнерного управления транзакциями Container Managed Transactions для EJB-компонента InterestCalculator На рис. 6.16 представлен XML-код дескриптора развертывания, сформированного для EJB-компонента InterestCalculator. Сервер приложений использует XML-дескриптор для настройки EJB-компонента InterestCalculator.
328 Глава б ч?вг|| »ersioF^"1.0" eraodieips'Gpi 251"'- i 'OOCTtPE врЬ-iarPuauc '-WSun Mitjosrsiems. mc aotd '[JeBcriptori'nodescrlBllafKftf^siTipbyii- if-name»BuSjness. Lfrgic «Jdisp3ay-n вте> ««rtefprfca-beans* ^iBtsEDrt- **ispi3t-namt*irte*P8tCik«lalor*ffltspiey-namc> «ejb-лап-Е»inta rasJC aicuMor «("sib- n*me> «noms^Dm deM.auvjhlpI. ejB.fi*es;on elateRJ.ejtJ.lnteretlC BituiaWiHome^tiDmE » <fBnwte*ccirn de№t e*|htpi .Effi^es5iofi.staiafui.f|o.i«irestCaBuii»r-4™nriD«> *BjD-riiss*com tieiTfl.atfqrireT ijo session statefu! е^Ь lnlsr»fC»ltul9toTbJB^e5ti-[iass> * б** story йч»*Б1а1еай tree ss icft-type > «bsnsiciioA'toe' coniain&TMlrsni*;!] on-typ e > *t0 нЩг№-ЩП вдЯпп» ■* ejOname " IntetesSC ak uralBr*Jepb-neme » *melfiQ* inflf* Serrate «rmalnod-inlf» «methofl- n*me>se^rincipal *(rna!riBfl-n.3 me» «mettico-perim** m>dCLihle </твВпм1-р: Рис. 6.16. XML-дескриптор развертывания для EJB-компонента InterestCakulator Вы должны задать имя JNDI Name для компонента InterestCalculator, чтобы клиенты могли получать ссылки на EJB-компоненты. Для данного примера укажите InterestCalculator в качестве имени JNDI Name (рис. 6.17). Имя JNDI Name не обязательно должно совпадать с именем EJB-компонента. Рис. 6.17. Задание имени JNDI Name для LIB-компонента InterestCalculator Хороший стиль программирования 6.1 Используйте имя удаленного интерфейса EJB в качестве имени JNDI для EJB-компонента. Это сделает ваш. код более удобным для чтения и позволит легче запомнить имя JNDI. Выполните развертывание приложения на сервере J2EE, выбрав пункт Deploy Application из меню Tools, либо щелкнув на кнопке Deploy Application в панели инструментов (рис. 6.18).
Сеансовые компоненты EJB и распределенные транзакции 329 Рис. 6.18. Развертывание корпорэтир! юга приложения на локальном компьютере localhost Укажите localhost в качестве целевого сервера Target Server и установите флажок Return Client Jar (рис. 6.19). JAR-файл клиента содержит классы-заглушки, которые клиент будет использовать для взаимодействия с Е JB-компонентом. Рис. 6.19. Задание возвращаемого клиенту JAR-фэйла Return Client Jar в средстве развертывания Application Deployment Tool Осуществив развертывание EJB-компонента InterestCalculator (рис. 6.20) выполните приложение InterestCalculatorClient для тестирования компонента InterestCalculator. При выполнении приложения IntcrestCalcuIatorClient в ката-
330 Глава 6 логе, задаваемом переменной CLASSPATH, должны содержаться файлы setCalculater,Client.jar и j2ee.jar. Например, в командной строке введите Java -classpath Dr\j2sdkee,2.l\lib\j2ee.jar,D:\Inteirest- CalculatorClient.jar;. Com.deitel.advjhtpl.ejb.session.stateful. client.InterestCalculatorClient Рис. 6.20. Успешное завершение процесса развертывания 6.3.3. Сеансовые компоненты EJB без состояния Сеансовые компоненты EJB без состояния не сохраняют информацию о состоянии между вызовами бизнес-методов. Как результат, контейнер EJB может использовать любой экземпляр EJB-компонента без состояния для ответа на запрос любого клиента. —^ Совет по повышению эффективности 6.1 " т^^ Сеансовые компоненты EJB без состояния могут выполняться более эффективно, чем сеансовые компоненты EJB с состоянием, поскольку овин экземпляр сеансового EJB без состояния может совместно использоваться многими клиентами, что сокращает потребности в памяти, операциях процессора и других ресурсах сервера. В листинге на рис. 6.21 описывается удаленный интерфейс MathTool. MathTool представляет собой сеансовый компонент EJB без состояния, определяющий бизнес-методы для генерирования ряда Фибоначчи и вычисления факториалов. Метод getFibonacciSeries (строки 14-15) генерирует ряд Фибоначчи, длина которого задается целочисленным параметром howMany. Метод getFactorial (строки 18-19) вычисляет факториал заданного целого числа. 1 // MathTool.java 2 // MathTool - удаленный интерфейс для EJB-компонента MathTool. 3 package com.deitel.advjhtpl.ejb.session.stateless.ejb; 4 5 // Базовые библиотеки Java
Сеансовые компоненты EJB и распределенные транзакции 331 6 import java.rmi.ReraoteException; 7 8 // Стандартные расширения Java 9 import javax.ejb.EJBObject; 10 11 public interface MathTool extends EJBObject 12 { 13 // получение ряда Фибоначчи 14 public int[] getFibonacciSeries( int howMany ) 15 throws RemoteException, IllegalArguraentException; 16 17 // получение факториала заданного целого числа 18 public int getFactorial( int number ) 19 throws RemoteException, IllegalArgumentExcaption; 20 } Рис. 6.21. Удаленный интерфейс MathTool для вычисления факториалов и генерирования ряда Фибоначчи Компонент MathToolEJB (рис. 6.22) предоставляет реализации бизнес-методов, объявленных в удаленном интерфейсе MathTool. MathToolEJB реализует интерфейс SessionBean (строка 9), указывая, что MathTool является сеансовым EJB-компонентом. Метод getFibonacciSeries (строки 14-48) генерирует ряд Фибоначчи. Ряд Фибоначчи 0, 1, 1, 2, 3, 5, 8, 13, 21 ... начинается с О и 1 и имеет то свойство, что каждое последующее число Фибоначчи является суммой предыдущих двух чисел Фибоначчи. EJB-компонент MathTool вычисляет ряд Фибоначчи в строках 25—44. Каждое число в ряду помещается в целочисленный массив series (строка 39). В строках 32-33 число О устанавливается в качестве нулевого числа в ряду, а число 1 устанавливается в качестве первого числа в ряду. В строке 39 в качестве значения следующего числа в ряду устанавливается сумму предыдущих двух чисел. 1 // MathToolEJB.Java 2 // MathToolEJB - сеансовый компонент EJB беа состояния с 3 // методами для вычисления ряда Фибоначчи и факториала. A package com.deitel.advjhtpl.ejb,session.stateless.ejb; 5 6 // Стандартные расширения Java 7 import javax.ejb.*; 8 9 public class MathToolEJB implements SessionBean { 10 11 private SessionContext sessionContext; 12 13 // получение ряда Фибоначчи 14 public int[] getFibonacciSeries( int howMany ) 15 throws IllegalArgumentException 16 { 17 // возбуждение исключения IllegalArgumentException, 18 // если длина ряда меньше двух 19 if ( howMany < 2 ) 20 throw new IllegalArgumentException{ 21 "Cannot generate Fibonacci series of " + 22 "length less than two." );
332 Глава 6 23 24 // начальные значения 25 int startPointl = 0; 26 int startPoint2 = 1; 27 28 // массив для хранения ряда Фибоначчи 29 int[] series = new int[ howMany ]; 30 31 // задание базовых случаев 32 series[ 0 ] = 0; 33 series[ 1 ] = 1; 34 35 // формирование ряда Фибоначчи 36 for ( int i = 2; i < howMany; i++ ) { 37 38 // вычисление следующего числа в ряду 39 series[ i ] = startPointl + startPoin±2; 40 41 // задание начальных условий для следужщей итерации 42 startPointl = startPoint2; 43 startPoint2 = series[ i ]; 44 } 45 46 return series; 47 48 } // конец метода getFibonacciSeries 49 50 // получение факториала заданного целого числа 51 public int getFactorial( int number ) 52 throws IllegalArgumentException 53 { 54 // возбуждение исключения IllegalArgumentException, если число меньше 0 55 if ( number < О ) 56 throw new IllegalArgumentException( 57 "Cannot calculate factorial of negative numbers." ); 58 59 // базовый случай рекурсии, возврат 1 60 if ( number = 0 ) 61 return 1,- 62 63 // рекурсивный вызов getFactorial для вычисления факториала 64 else 65 return number * getFactorial( number - 1 ); 66 67 } // конец метода getFactorial 68 69 // задание контекста SesaionContext 70 public void setSessionContext( SessionContext content * 71 { 72 sessionContext = context; 73 } 74 75 // создание нового экземпляра MathTool 76 public void e]bCreate() {} 77
Сеансовые компоненты EJB и распределенные транзакции 333 78 // удаление экземпляра. MathTool 79 public void ejbRemove() {} 80 81 // активация экземпляра MathTool 82 public void ejbActivateО {} 83 84 // пассивации экземпляра MathTool 85 public void ejbPassivate() {} 86 } Рис. 6.22. Реализация MathToolEJB удале-шого интерфейса MathTool Метод get Factorial (строки 51-67) вычисляет факториал неотрицательного целого числа, который по определению представляет собой произведение ■ п - (л-1) ■ (п-2) • ... * 1 В строках 56-57 возбуждается исключение IllegalArgumentException, если целочисленный параметр number меньше 0. В строках 60—61 реализуется базовый класс для рекурсивного вычисления. В строке 65 вычисляется факториал путем рекурсивных вызовов метода getFactorial. Контейнер EJB вызывает метод setSessionContext (строки 70-73), когда активирован экземпляр EJB-компонента, и клиенту предоставлен удаленный интерфейс. Параметр SessionContext реализует метод getEJBObject, который EJB-kom- понент может использовать для извлечения ссылки на текущий экземпляр EJB-компонента. При вызове клиентом метода create интерфейса MathToolHome (рис. 6.23) контейнер EJB вызывает метод ejbCreate (строка 76) Метод ejbCreate выполняет инициализацию EJB-компонента. Метод ejbCreate интерфейса MathToolEJB имеет пустую реализацию, поскольку этот EJB-компонент не требует инициализации. Контейнер EJB вызывает метод ejbRemove (строка 79) для удаления экземпляра EJB-компонента MathTool. Контейнер EJB вызывает метод ejbActivate (строка 82), когда экземпляр EJB-компонента извлекается из пула EJB-контейнера и ассоциируется с конкретным клиентом. Контейнер EJB вызывает метод ejbPassivate (строка 85), когда экземпляр EJB-компонента больше не нужен и может быть возвращен в пул готовности. Интерфейс MathToolHome (рис. 6.23) представляет собой собственный интерфейс для EJB-компонента MathTool. Метод create (строки 15-16) создает новый EJB-компонент MathTool. Контейнер EJB вызывает метод ejbCreate интерфейса MathToolEJB, когда клиент вызывает метод create интерфейса MathToolHome. Инструкции по развертыванию вы можете найти в разделе 6.3.2. Напомним, что MathTool является сеансовым EJB-кОмпонентом без состояния, поэтому вы должны указать на зто в процессе развертывания. Не забудьте также указать соответствующее имя JNDI (например, MathTool) для EJB-компонента MathTool. 1 // MathToolHome.j ava 2 // MathToolHome - собственный интерфейс для EJB-компонента MathTool. 3 package com.deitel.advjhtpl.ejb.session.stateless.ejb; 4 5 // Базовые библиотеки Java 6 import Java.rmi.RemoteExсерtion; 7 8 // Стандартные расширения Java 9 import javax.ejb.EJBHome; 10 import javax.eib.CreateException; 11
334 Глава 6 12 public interface MathToolHome extends EJBHome { 13 14 // создание нового БJB-компонента MathTool 15 public MathTool create<) throws RemoteException, 16 CreateException ,- 17) Рис 6.23. Интерфейс MathToolHome для создания EJB-компонентов MathTool Mat hToolC lien t (рис. 6.24) представляет собой клиентское приложение для EJB-компонента MathTool. Пользовательский интерфейс состоит из текстового поля JTextField, в которое пользователь может вводить целое число, кнопки J Butt on для вызова метода getFactorial интерфейса MathTool и кнопки JBntton для вызова метода getFibonacciSeries интерфейса MathTool. Результаты вычислений отображаются в текстовой области JTextArea. Конструктор MathTool Client (строки 29-45) вызывает метод createMathTool (строка 35), чтобы создать новый экземпляр EJB-компонента MathTool. В строке 38 вызывается метод createGUI для создания и размещения компонентов GUI пользовательского интерфейса приложения. Метод createMathTool (строки 48-83) использует класс InitialContext (строка 53) для поиска интерфейса MathToolHome в каталоге JNDI (строки 56-57). В строке 65 вызывается метод create интерфейса MathToolHome для создания экземпляра EJB-компонента MathTool. Метод getFactorialBntton (строки 86-122) создает кнопку JButton, которая при нажатии на ней вызывает метод getFactorial EJB-компонента MathTool. В строках 100-104 осуществляется синтаксический анализ числа, введенного в поле numberTextField, и вызывается метод getFactorial удаленного интерфейса MathTool. В строках 107-108 в текстовой области resultsTextArea отображается значение факториала. В строках 113-115 перехватывается исключение Remote- Exception, если имеет место ошибка при вызове метода getFactorial. Метод getFibonacciButton (строки 125-182) создает кнопку JBntton, при нажатии которой аызывается метод getFibonacciSeries EJB-компонента MathTool. В строках 140—145 осуществляется синтаксический анализ числа, введенного в поле numberTextField, и вызывается метод getFibonacciSeries удаленного интерфейса MathTool. Метод getFibonacciSeries возвращает массив целых чисел, содержащий ряд Фибоначчи, длина которого задается целочисленным параметром howMany. В строках 148-168 осуществляется формирование буфера StringBufCer, содержащего ряд Фибоначчи, и отображение ряда в текстовой области resultsTextArea. 1 // MathToolClient.Java 2 If MathToolClient - GUI для вычисления факториалов и 3 // рядов Фибоначии с помощь» EJB-компоаента MathTool, 4 package com.deitel.advjntpl.ejb.session.stateless.client; 5 6 // Базовые библиотеки Java 7 import j ava.awt.*; 8 import Java.awt.event.*; 9 import java.rmi.*; 10 11 // Стандартные расширения Java 12 import javax.swing,*; 13 import javax.rmi.*; 14 import javax.naming.*; 15 import javax.e jb.*; 16
Сеансовые компоненты EJB и распределенные транзакции 335 17 // Библиотеки Deitel 18 inport com.deitel.advjhtpl.ejb.session.stateless.ejb.*; 19 20 public class HathToolClient extends JFrame { 21 22 private HathToolHome mathToolHome; 23 private HathTool mathTool; 24 25 private JTextftrea resultsTextArea; 26 private JTextEield numberTextEield; 27 28 // конструктор MathToolClient 29 public MathToolClient() 30 { 31 super( "Stateless Session EJB Example" ); 32 33 // создание объекта MathTool для вычисления 34 // факториалов и рядов Фибоначчи 35 createHathToolO ; 36 37 // создание и размещение компонентов GUI 36 createGUIO; 39 40 addffindowListener( getWindowListener() ); 41 42 setSizef 425, 200 ); 43 setVisible( true ); 44 45 > // конец конструктора MathToolClient 46 47 // создание экземпляра EJB-компонентз MathTool 4в private void createMathTool() 49 { 50 // поиск интерфейса MathToolHome к создание EJB-комповента MathTool 51 try { 52 53 InitialContext initialContext = new lnitialContext<); 54 55 // поиск EJB-компонента MathTool 56 Object homeObject = 57 initialContext.lookup( "MathTool" ),- 58 59 // получение интерфейса MathToolHome €0 mathToolHome = < MathToolHome ) 61 PortableRemoteObject.narrow! homeObject, 62 HathToolHome.class ); 63 64 // создание экземпляра EJB-компонента HathTool 65 mathTool = mathToolHome.create(); 66 67 ) // конец оператора try 68 69 // обработка исключения, если EJB-хомпонект MathTool не найден 70 catch ( NamingException namingException ) { 71 namingException.printStackTrace(); 72 }
336 Глава 6 73 74 // обработка исключения при создании ЕОВ-хомпонента MathTool 75 catch ( RemoteException remoteException ) { 7-6 remoteException. printStaclcTrace ( System. ere ) ; 77 } 78 79 //обработка исключения при создании EJB-хонпокента MathTool 80 catch ( CreateException createException ) { 81 createException.printstackTrace( System.ere ); 82 } S3 } // конец метода createMathTool 84 85 // создание кнопки JButton для вычисления факториала 86 private JButton getFactorialButton() 87 { B8 JButton factorialButton = 89 new JButton( "Calculate Factorial" ) ,- 90 91 // добавление слушаетеля ActionListener для кнопки вычисления факториала 92 factorialButton.addActionListener( 93 new ActionListener() { 94 95 public void actionPerformad( ActionEvent event ) 96 { 97 // использование EJB-компонента MathTool для вычисления факториала 98 try I 99 100 int number = Integer.parseInt( 101 munberTextField.getText() ): 102 103 // получение факториала целого числа, введенного пользователей 204 int result = mathTool.getFactorial( number ); 105 106 // отображение результатов в области resultsTextArea 107 resultsTextArea.setTfext( number +"!=-+ 108 result } ; 109 110 \ I f конец оператора try 111 112 // обработка исключения при вычислении факториала 113 catch ( RemoteException remoteException ) ( 114 remoteException.printStackTraco(); 115 J 116 } // конец метода actionPerformed 117 } 118 }; // конец слушателя addActionListener 119 120 return factorialButton; 121 122 } // конец метода getFactorialButton 123 124 // создание кнопки JButton для генерирования ряда Фибоначчи 125 private JButton getEibonacciButton()
Сеансовые компоненты EJB и распределенные транзакции 337 126 { 127 JButton fibonacciBufcton = 128 new JButton( "Fibonacci Series" ); 129 130 // добавление слушателя ActionListener для генерирования ряда Фибоначчи 131 fibonacciButton.addActionListener( 132 new ActionListener0 { 133 134 public void actionPerformed( ActionEvent event ) 135 { 136 // генерирование ряда Фибоначчи с помощью EJB-компонента MathTool 137 try { 138 139 // получение числа, введеннного пользователем 140 int number = Integer.parselnt( 141 numberTextField.getText() ); 142 143 // получение ряда Фибоначчи 144 int[] series = piathTool.getFibonacciSeries( 145 number ); 146 147 // создание буфера StringBufirer для хранения ряда 148 StringBuffer buffer = 149 new StringBuffer( "The first " ); 150 151 buffer.append{ number ); 152 153 buf fer. append ( " Fibonacci number (a): \n". }; 154 155 // добавление каждого из чисел ряда в буфер 156 for ( int i = 0; i < series.length; i++ ) ( 157 158 // не добавлять запятую перед первым числом 159 if { i != О ) 160 buffer.append( ", " ); 161 162 // добавление следующего чисела ряда в буфер 163 buffer.append( String.valueOff 164 series[i] ) ) ; 165 } 166 167 // отображение ряда в области resultsTextArea 168 resultsTextArea.setText( buffer.toStringO ); 169 170 } // конец оператора try 171 172 // обработка исключения при вычислении ряда 173 catch ( RemoteException гелю teException ) { 174 remoteException.printStackTrace(); 175 } 176 } // конец метода actionPerformed 177 } 178 ); // конец слушателя addActionListener 179 180 return fibonacciButton;
338 Глава 6 181 182 } // конец метода getFibonacciButton 183 184 // создание макета размещения компонентов GUI 185 public void createGOIO 186 { 187 // создание области JTextArea дли отображения результатов 1S6 resultsTextArea = new JTextArea О; 189 reaultsTextArea.setLineWrap{ true ); 190 reaultsTextArea.setWrapStyleWoxd< true ); 191 resultsTextArea,setEditable( false ); 192 193 // создание поля JTextField для пользовательского ввода 194 nuiriberTextField = new JTextField( 10 ); 195 196 // создание кнопки JButton для вычисления факториала 197 JButton factorialButton = getFactorialButton(); 198 199 // создание кнопки JButton для генерирования ряда Фибоначчи 200 JButton fibonacciButton = getFibonaceiButton(); 201 202 Container contentPane = getContentPane(); 203 204 // помещение объекта resultsTextArea в контейнер JSerollPane 205 JSerollPane resultsSerollPane = 206 new JSerollPane( resultsTextArea ); 207 208 contentFane.add( resultsScrollPane, 209 BorderLayout.CENTER ); 210 211 // добавление элементов ввода в новую панель JPanel 212 JPanel inputPanel = new JPanel( new FlowLayoutO ); 213 inputPanel.add( new JLabel( "Enter an integer: " ) ); 214 inputPanel.add( nuniberTextField ); 215 216 // добавление коыпонетов JButton в новую панель JPanel 217 JPanel buttonPanel = new JPanel( new FlowLayoutO ) ." 218 buttonPanel.add( factorialButfcon ); 219 buttonPanel.add( fibonacciButton ); 220 221 // добавление панели inputPanel и панели buttonPanel в новую панель JPanel 222 JPanel controlPanel = 223 new JPanel( new GridLayout( 2, 2 ) ) ; 224 225 controlPanel.addt inputPanel ); 226 controlPanel.add( buttonPanel ); 227 228 contentPane.add{ controlPanel, BorderLayout.NORTH ); 229 230 } // конец метода createGUI 231 232 // получение слушателя WindowListener для выхода иэ приложения 233 private WindowListener getWindowListener{) 234 { 235 return new WindowAdapter<) { 236
Сеансовые компоненты EJB и распределенные транзакции 339 237 23S 233 240 241 242 243 244 245 246 247 24В 249 250 251 252 253 254 255 256 257 258 259 260 261 262 2 63 264 265 266 } public void windowClosirtg{ WindowEvent event ) { II удаление экземпляра MathTool try { mathTool.remove(); } // обработка исключения при удалений EJB-компонента MathTool catch ( RemoveException. removeException ) { removeException.printStackTrace(); System.exit( ~1 ); ) // обработка исключения при удалении EJB-компонента MathTool catch ( KemoteException remoteException ) { remoteException.printstackTrace(); System.exit( -1 ); > System.exit( 0 ); } // конец метода windovCLosing }, } // конец метода getWindowListener // выполнение приложения public static void main( String[] args ) { MathToolClient client, ■ new MathToolClientO |7!=S04<I ЩШ pifi first 7 Fibonacci numbEr(s)' B, l.i.2, 3.5. t Рис. 6.24. Приложение MathToolClient для взаимодействия с EJB-компонентом MathTool
340 Глава 6 6.4. EJB-транзакции 8 Java 2 Enterprise Edition имеется поддержка распределенных транзакций. Распределенная транзакция — это транзакция, которая имеет дело с несколькими базами данных или с несколькими серверами приложений. Например, распределенная транзакция может использоваться для атомарной передачи капитала со счета в одном банке на счет в другом банке. J2EE поддерживает два метода для определения границ действия транзакции: разграничение с управлением, на стороне компонента и разграничение с управлением на стороне контейнера. Разграничение с управлением на стороне компонента требует, чтобы разработчик компонента EJB вручную писал код для границ транзакций в EJB-компонентах с помощью средств API Java Transaction (JTA). Разграничение с управлением и а стороне контейнера дает возможность разработчику EJB-компонента декларативно задавать границы транзакции при развертывании EJB-компонента. Общая методическая рекомендация 6.2 Компоненты-сущности EJB могут использовать только разграничение транзакций, управляемое контейнером. 6.4.1. Собственный и удаленный интерфейс EJB MoneyTransfer EJB-компонент MoneyTransfer демонстрирует актуальность применения распределенных транзакций и представляет их реализацию с использованием разграничения, управляемого компонентом, и разграничения, управляемого контейнером. В этом примере мы осуществляем перевод денег со счета в банке ВапкАВС на счет в банке BankXYZ. Сначала мы снимаем деньги со счета в банке ВапкАВС, а затем зачисляем их на счет в банке BankXYZ. Транзакции необходимы, чтобы гарантировать, что деньги будут возвращены на счет ВапкАВС, если перевод денег на счет BankXYZ окончится неудачей. Нам также нужно гарантировать, что если снятие денег со счета ВапкАВС закончится неудачей, деньги не будут зачислены на счет BankXYZ. Удаленный интерфейс MoneyTransfer (рис. 6.25) предоставляет методы для перевода денег с одного счета на другой и для получения остатков по счетам в двух различных банках. Метод transfer (строка 15) переводит заданную сумму со счета в банке ВапкАВС на счет в банке BankXYZ. Метод getBaokABCBalance (строка 18) возвращает баланс (остаток) счета в йанке ВапкАВС. Метод getBankXYZRalance (строка 21) возвращает баланс счета в банке BankXYZ. Интерфейс MoneyTrans- ferHome (рис. 6.26) предоставляет метод create (строки 15-16) для создания экземпляров EJB-компонента MoneyTransfer. 1 // MoneyTransfer.java 2 // MoneyTransfer - удаленный интерфейс для EJB-компонента 3 // MoneyTransfer, 4 package com.daitel.advjhtpl.ejb.transactions; 5 6 // Набор базовых библиотек Java 7 import Java.rmi .RemoteException; 3 9 // Стандартные расширения Java 10 import iavax.ejb.EJBObject; 11 12 public interface MoneyTransfer extends EJBObject { 13
Сеансовые компоненты EJB и распределенные транзакции 341 14 // перевод суммы из банка BankABC в банк BankXYZ 15 public void transfer( double amount ) throws RemoteException; 16 17 // получение баланса счета в банке BankABC IB public double getBankABCBalance() throws RemoteException; 19 20 // получение баланса счеса в банке BankXYZ 21 public double getBankXYZBalance() throws RemoteException; 22 ) Рис. 6.25. Удаленный интерфейс MoneyTransfer для перевода денег и получения балансов по счетам 1 // MoneyTransferHome.java 2 // MoneyTransferHome - собственный интерфейс для 3 // EJB-компонента MoneyTransferHome. 4 package com.deitel.advjhtpl.ejb.transactions; 5 6 // Базовые библиотеки Java 7 import Java.rmi.RemoteException; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public interface MoneyTransferHome extends EJBHome { 13 14 // создание EJB-хомпонента MoneyTransfer 15 public MoneyTransfer create О throws RemoteException, 16 CreateException; 17} ____ Рис. 6.26. Интерфейс MoneyTransferHome для создания EJB-компонентов MoneyTransfer 6.4.2. Разграничение транзакций с управлением на стороне компонента Разграничение транзакций с управлением на стороны компонента требует, чтобы разработчик EJB-компонента вручную описывал границы действия транзакции в коде EJB-компонента. Разграничение транзакций с управлением на стороне компонента может быть использовано только для сеансовых EJB-компонентов. Компонент MoneyTransferEJB (рис. 6.27) реализует удаленный интерфейс MoneyTransfer с использованием разграничения транзакций, управляемого компонентом, чтобы обеспечить атомарность обновлений баз данных в методе transfer (строки 26-81). В строках 29-30 создается транзакция Us erTrans action. В строке 34 транзакция transaction начинается с вызова метода begin интерфейса UserTransaction. Все операторы, следующие после начала транзакции transaction, являются составной частью транзакции до тех пор, пока транзакция не будет завершена или отменена. 1 // MoneyTransferEJB.java 2 // MoneyTransferEJB - сеансовый компонент EJB без состояния для 3 // перевода денег со счета в банке BankABC на счет в банке BankXYZ 4 // с использованием разграничения транзакции, управляемого компонентом. 5 package com. deitel. advjhtpl. ejb. transactions .beanmanaged,-
6 7 // Базовые библиотеки Java 8 import Java. util. * -, 9 import Java. sql. * r- 10 11 // Стандартные расширения Java 12 import javax.ejb-*; 13 import j avax.naming.*; 14 import javax.transaction.*; 15 import 3avax.sql.*; 16 17 public class MoneyTransferEJB implements SessionBean { 18 19 private SessionContext sessionContext; 20 private Connection bankOneConnection; 21 private Connection bankTwoConnection; 22 private Preparedstatement withdrawalStatement; 23 private PreparedStatement depositStafcement,- 24 25 // перевод денег ив банка BankABC в банк BankXYZ 26 public void transfer( double amount ) throws EJBException 27 { 28 // создание транзакции для перевода денег 29 UserTransaetion transaction = 30 sessionContext.getUserTransaction(); 31 32 // качало разграничения транзакции, управляемого компонентом 33 try { 34 transaction.begin(); 35 } 36 37 // перехват исключения, если метод не достигает успеха 38 catch ( Exception exception ) { 39 40 // возбуждение исключения EJBException, указывающего на неудачу транзакции 41 throw new EJBException{ exception ); 42 } 43 44 // перевод денег со счета в банке ВалкАВС на счет в банке 45 // BankXYZ с использованием разграничения транзакции, управляемого компонентом 46 try { 47 48 withdrawalStatement.ееtDoublef l, amount ); 49 50 // снятие денег со счета в банке BankABC 51 withdrawalStatement.executeUpdate(); 52 53 depositstatement.setDouble{ 1, amount ); 54 55 // начисление денет- на счет в банке BankXYZ 56 depositstatement.executeUpdate(); 57 58 // завершение транзакции 59 transaction.commit();
Сеансовые компоненты EJB и распределенные транзакции 343 60 61 } // конец блока try 62 63 // обработка исключений при снятии денет", начислении 64 // девег и завершении транзакции 65 catch ( Exception exception ) { 66 67 // попытка отмени (отката) транзакции 68 try { 69 transaction.rollback(); 70 } 71 72 // обработка исключений при откате транзакции 73 catch ( SystemEXception systemException ) { 74 throw new EJBException{ systeaiException ); 75 } 76 77 // возбуждение исключения EJBException, указывающего на неудачу транзакции 78 throw new EJBException( exception ); 79 } 80 81 } // конец метода transfer 82 83 // получение баланса счета в банке BankABC 84 public double getBankABCBalanceО throws EJBException 85 { 86 // получение баланса счета в банке BankABC 87 try { 88 89 // выбор номера счета # 12345 для получения баланса 90 String select = "SELECT balance FROM Account " + 91 "WHERE acoountlD = 12345"; 92 93 PreparedStatement selectStatement = 94 bankOneConnection.prepareStatenient( select ); 95 96 ResultSet resultSet = selectStatement.executeQuery(); 97 98 // получение первой записи в ResultSet и вовврат баланса 99 if ( resultSet.next() ) 100 return resultSet.getOouble( "balance" ); 101 else 102 throw new EJBException( "Account not found" ); 103 104 } // конец блока try 105 106 // обработка исключения пря получении баланса счета 107 catch ( SQLException sqlException ) { 108 throw new EJBException( sqlException ); 109 > 110 111 } // конец метода getBankABCBalanee 112 113 // получение баланса счета в банке BankXYZ 114 public double getBankXYZBalance{) throws EJBException
344 Глава 6 115 { 116 // получение баланса счета в банке BankXYZ 117 try { 118 119 // выбор номера счета # 54321 для получения баланса 120 String select = "SELECT balance FROM Account " + 121 "WHERE accountID = 54321"; 122 123 PreparedStatement selectStatement = 124 bankTwoConnection.preparestatement{ select ); 125 126 ResultSet resultSet = selectStatement.executeQuery(); 127 128 // получение первой записи в ResultSet и возврат баланса 129 if ( resultSet.next() ) 130 return resultSet.getOouble( "balance" ); 131 else 132 throw new EJBException( "Account not found" ); 133 134 } // конец блока try 135 136 // обработка исключения при получении баланса счета 137 oatch ( SQLException sqlException ) { 138 throw new EJBException( sqlException ); 139 J 140 141 } // конец метода getBankXYZBalance 142 143 // задание контекста SessionContext 144 public void SetSessionContext( SessionContext context ) 145 throws EJBException 146 { 147 SessionContext = context; 148 149 openDatabaseResources () ; 150 ) 151 152 // создание экземпляра компонента MoneyTransfer 153 public void ejbCreste() {} 154 155 // удаление экземпляра компонента MoneyTransfer 156 public void ejbRemove() throws EJBException 157 [ 158 closeDatabaseResources() ; 159 > 160 161 // пассивация экземпляра компонента MoneyTransfer 162 publio void ejbPaesivate() throws EJBException 163 ( 164 closeDatabaseResources() ; 165 ) 166 167 // активация экземпляра компонента MoneyTransfer 168 public void ejbActivate() throws EJBException 169 { 170 openDatabaseResources О .■'
Сеансовые компоненты EJB и распределенные транзакции 345 L71 } 172 173 // закрытие соединений с бааой данных и подготовленных операторов 174 private void closeDatabaseResources() throws EJBException 175 i 176 // закрытие ресурсов базы данных 177 try { 17В 179 // закрытие подготовленных операторов 180 depositStatement.close(); 181 depositStateroent = null; 182 1B3 withdrawalStatement.close(); 184 withdrawalStatement = null; 1B5 186 // закрытие соединений с базой данных 187 bankOneConnection.close(); 188 bankOneConnection = null; 189 190 bankTwoConnection.close(); 191 bankTwoConnection = null; 192 1 ' 193 194 // обработка исключения при закрытии соединений с базой данных 195 catch { SQLException sqlException ) { 196 throw new EJBExceptioft( sqlException ); 197 ) 19B 199 } // конец метода closeDatabaseConnections 200 201 // открытие соединений с базой данных и создание подготовленных операторой 202 private void openDatabaseResources() throws EJBException 203 { 204 // поиск источников данных для BankABC и BankXYZ и 205 // соединений с ними 206 try { 207 Context initialContext = new initialContext (J ; 208 209 // получение ссылки на источник данных из каталога JTIDI 210 OataSource dataSource = ( DataSource ) 211 initialContext.lookup( 212 "java:co>np/env/jdbc/BankABC" ) ; 213 214 // получение соединения Connection из источника данных DataSource 215 bankOneConnection = dataSource.getConnection(); 216 217 dataSource = ( DataSource) initialContext.lookup( 218 "java:comp/env/jdbc/BankXYZ" ); 219 220 bankTwoConnection = dataSource.getConnection(); 221 222 // подготовка оператора снятия денег со счета #12345 а 223 // банке BankABC
346 Глава 6 224 String withdrawal = "UPDATE Account SET balance = " + 225 "balance - ? WHERE accountID = 12345"; 226 227 withdrawalStatement = 228 bankOneConnection.prepareStatement( withdrawal ); 229 230 // подготовка оператора зачисления денег на счет 231 // #54321 в банке BankXYZ 232 String deposit = "UPDATE Account SET balance = " + 233 "balance + ? WHERE accountID = 54321"; 234 235 depositStatement = 236 bankTwoConnection.prepareStatement( deposit ); 237 238 } // коней блока try 239 240 // обработка исключения, если источник данных не найден в каталоге 241 catch ( NamingException namingException ) ( 242 throw new EJBException( namingException ); 243 } 244 245 // обработка исключения при получении соедиения с источником даяиих 246 catch { SQLException sqlException ) { 247 throw new EJBException( sqlException ); 248 } 249 250 } // конец метода openOatabaseConnections 251 } Рис. 6.27. Реализация MoneyTransferEIB удаленного интерфейса MoneyTransfer с использованием разграничения транзакции с управлением на стороне компонента В строках 4.8—51 осуществляется снятие заданной суммы amount со счета в базе данных ВапкЛВС. В строках 53-56 осуществляется зачисление заданной сумму amount на счет в базе данных BankXYZ. Оба эти обновления являются частью транзакции transaction, начатой в строке 34, несмотря на то, что они применяются к различным базам данных. В строке 59 осуществляется завершение транзакции transaction для сохранения обновлений каждой из баз данных. В строках 65-79 перехватываются исключения Exception, возбуждаемые в строках 46-61. В строке 69 вызывается метод rollback интерфейса UserTraits- action. для отмены любых обновлений, которые имели место внутри границ транзакции. Метод rollback обеспечивает, что если какая-либо важная часть метода transfer оканчивается неудачей, все изменения, сделанные в обеих базах данных, отменяются, и гарантируется целостность данных. В строках 73-75 перехватывается исключение SystemException, которое возбуждается методом rollback интерфейса JJsezTraBsactios, если выполнение метода rollback оканчивается неудачей. В строках 74 и 78 возбуждаются исключения EJBException, которые облегчают отладку приложения. Методы getBankABCBalance (строки 84-111) и getBankXYZBalance (строки 114-141) выполняют простые операторы SQL SELECT для извлечения балансов по счетам в каждом из банков. Методы set Session Con text (строки 144-150) и ejbActivate (строки 168-171) вызывают метод openDatabascResource (строки 202-250) для создания соединений (Connection) и подготовленных операторов
Сеансовые компоненты EJB и распределенные транзакции 347 (PreparedStatement) для каждой из баз данных, которые будут использоваться в течение срока существования экземпляра EJB-компонента MoneyTransfer. Методы ejbRemove (строки 156-159) и ejbPassivate (строки 162-165) вызывают метод closeDatabaseResource (строки 174-199) для закрытия соединений и подготовленных операторов. 6.4.3. Разграничение транзакций с управлением на стороне контейнера Разграничение транзакций с управлением на стороне контейнера дает возможность разработчику EJB-компонента реализовывать EJB без указания границ действия транзакции. Разработчик EJB-компонента декларативно предоставляет семантику разграничения транзакций при развертывании приложения. Компонент MoneyTransferEJB (рис. 6.28) реализует удаленный интерфейс MoneyTransfer с использованием разграничения транзакции, управляемого контейнером. Метод transfer (строки 25-51) схож с методом transfer компонента, представленного на рис. 6.27. Однако обратите внимание, что эта версия метода transfer не объявляет каких-иибо границ транзакции, предоставляя сделать зто администратору развертывания EJB-компонента. Администратор развертывания компонента EJB задает границы транзакции, устанавливая один из шести типов транзакций, приведенных в таблице на рис. 6.29. 1 // MoneyTrajisferEJB,java 2 // MoneyTransferEJB - сеансовый кохпонент EJB без состояния для 3 // перевода, денег со счета в банке ВапУ&ВС на счет в банке BankXXZ с 4 // использованием разграничения транзакции, управляемого контейнером. 5 package com.deitel.advjhtpl.ejb.transactions.containermanaged; 6 1 i/ Базовые библиотеки Java 8 import java.util.*; 9 import java.sql.*; 10 11 // Стандартные расширения Java 12 import javax.ejb.*; 13 import javax.naming.*; 14 import j avax.sql.*; 15 16 public class MoneyTransferEJB implements SessionBean { 17 1Й private SassionContext sessionContext; 19 private Connection bankOneConnection; 20 private Connection bankTwoConnection; 21 private PreparedStatement *itlidxawalStatement; 22 private PreparedStatement depositStatement; 23 24 // перевод денег- из банка BankABC в банк BankXYZ 25 public void transfer( double amount ) throws EJBException 26 I 27 // перевод денег со счета в банке BankABC на счет в банке 28 // B^nJtXYZ с использованием рааграиичевдя тгранаахции под управлением контейнера 29 try { 30 31 withdrawalstatement.setDouble( 1, amount ); 32 33 // снятие денег со счета в банке Вап*АВС
348 Глава Б 34 withdrawalstatement.executeUpdate<); 35 36 depositStatement.setDouble( 1, amount ); 37 3B // помещение денег на счет в банке BankXYZ 39 depoaitStatement.oxecutoOpdatc(); 40 41 i // кошен блока try 42 43 // обработка исключения при снятии и зачислении денег на счет 44 catch { SQLException sqlException ) { 45 46 // Возбуждение исключения EJBException, указыважщего на 47 // неудачу перевода. Откат транзакции с контейнерным управлением 48 throw new EJBException( sqlException ); 49 } 50 51 } // конец метода transfer 52 53 // получение баланса счета в банке ВаякАВС 54 public double getBankABCBalance() throws EJBException 55 ( 56 // получение баланса счета в банке ВапкАВС 57 try { 58 59 // выбор счета # 12345 для получения баланса 60 String select = "SELECT balance FROM Account " + 61 "WHERE accountID = 12345"; 62 63 PreparedStatement selectstatement = 64 bankOneConnection.prep&reStatement( select ); 65 66 ResuitSet resultSet = selectstatement.executeQuery{); 67 6B // получение первой записи в ResultSet и возврат баланса 69 if ( resultSet.next() ) 70 return resultSet.getDouble( "balance" ); 71 else 72 throw new EJBException( "Account not found" ); 73 74 J // конец блока try 75 76 // обработка исключения при получении баланса счета 77 catch ( SQLException sqlException ) { 78 throw new EJBException( sqlException ); 79 } 80 81 } // конец метода getBankABCBalance 82 83 // получение баланса счета в банке BankXYZ 84 public double getBankX¥2Balance() throws EJBException 85 { 86 // получение баланса счета в банке BankXYZ 87 try { 88 89 // выбор счета # 54321 для получения баланса 90 String select = "SELECT balance ИЮМ Account " +■
Сеансовые компоненты EJB и распределенные транзакции 349 91 "WHEBE accountlD = S4321"; 92 93 PreparedStatement selectStatement = 94 banJcTwoConnection.prepareStatement( select ); 95 96 ResultSet resultSet = selectStatement.executeQuery(); 97 98 // получение первой записи в ResultSet и возврат баланса 99 if ( resultSet.nextО ) 100 return resultSet.getDouble( "balance" ); 101 else 102 throw new EJBException( "Account not found" ); 103 104 } // конец блока try 105 106 // обработка исключения при получении баланса счета 107 catch ( SQLException sqlExcepfcion ) { 108 throw new EJBException( sqlException ); 109 } 110 111 } // конец метода getBankXYZBalance 112 113 // задание контекста SessionContext 114 public void setSessionContext( SessionContext context ) 115 throws EJBException 116 { 117 SessionContext = context; 118 119 openDatabaseResourcesО; 120 } 121 122 // создание экземпляра компонента MoneyTransfer 123 public void ejbCraateО {} 124 125 // удаление экземпляра компонента MoneyTransfer 126 public void ejbRemove(} throws EJBException 127 { 128 closeDatabaseResourcesO; 129 } 130 131 // пассивация экземпляра компонента MoneyTransfer 132 public void ejbPassivate0 throws EJBException 133 ( 134 closeDatabaseResourcesO; 135 } 136 137 // активация экземпляра компонента MoneyTransfer 138 public void ejbActivate() throws EJBException 139 { 140 openDatabaseResources 0; 141 } 142 143 // Закрытие соединений с базой данных и подготовленных операторов 144 private void closeDatabaseResourcesO throws EJBException 145 { 146 // закрытие ресурсов базы данных 147 try { 148
Глава б 149 // закрытие подготовленных операторов 150 depositstatement.close() ; 151 depositstatement = null; 152 153 withdrawalStatement.close(); 154 withdrawalStatement = null ,- 155 156 // закрытие соединений с базой данных 157 bankOneConnection.close(); 158 bankOneConnection = null; 159 160 bankTwoConnection.closet); 161 bankTwoConnection = null; 162 } 163 164 // обработка исключения при закрытии соединений с базой данных 165 catch < SQLExoeption sqlSxdsption } ( 166 throw new EJBException( sqlException ); 167 } 168 169 } // конец метода closeDatabaeeConnectiona 170 171 // открытие соединений с базой данных и подготовленных операторов 172 private void openDatabaseResources0 throws EJBException 173 { 174 // поиск источников данных для BankABC и BankXYZ и 175 // создание соединений с ними 17 6 try { 177 Context initialContext = new InitialContext(); 178 179 // получение ссылки на источник данных из каталога JNDI 180 DataSourсе dataSource = { DataSource ) 181 initialContext.lookup{ 182 "java:comp/env/jdbc/BankABC" ); 183 184 // получение соединения от источника данных 185 bankOneConnection = dataSource.getConnectiont); 186 187 dataSource = ( DataSource) initialContext.lookup( 188 "java:conip/env/;}dbc/BankXYZ" ); 189 190 bankTwoConnection = dataSource.getConnectionO: 191 192 // подготовка оператора снятия денег со счета 193 // #12345 в банке BankABC 194 String withdrawal = "UPDATE Account SET balance = " + 195 "balance - ? WHERE accountID = 12345"; 196 197 withdrawalStatement = 196 bankOneConnection.prepareStatement{ withdrawal ); 199 200 // подготовка оператора зачисления денег на очах 201 // #54321 в банке BankXYZ 202 String deposit = "UPDATE Account SET balance = " + 203 "balance + ? WHERE accountID = 54321"; гол 205 depositstatement = 206 bankTwoConnection.preparestatement( deposit );
Сеансовые компоненты EJB и распределенные транзакции 351 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 } } // конец блока try // обработка исключения, если источник данных не найден в каталоге catch { NamingExcepfcion namingException ) { throw new EJBExceptian ( namingException 'j ; } // обработка исключения при получении соединения с источником данных catch { SQLExceptiort sqlException ) { throw new EJBException( sqlExeeption ); ) } // конец метода openDatabaseConnections Рис, 6.28. Реализация MoneyTransferEJB удаленного интерфейса Money!ransfer, использующая разграничение транзакций с управлением на стороне контейнера В строке 66 возбуждается исключение EJBException в ответ на любое исключение SQLException, возбужденное в строках 29-41. Контейнер EJBContainer осуществляет откат текущей транзакции transaction., если метод transfer возбуждает исключение EJBException (строка 48). В таблице на рис. 6.29 приведен перечень существующих типов транзакций, применяемых при контейнерном управлении разграничением. Администратор развертывания задает тип транзакции для каждого бизнес-метода при развертывании приложения. Тип транзакции Описание NotSupported Метод не поддерживает транзакции. Контейнер EJB приостанавливает контекст существующей транзакции, если метод вызывается внутри контекста транзакции. Required | Метод требует применения транзакции. Контейнер EJB создает новую ! транзакцию, если метод вызывается без контекста существующей | транзакции, и завершает транзакцию в конце выполнения метода. Supports Метод поддерживает транзакции. Контейнер EJB не будэт создаватэ новую транзакцию, если метод вызывается без контекста 1 существующей транзакции, но будет выполнять метод как часть 1 существующей транзакций, если таковая имеется. RequiresNew Метод требует новой транзакции. Контейнер EJB приостанавливает контекст существующей транзакции и начинает новую транзакцию, 1 если метод вызывается как часть Другой транзакции. Mandatory Never Метод должен выполняться в контексте существующей транзакции. Контейнер EJB возбуждает исключение TransactionRequiredException, если метод вызывается без действительного контекста транзакции. Метод не должен выполняться в контексте транзакции. Контейнер EJB возбуждает исключение RemoteExceptron. если метод вызывается внутри контекста транзакции. J Рис. 6,29. Типы транзакций для разграничения транзакций с упразлением на стороне контейнера
352 Глава б 6.4.4. Клиентский EJB-компонент MoneyTransfer Приложение MoneyTransferEJBClient (рис. 6.30) предоставляет пользовательский интерфейс для взаимодействия с EJB-компонентом MoneyTransfer. В строках 24-26 объявляются объекты JTextField для отображения балансов счетов и приема от пользователя перечисляемой суммы transfer amount. В строке 34 вызывается метод createMoneyTransfer для создания нового EJB-компонента MoneyTransfer. В строке 37 вызывается метод createGUI для создания и размещения компонентов графического интерфейса пользователя (GUI) для приложения. GUI состоит из текстовых полей JTextField для отображения балансов счетов и ввода переводимой суммы, а также кнопки JButton для осуществления перевода. В строке 40 вызывается метод display Balances для отображения текущих балансов счетов в банках ВапкАВС и BankXYZ. Метод createMoneyTransfer (строки 47-80) использует интерфейс MoneyTrans- ferHome для создания экземпляра EJB-компонента MoneyTransfer. В строке 51 создается объект InitialContext для нахождения EJB-компонента MoneyTransfer в каталоге JNDI. В строках 54—59 создается метод lookup класса InitialContext для получения удаленной ссылки на интерфейс MoneyTransfer. В строке 62 создается новый экземпляр EJB-компонента MoneyTransfer путем вызова метода create интерфейса MoneyTransferHome. Метод getTransferButton (строки 108-142) создает кнопку JButton для перевода средств со счета в банке ВапкАВС на счет в банке BankXYZ. В строках 120-124 осуществляется чтение переводимой сумму amount, введенной пользователем, и вызывается метод transfer интерфейса MoneyTransfer. В строке 127 вызывается метод displayRalances для обновления содержимого экрана новыми значениями балансов счетов. На рис. 6.30 показаны три копии содержимого экрана для приложения MoneyTransferEJBClient. Введите любое допустимое значение в текстовое поле и нажмите кнопку Transfer. EJB-компонент должен обновить записи в базах данных, 1 // MoneyTransferEJBClient.java 2 // MoneyTransferEJBClient ~ клиент для взаимодействия с 3 // EJB-компонентом MoneyTransfer. 4 package com.deitel.advjhtpl.ejb.transactions.client; 5 6 // Бааовые библиотеки Java 7 import 3 ava.awt.*; 8 import j ava.awt.event.*; 9 import java.rmi.*,- 10 11 // Стандартные расширения Java 12 import j avax.swing.*; 13 import javax.ejb.*; 14 import javax.rmi.*; 15 import javax.naming. *; 16 17 // Библиотеки Deitel 18 import com. deitel. advjhtpl. ejb. transactions. *,- 19 20 public class MoneyTransferEJBClient extends JFrame { 21 22 private MoneyTransfer moneyTransfer; 23 24 private JTextField banXABCBalanceTextField; 25 private JTextField bankXYZBalanceTextField;
Сеансовые компоненты EJB и распределенные транзакции 353 26 private JTextField transierAmountTextField; 27 28 // конструктор MoneyTransferEJBClient 29 public MoneyTransferEJBClient( String JttDIName ) 30 { 31 super( "MoneyTransferEJBClient" ); 32 33 // создание EJB-компонента MoneyTransfer для перевода денег 34 createMoneyTransfer ( JNDINanse ) ; 35 36 // создание и размещение компонентов GUI 37 createGUIO ; 38 39 // отображение текущих балансов счетов в банках BankABC и BankXYZ 40 displayBalances() ; 41 42 setsize( 400, 300 ); 43 setVisible( true ); 44 } 45 46 // создание EJB-компонента MoneyTransferEJB для перевода денег 47 private void createMoneyTransfer( String JNDIName ) 48 { 49 // поиск EJB-компонента MoneyTransfее по заданному имени JNDIName 50 try { 51 InitialContext context = new InitialContext {) ; 52 53 // поиск EJB-хомпокеита MoneyTranafar 54 Object homeObject = context.lookup( JNDIName ); 55 56 // получение интерфейса MoneyTransfer 57 MoneyTransferHome moneyTransferHome = 58 ( MoneyTransferHome ) PortableRemoteObject.narrow{ 59 homeObject, MoneyTransferHome.class ); 60 €1 // создание экземпляра EJB-компонента. MathTool 62 moneyTransfer = moneyTransferHome.create(); 63 64 ) // конец блока try 65 66 tf обработка исключения при поиске EJB-компонента MoneyTransfer 67 catch ( NamingException naroingException ) { 68 namingException.printstacXTrace<); 69 ] 70 71 // обработка исключения при поиске EJB-компонента MoneyTransfer 72 catch ( CreateException createException ) t 73 createException.printStacxTraee(}; 74 1 75 76 // обработка исключения при поиске EJB-компонента MoneyTransfer 77 catch ( RemoteException remoteException ) { 78 remoteException.printStackTraceО; 79 } 80 } // конец метода createMoneyTransfer 81 82 // отображение баланса счета в банке BankABC
83 private void displayBalances() 84 { 85 try { 86 87 // получение и отображение баланса счета в банке БапкАВС 85 double balance = moneyTransfer.getBankABCBalance(); 89 90 bankABCBalanceTextField.setText( 91 String.valueOf( balance ) ); 92 93 // получение и отображение баланса счета в банке BankXYZ 94 balance = moneyTransfer.getBankXYZBalance(); 95 96 banJeXYZBalanceTextField.aetText( 97 String.valueOf{ balance ) ); 98 } 99 100 // обработка исключения при вызове методов EJB-компонента MoneyTransfer 101 catch ( RemoteEKception remoteException ) { 102 JOptionPane.showMessageDialog( this, 103 remoteException. getMessageO ) ; 104 } 105 } // конец нетода displayBalances 106 107 // совдание кнопки для перевода денег со счета на счет 105 private JButton getTransferButtonQ 109 { 110 JButton transferButton = new JButton( "Transfer" ); 111 112 transfaxButton.addActiohListener( 113 new ActionListener() ( 114 115 public void actionPerformedf ActionEvent event ) 116 { 117 try ( 118 119 // получение переводимой суммы из содержимого поля JTextField 120 double amount = Double.parseDouble( 121 transferAmountTextField.getTextO ); 122 123 // перевод денег 124 moneyTransfer.transfer( amount ); 125 126 f f отображение новых балансов 127 displayBalances(); 125 ) 129 130 // обработка исключений при переводе денег 131 catch ( RemoteException remoteException ) { 132 JOptionPane.showMessageDialog( 133 MoneyTransferBJBClient.this, 134 remoteException.getMessage() ); 135 } 136 } // конец метода aetionPerformed 137 } 138 ); // конец слушателя addActionListener
Сеансовые компоненты EJB и распределенные транзакции 355 139 140 return transferButton,- 141 142 } // коней, метода getTtransferButton 143 144 // создание и размещение компонентов GUI 145 private void createGUX(} 146 { 147 // создание текстовых полей JTextFields для ввода И отображения 148 banfcABCBalanceTextTield = new JTeKtField( ID ); 149 bankABCBalanceTextField.setEditable( false ) ,- 150 151 bankXYZBalanceTextField = new JTextField{ 10 ); 152 bankXYZBalanceTextField.setEditable{ false ); 153 154 transferAmountTextField = new JTextField( 10 ); 155 156 // создание кнопки для перевода денег со счета на счет 157 JButton transferButton = getTransferButton(); 158 159 // компоновка пользовательского интерфейса 160 Container contentPane = getContentPane{); 161 contentPane.setLayout( new GridLayout( 3, 2 ) ); 162 163 contentPane.add( transferButton ); 164 contentPane.add( transferAmountTextField ); 165 166 contentPane.add( new JLabel( "Bank ABC Balance: " ) ); 167 contentPane.add{ bankABCBalanceTextField ); 168 169 contentPane.add( new JLabel( "Bank XYZ Balance: " ) ); 170 contentPane,add( oankXYZBalatxceTextEield. ) ; 171 172 ) // конец метода createGUT 173 174 // получение слушателя WindowListener для выхода из приложения 175 private WindowListener getWindowListener() 176 I 177 // удаление EJB-коыпонента MoneyTransfer при выходе пользователя из приложении 178 return new WindowAdapter() { 179 180 public void windowClosing{ WindowEvent event ) ( 181 182 II удаление EJB-компонента MoneyTransfer 183 try { 184 moneyTransfer. remove () ; 185 } 186 187 // обработка исключения при удалении EJB-компонента MoneyTransfer 188 catch ( RemoveException removeException ) { 189 removeException.printStacxTraceО; 190 System.exit{ 1 ); 191 } 192 193 // обработка исключения при удалении EJB-коыпонента MoneyTransfer
356 Глава 6 194 195 196 19"? 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 J ь- catch ( RemoteException remoteException ) { гелю teExcep ti on.prin tS tackTrace(>; System.exit( 1 ); ) System.exit( 0 ); } // конец метода windowClosing } // конец метода getWindowListener // выполнение приложения public static void main( String[] args ) { // проверка, что пользователь предоставил имя JNDI для EJB-компонента MoneyTransfer if ( args.length '= 1 ) System.err.println( "Usage: Java MoneyTransferEJBCIient <JNDI Hame>" ); // запуск приложения с использованием предоставленного имени JNDI else new MoneyTransferEJBCIient( args[ 0 ] }; ) tbtfunrn ?&W \i: Ш^-W ■-■■:':■ 'a/ ■ ■■ 4t~i,.■ ■!..'• •:-■'•'. rts». ■ ..■; вяцднД^.п^!-: ■ .. j«uJHfc -ft*,-■■■.■■■■ ■.■-■••■4 '"■',- " '■%..!■- ■". ■•'• •!,. ' ■'■ .*;. i'-.: ■■• i] '->■• ■■■' %%. • •■■■ -■'"■;...n £'#&: C- ■Л.-Л -!■,-,. ; ^ ;;4t>'»*B*«t* .. ■/*/: , J1000.00 tmm.u Ъ^г Si'- *":... ■ ' ■ •*,: ;■■■> ■ <■* S.ji&* i-'. i- . * Рис. 6.30. Приложение MoneyTransferEJBCIient для взаимодействия с EJB-компонентом MoneyTransfer (часть 1)
Сеансовые компоненты EJB и распределенные транзакции 357 Рис. 6.30. Приложение MoneyTransferEJBCIient для взаимодействия с EJ В-компонентом MoneyTransfer (часть 2) 6.4.5. Развертывание EJB-компонента MoneyTransfer EJB-комяонент MoneyTransfer осуществляет доступ к двум базам данных, которые хранят информацию о счете. В примерах используется система управления базами данных Cloudscape, с которой мы познакомились в главе 4. Следуйте инструкциям, приведенным в разделе 4.10.1, чтобы установить и настроить базы данных для EJB-компонента MoneyTransfer. SQL-сценарий transacti.on.sql для баз данных имеется в комплекте примеров к этой главе на сайте компании Deitel www.deitel.com, а также на сайте издательства «Бином» www.binoni-press.ru/ books/adv_java2.htm. Добавьте следующий текст |jdbc/BankABC[jdbc:cloudscape:rrai:BankXYZ;create=true; | jdbc/BankXYZ | jdbc: cloudscape: rmi: BankXYZ,- create=true ; в свойство jdbc.dataSource в файле C:\j2sdkeel-2.1\config\default.properties- Развертывание каждой из версий EJB-компонента MoneyTransfer очень похоже на развертывание других сеансовых компонентов EJB. При развертывании EJB-компонента MoneyTransfer необходимо создать в мастере New Enterprise Bean Wizard (рис. 6.31) ссылки на ресурсы для двух баз данных. Щелкните на кнопке Add, чтобы добавить новую ссылку на ресурс. Заполните поля Coded Name и JNDI Name строками jdbc/BankABC для базы данных BankABC (рис. 6.32) и jdbc\BankXYZ для базы данных BankXYZ (рис. 6.33). В диалоговом окне Transaction Management мастера New Enterprise Bean Wizard выберите Bean- Managed Transaction для версии MoneyTransferEJB с управлением транзакцией на стороне компонента (рис. 6.34) или Container-Managed Transaction для версии MoneyTransferEJB с контейнерным управлением транзакцией (рис. 6.35). Для EJB-компонента с контейнерным управлением выберите тип транзакции для каждого метода (см. раздел 6.4.3, в котором дано описание типов транзакций). В остальном процесс развертывания аналогичен развертыванию других сеансовых EJB-компонвнтов. Перед выполнением MoneyTransferEJBCIient убедитесь, что сервер Cloudscape запущен.
358 Глав Рис. 6.31. Диалоговое окно ссылок на ресурсы Resource References мастера New Enterprise Bean Wizard Рис. 6.32. Добавление ссылки на ресурсы для банка ВапкАВС
Сеансовые компоненты EJB и распределенные транзакции 359 Рис. 6.33. Добавление ссыпки на ресурсы для банка BankXYZ Рис. 6.34. Выбор управления Рис. 6.35. Выбор контейнерного транзакциями на стороне компонента управления транзакциями Bean-Managed Transaction Container-Managed Transaction 6.5. Ресурсы в Internet и во Всемирной паутине Java.sun.com/products/ejb Основная страница сайта корпорации Sun, сосвященного Enterprise Java Bean. Содержит статьи, докумеатвцию и примеры. www,javaworld.com/javaworld/topicalindex/jw-ti-ejb.htanl Список статей, имеющих отношение к технологии EJB. www . jguru. com/f a<j/h(jnie. j зр? topic=S JB Список часто задаваемых вопросов (FAQ) no EJB на сайте jguru.com. www.theserverside.com TheServerside.com — сетевое сообщество разработчиков на J2EE. Здесь вы найдете форумы, статьи и другие ресурсы для построения приложений с помощью J2EE.
360 Глава 6 Резюме • Каждый компонент EJB состоит из удаленного интерфейса, собственного (* домашнего») интерфейса и реализации EJB-компонента. • Удаленный интерфейс объявляет бизнес-методы, которые могут вызывать клиенты EJB-компонента, Собственный интерфейс предоставляет методы create для создании новых экземпляров EJB-компонента, методы finder для нахождения экземпляров EJB-компонента и методы remove для удаления экземпляров EJB-компонента. Реализация EJB-компонента определяет бизнес-методы, объявленные в удаленном интерфейсе, и методы создания (create), удаления (remove) и поиска (finder) собственного интерфейса. • Компоненты EJB имеют сложный жизненный цикл, который управляется контейнером EJB. Контейнер EJB создает классы, которые реализуют собственный и удаленный интерфейсы. • Спецификация J2EE определяет шесть ролей для реализации корпоративных систем. Каждая роль отвечает за формирование определенной части распределенного приложения. • Удаленный интерфейс для компонента EJB объявляет бизнес-методы, которые может вы- зывать клиент EJB-компонента. Удаленный интерфейс должен расширять интерфейс javax.ejb.EJBObject. ■ Каждый метод удаленного интерфейса должен объявлять, что он возбуждает исключение java.rmi.RejnoteException. Каждый метод также может возбуждать специфичные для приложения исключения, например, Illegal Argument Except ion, если предоставленный параметр не отвечает определенному критерию. • Собственный интерфейс для компонента EJB объявляет методы для создания, удаления и нахождения экземпляров EJB-компонента. Собственный интерфейс должен расширять интерфейс javax.ejb.EJBHome. • В зависимости от типа EJB-компонента (т.е. сеансовый компонент или компонент-сущность) контейнер вызывает методы реализации EJB-компонента, которые соответствуют методам создания (create), удаления (remove) и поиска (finder) собственного интерфейса. • Реализация EJB-компонента определяет бизнес-методы, объявленные в удаленном интерфейсе EJB-компонента, и методы создания (create), удаления (remove) и поиска (finder) собственного интерфейса EJB-компокеита. Реализация EJ8-компонента должна также реалнзовыватъ методы интерфейса javax.ejb.SessionBean для сеансовых компонентов EJB или интерфейса javax.ejb.EntityBean для компонентов-сущностей EJB. • Контейнер EJB управляет жизненным циклом, взаимодействиями с клиентами и вызовами методов, транзакциями, безопасностью и исключениями для EJB-компонент а. Клиенты EJB-компонента не взаимодействуют с EJB-компонентом напрямую. При вызове клиентом бизнес-метода удаленного интерфейса EJB-компонента вызов сначала поступает контейнеру EJB, который затем делегирует вызов бизнес-метода реализации EJB-компонента. • Сеансовые компоненты EJB существуют в течение сеанса данного клиента. Каждый экземпляр сеансового компонента EJB ассоциируется с одним клиентом. Сеансовые компоненты EJB могут манипулировать данными в базе данных, но, в отличие от компонентов-сущностей EJB, сеансовые EJB-компоненты не являются сохраняемыми (персистент- ными) и не представляют данные из баз данных напрямую. • Сеансовые компоненты EJB с состоянием хранят информацию о состояпии между вызовами бизнес-методов. Например, сеансовые компоненты EJB с состоянием могут хранить информацию о содержимом магазинной тележки покупателя в процессе посещения потребителем книжного Internet-магазина. • Интерфейс SessionContext расширяет интерфейс EJBContainer, который предоставляет методы для получения информации о контейнере EJB. • Контейнер EJB вызывает метод ejbCreate, когда клиент вызывает метод create в собственном интерфейсе. Реализация EJB-компонента должна предоставлять метод ejbCreate для нахождения метода create, объявленного в собственном интерфейсе. Методы ejbCreate должны иметь такое же количество и те же типы параметров, что и соответствующие методы create. • Контейнер EJB вызывает методы ejbRemove в ответ sa вызов метода remove собственного интерфейса. • Контейнер EJB вызывает метод ejbPassivate, если он определяет, что EJB-компоаент больше не следует хранить в памяти.
Сеансовые компоненты EJB и распределенные транзакции 361 • Контейнер EJB вызывает метод ejbActivate для восстановления экземпляра EJB-компонента, который контейнер ранее пассивировал. Коптейнер EJB активирует экземпляр EJB-компонента, если клиент вызывает бизнес-метод этого экземпляра EJB-компонента. • Технология RMI — ПОР дает возможность объектам RMI взаимодействовать с компонентами CORBA, которые связываются друг с другом с помощью протокола Internet Inter-Orb Protocol (ПОР). CORBA представляет собой независимую от языка программирования инфраструктуру для построения распределенных систем. Чтобы обеспечить совместимость между компонентами EJB и CORBA, EJB-компоненты взаимодействуют между собой с помощью интерфейса RMI — ПОР. » Сеансовые компоненты EJB без состояния не сохраняют информацию между вызовами бизнес-методов. Как результат, при ответе на клиентский запрос может быть использован любой экземпляр сеансового компонента EJB. Это способствует более высокой производительности сеансовых компонентов EJB без состояния в сравнении с сеансовыми компонентами EJB с состоянием. • Java 2 Enterprise Edition поддерживает распределенные транзакции. Распределенная транзакция — это транзакция, которая имеет дело с несколькими базами данных и несколькими серверами EJB. • В J2EE поддерживается два метода для определения границ действий транзакций: разграничение транзакций с управлением ка стороне компонента и разграничение транзакций с управлением па стороне контейнера. • Разграничение транзакций с управлением на стороне компонентам требует, чтобы разработчик EJB-компонента вручную писал код Для границ действия транзакций в EJB-kom- понентах, используя средства API Java Transaction Services. Разграничение транзакций с управлением на стороне контейнера дает возможность разработчику EJB-компонента декларативно задавать границы транзакций при развертывании EJB-компонентов. • Разграничение транзакций с управлением на стороне компонента может использоваться только для сеансовых компонентов EJB, • Разграничение транзакций с контейнерным управлением дает возможность разработчику EJB реализовывать EJB-компонент без указания границ транзакции. Разработчик EJB-компонента декларативно предоставляет семантику разграничения транзакций при развертывании приложения. Терминология application server — сервер приложений bean-managed transaction demarcation — разграничение транзакций с управлением на стороне компонента business methods — бизнес-методы container-managed transaction demarcation — разграничение транзакций с управлением на стороне контейнера CORBA (Common Object Request Broker Architecture), технология построения распределенных приложений create methods — методы создания distributed transaction — распределенная транзакция EJB container — контейнер EJB EJB implementation — реализация EJB EJB server — сервер EJB ejbActivate, метод EJBContext, интерфейс ejbCreate, методы ejbPassivate, метод ejbRemote, метод Enterprise Java Beans (EJB) entity EJB — компонент-сущность EJB home interface - - собственный интерфейс IllegalArgumentException, исключение Internet Inter-Orb Protocol (HOP), протокол J*2KK (Java 2 Enterprise Edition) Java Transaction Services (JTS), сервисы управления транзакциями Java j a va. r mi. RemoteExceptio n java:eomp/env — контекст идентификации j a va x. e jb .EJBHome javax.ejb.EJBObjeet javax.ejb.SessionBean JNDI (Java Naming and Directory Interface) directory — служба каталогов JNDI least recently used — замещение компонента с наиболее давним использованием naming context — контекст идентификации remote interface of on EJB — удаленный интерфейс EJB-компонента remove methods — методы удаления RMI — ПОР, технология построения распределенных приложений session EJB — сеансовый компонент EJB SessionContext, интерфейс statefui session EJB — сеансовый компонент EJB с состоянием stateless session EJB — сеансовый компонент EJB без состояния
362 Глава 6 Упражнения для самоконтроля 6.1. Назовите два основных типа сеансовых компонентов EJB. В чем состоит главное различие между ними? 6.2. Назовите три объекта Java, которые разработчик компонентов EJB должен предоставить для каждого EJB-компонента. 6.3. Какие функции возложены на контейнер EJB? 6.4. Как клиент получает удаленную ссылку на экземпляр EJB-компонента? 6.5. Какие тины разграничения транзакций могут использовать EJB-компоненты? Какие преимущества имеет каждый из типов? Ответы на упражнения для самоконтроля 6.1. Существуют сеансовые компоненты EJB с состоянием и сеансовые компоненты EJB без состояния. Сеансовые компоненты EJB с состоянием хранят информацию о состоянии между вызовами бизнес-методов в ходе сеансе с клиентом. Сеансовые компоненты EJB без состояния не сохраняют информацию о состоянии между вызовами бизнес-методов. 6.2. Разработчик компонента EJB должен предоставить удаленный интерфейс, собственный интерфейс и реализацию EJВ-компонента. 6.3. Контейнер EJB отвечает за управление жизненным циклом EJB-компонента- Контейнер EJB создает классы для реализации собственного и удаленного интерфейсов и делегирует вызов бизнес-методов реализациям EJB-компонентам, предоставленным разработчиком. Контейнер EJB также предоставляет ресурсы, например, соединения с базами данных, во время выполнения приложения, а также управляет жизненным циклом компонентов, 6.4. Клиент ищет собственный интерфейс EJB-компонента в каталоге JNDI. Для сеансовых компонентов EJB клиент после этого вызывает один из методов создания (create) собственного интерфейса. Для компонентов-сущностей EJB клиент может вызвать один нз методов создания (create) или поиска (finder) собственного интерфейса. 6.5. EJB-компоневты могут использовать либо разграничение транзакций с управлением на стороне компонента, либо разграничение транзакций с управлением на стороне контейнера. Разграничение транзакций с управлением на стороне компонента дает возможность разработчику избирательно управлять границами действия транзакций. Разграничение транзакций с управлением на стороне контейнера упрощает реализацию EJB-компонента, позволяя разработчику EJB-компонента декларативно задавать границы транзакции на этапе развертывания. Упражнения 6.6. Сеансовые компоненты EJB бее состояния имеют преимущество в производительности перед сеансовыми компонентами без состояния. Преобразуйте пример, представленный на рис. 6.3, 6.4, 6.5 и 6.6, использовав вместо сеансового EJB-компонента с состоянием сеансовый EJB-компонент без состояния. 6.7. Добавьте новый рекурсивный бизнес-метод power(base, exponent) в EJB-компонент MathTool (рис. 6.21, 6.22, 6.23), который при вызове возвращает base "4"Mdt где base — основание, a exponent — показатель степени. Например, power(3, 4) = 3*3*3*3. Если exponent не является целым числом, большим или равным 1, должно возбуждаться исключение IllegalArgumentException. [Подсказка. На шаге рекурсии должно использоваться отношение base -ч™»* = base * base «P°«™-i а условие окончания наступает при равенстве 1 показателя степени exponent, поскольку base1 = basa Модифицируйте клиента, представленного на рис. 6.24, чтобы дать возможность пользователю вводить значения для основания степени base и показателя степени exponent.]
7 Компоненты EJB с данными Цели • Понять, как EJB* компоненты с данными обеспечивают сохранение данных. • Получить представление о проблемах, связанных с синхронизацией EJB-компонентов с данными, хранящимися в базе данных. • Познакомиться с жизненным циклом EJB-Компонента с данными. • Узнать о преимуществах и недостатках управления персистентностью на стороне контейнера и управления персистентностью на стороне компонента. В бизнесе нет ничего более важного, чем своевременная доставка. Джозеф Эдисон Как известно, смысл определяется толкованием. Джордж Элиот События, которые предопределены, требуют минимального вмешательства. Они совершаются без нашего участия, и м вдруг осознаем, что дело, к которому мы боялись приступить, уже сделано. Амедия Барр
364 Глава 7 7.1. Введение Основной составляющей корпоративного приложения является информационный уровень, хранящий данные, используемые приложением. В этой главе будут рассмотрены компоненты EJR для управления данными (компоненты-сущности), которые дают возможность разработчикам создавать представления данных, полученных от информационного уровня (например, данных, хранящихся в реляционной базе данных), на основе объектов. Контейнеры EJB предоставляют расширенные функции, которые упрощают разработку компонентов-сущностей EJB. Так, на основе информации, предоставленной на этапе развертывания (например, посредством SQL-запросов), контейнер EJB-сущности может автоматически генерировать код для сохранения и извлечения данных, представляемых EJB-компонентом. Для компонентов-сущностей EJB, которые представляют более сложные данные (например, данные, хранящиеся в дескольких таблицах базы данных), программист может самостоятельно написать код для сохранения и извлечения данных. В этой главе будут рассмотрены две версии компонента-сущности Е JB, который отображает информацию об одном сотруднике компании. В первой версии компонент-сущность EJB использует JDBC для сохранения данных в реляционной базе данных. Во второй версии используется способность контейнера управлять сохранением и извлечением данных, что позволяет упростить реализацию EJB-компо- нента. Изучив эту главу, вы сможете создавать и развертывать компоненты-сущности EJB, посредством которых компоненты бизнес-логики, такие как сеансовые компоненты EJB (см. главу 6), могут осуществлять доступ к данным информационного уровня.
Компоненты EJB с данными 365 7.2. Обзор EJB-компонентов с данными Каждый экземпляр компонента-сущности EJB отображает определенный блок данных, например, запись в таблице базы данных. Имеется два типа компонентов-сущностей EJB: с персистентностью (сохраняемостью)1, управляемой самим компонентом, и с персистентностью, управляемой контейнером. Компоненты-сущности EJB, в которых используется управление персистентностью на стороне компонента, должны сами реализовывать код для хранения и извлечения данных из источников данных, которые они представляют. Например, компонент-сущность EJB, в котором управление персистентностью осуществляется самим компонентом, может использовать JDBC для сохранения и извлечения данных из реляционной базы данных. Компоненты-сущности EJB, в которых используется управление персистентностью на стороне контейнера, реализуют вызовы для доступа к информации из постоянных источников данных, основываясь на возможностях контейнера EJB. Администратор развертывания должен предоставить информацию об источнике данных при развертывании EJB-компонента. Компоненты-сущности EJB предоставляют методы create для создания новых экземпляров EJB-компонентов, методы remove для удаления экземпляров EJB-компонентов и методы finder для поиска экземпляров EJB-компонентов. Для компонентов-сущностей EJB, которые представляют информацию, хранящуюся в базе данных, каждый метод create выполняет операции вставки INSEBT для создания новых записей в базе данных, а каждый метод remove выполняет операции DELETE для удаления записей из базы данных. Каждый метод finder находит экземпляры компонентов-сущностей EJB, которые удовлетворяют определенному критерию поиска (например, с помощью операций SELECT). Далее в этой главе мы обсудим каждый из этих методов. 7.3. Компонент-сущность EJB Employee, хранящий информацию о сотруднике В последующих разделах мы создадим компонент-сущность EJB (Employee), который хранит информацию о сотруднике. Будут рассмотрены две реализации EJB-компонента Employee. В первой реализации (раздел 7.5) для хранения и извлечения информации о сотруднике из основной базы данных используется управление персистентностью на стороне самого компонента. Во второй реализации (раздел 7.6) используется управление персистентностью на стороне контейнера. Обе эти реализации используют один и тот же удаленный интерфейс Employee и собственный интерфейс EmployeeHome, которые будут рассмотрены в разделе 7.4. Для хранения данных о сотрудниках будет использоваться база данных Cloudscape Employee. Чтобы создать базу данных Employee, выполните SQL-сценарий employee.sql, который имеется в комплекте примеров для книги на сайте компании Deitel www.deitel.com, а также на сайте издательства «Бином» www.binom- press.ru/books/advjava2.htm. Инструкции по выполнению SQL-сценариев в Cloudscape можно найти к главе 4. Чтобы настроить эталонную реализацию J2EE для работы с базой данных Employee, добавьте текст | jdbc/Employee|jdbc:cloudscape:rmi:Employee;create=true в конец значения свойства jdbc.datasou.rces в файле конфигурации default.properties J2EE. Используется также термин «постоянство*. — Прим. ред.
Збб Глава 7 7.4. Собственный и удаленный интерфейсы EJB-компонента Employee Удаленный интерфейс Employee (рис. 7.1) предоставляет методы для задания и получения информации о сотруднике. Обратите внимание, что интерфейс Employee расширяет интерфейс EJBObject (строка 11). Это обязательно для всех удаленных интерфейсов EJB. Удаленный интерфейс Employee предоставляет методы set и get для каждого свойства объекта Employee, включая socialSeeurity Nnmber, firstName, lastName, title и salary. Для свойства employee ID метод set не предусмотрен, поскольку это поле является первичным ключом. Каждый метод set и get возбуждает исключение RemoveException. Это обязательно для всех методов, определенных в удаленном интерфейсе. 1 // Employee.java 2 // Employee - удаленный интерфейс для БJB-компонента Address. 3 package com.deitel.advjhtpl.ejb.entity; 4 5 // Базовые библиотеки Java 6 import java.rmi.RemoteException; 7 8 // Стандартные расширения Java 9 import javax.ejb.EJBObject; 10 11 public interface Employee extends EJBObject { 12 13 // получение идентификатора сотрудника EmpioyeelD 14 public Integer getEmployeelD() throws RemoteException; 15 16 // задание номера свидетельства социального страхования 17 public void setSocialSecurityNumber( String number ) 18 throws RemoteException; 19 20 // получение номера свидетельства социального страхования 21 public String getSocialSecurityNumber() 22 throws RemoteException; 23 24 // задание имени 25 public void setFirstName( String name ) 26 throws RemoteException,- 27 28 // получение имени 29 public String getFirstName() throws RemoteException; 30 31 // задание фамилии 32 public void setLastName( String name ) 33 throws RemoteException; 34 35 // получение фамилии 36 public String getiastName() throws RemoteException; 37 38 // задание должности 39 public void setTitle( String title ) 40 throws RemoteException; 41 42 // получение должности
Компоненты EJB с данными 367 43 public String getTitle{) throws RemoteException; 44 45 // задание величины оклада 46 public void setSalary{ Double salary ) throws RemoteException; 47 48 // получение величины оклада 49 public Double getSalaryO throws RemoteException; 50 } Рис. 7.1. Удаленный интерфейс Employee для задания и получения информации о сотруднике Экземпляр EJB-компонента представляет определенную строку в соответствующей таблице базы данных. Собственный интерфейс для компонента-сущности EJB определяет методы finder для поиска определенных строк в таблице и методы create для вставки новых записей. Интерфейс EmpIoyeeHome (рис. 7.2) предоставляет метод поиска findByPrimary Key (строки 15-16) для нахождения экземпляров EJB-компонента Employee на основе первичного ключа. Первичным ключом для EJB-компонента Employee является employeelD. Метод findByPrimaryKey возбуждает исключение Finder- Exception, если сотрудник с указанным первичным ключом primaryRey не может быть найден. Метод create (строки 19-20) создает новые экземпляры EJB-компонента Employee. Метод create возбуждает исключение CreateException, если возникает проблема при создании экземпляра EJB-компонента. 1 // EmpIoyeeHome.Java 2 // EmpIoyeeHome - собственный интерфейс для EJB-компонента Employee. 3 package coni.deiteX . advjtitpl *e jb .entity; 4 5 // Базовые библиотеки Java 6 import java.rmi.*; 1 import java.util.*; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public interface EmpIoyeeHome extends EJBHome { 13 14 // поиск сотрудника по заданному первичному ключу 15 public Employee findByPrimaryKey( Integer primaryKey ) 16 throws RemoteException, FinderException; П 18 // создание нового EJB-компонента Employee 19 public Employee create{ Integer primaryKey ) 20 throws RemoteException, CreateException; 21 } Рис. 7.2. Интерфейс EmpIoyeeHome для нахождения и создания EJB-компонентов Employee Метод findByPrimaryKey является одним из методов поиска для компонентов-сущностей EJB. Каждый компонент-сущность EJB должен иметь метод findByPrimaryKey, который принимает в качестве аргумента класс первичного ключа компонента EJB. Компоненты-сущности EJB также могут определять дополнительные методы поиска. Имя метода поиска должно начинаться с fiadBy
368 Глава 7 и заканчиваться именем свойства, используемым в качестве критерия поиска. Например, метод поиска для нахождения сотрудников на основе значения свойства title должен носить имя findByTitle. Метод поиска для нахождения сотрудников, имеющих определенный уровень заработной платы, должен носить имя findBy- Salary Range. 7.5. EJB-компонент Employee с персистентностью, управляемой компонентом В этом разделе описывается EJB-компонент Employee с персистентностью, управляемой компонентом, и осуществляется его развертывание. В реализации с управлением персистентностью на стороне компонента для хранения сведений о сотруднике в базе данных используются средства JDBC. 7.5.1. Реализация EJ В-компонента Employee На рис. 7.3 представлена реализация EJB-компонента Employee, в которой используется управление персистентностью на стороне компонента. Класс EmployeeEJB реализует интерфейс EntityBean (строка 15). Все реализации компонента-сущности EJB должны реализовывать интерфейс EntityBean. В строке 17 объявляется ссылка типа EntityContext для EJB-компонента entityContext. Entity Con text предоставляет собой EJB-компонент, содержащий информацию о контейнере, в котором развернут EJB-компонент. Объект Connection (строка 18) представляет соединение EJB-компонента с базой данных Employee. В строках 20-25 объявляются частные переменные экземпляра, которые кэшируют данные, извлеченные из базы данных, и обновления, внесенные клиентом. 1 // EmployeeEJB.Java 2 // EmployeeEJB - компонент-сущность EJB, который сам осуществляет 3 // управление персистентностью для хранения сведений о сотруднике в базе данных. 4 package com.dei tel.advj htpl.e jb.en ti ty.bmp; 5 6 // Базовые библиотеки Java 7 import java.sql.*; 8 import java.rmi.RemoteException; 9 10 // Стандартные расширения Java 11 import javax.ejb.*; 12 import javax.sql.*; 13 import javax.naming.*; 14 15 public class EmployeeEJB implements EntityBean { 16 17 private EntityContext entityContext; IS private Connection connection; 19 20 private Integer employeelD; 21 private String socialSecurityNuniber; 22 private String firstName; 23 private String lastName; 24 private String title; 25 private Double salary; 26
Компоненты EJB с данными 369 // получение идентификатора сотрудника EmployeelD public Integer getEmployeelD() return employeelD; // задание номера свидетельства социального страхования public void ssetSocialSecurityNumber ( String number } socialSecurityltumber = number; // получение номера свидетельства социального страхования public String getSocialSecurityNumber() return socialSecurityNumber; // задание имени public void setFirstNarae( String name ) firstName = name; // получение имени public String getFirstName() return firatName; // задание фамилии public void setLastKame{ String name ) lastName = name; // получение фамилии public String getiastMame() return lastName; // задание должности public void setTitle( String jobTitle ) title = jobTitle; // получение должности public String getTitleO return title; // задание величины оклада public void aetSalary( Double amount )
370 Глава 7 83 { 84 salary = amount; 85 J 86 87 // получение величины оклада 88 public Double getSalaryO 89 { 90 return salary; 91 } 92 93 // создание нового объекта Employee 94 public Integer ejbCreatef Integer primaryKey ) 95 throws CreateException 96 { 97 employeelD = primaryKey,- 98 99 // вставка (IHSERT) сведений о новом сотруднике в базу данных 100 try { 101 102 // создание оператора INSERT 103 String insert = "INSERT INTO Employee " + 104 "( employeelD ) VALUES ( ? )" ; 105 106 // создание подготовленного оператора PreparedStatement для операции вставки 107 PreparedStatement insertstatement = 108 connection.prepares tatament( insert ); 109 110 // задание значений для оператора PreparedStatement 111 insertstatement.setlnt( 1, employeelD.intValue() ); 112 113 // выполнение вставки (INSERT) и закрытие оператора PxeparedStatement 114 insertstatement.executeUpdate(); 115 insertstatement.close(); 116 117 return employeelD; 118 } 119 120 // возбуждение исключения EJBException, если вставка оканчивается неудачей 121 catch ( SQLException sqlException ) { 122 throw new CreateException( sqlException.getMessage() ); 123 } 124 } // конец метода ejbCreate 125 126 // выполнение остальных действий после создания нового объекта Employee 127 public void еjbPoetCreate( Integer primaryKey ) {} 128 129 // удаление информации о сотруднике из базы данных 130 public void ejbRemove() throws RemoveException 131 I 132 // удаление (DELETE) записи о сотруднике 133 try I 134
Компоненты EJB с данными 371 135 // получение первичного ключа для удаляемого сотрудника 136 Integer primaryKey = 137 ( Integer ) entityContext.getPrimaryKey(); 138 139 // создание оператора удаления DELETE 140 String delete = "DELETE FROM Employee WHERE " + HI "employeelD = ?" ; 142 143 // создание подготовленного оператора FreparedStatement для выполнения удаления 144 FreparedStatement deleteStatement = 145 connection.prepareStatement( delete ); 146 147 // задание значений для оператора PreparedStatement 148 deleteStatement.setlntf 1, primaryKey.intValue() J; 149 150 // выполнение удаления и Закрытий подготовленного оператора PreparedStatement 151 deleteStatement.executeUpdate(); 152 deleteStatement.close(); 153 } 154 155 // возбуждение нового исключения EJBException, если удаление заканчивается неудачей 156 catch { SQLException sqlException } { 157 throw new RemoveException( sqlException.getttessage(> ); 158 I 159 } // конец метода ejbRemove 160 161 // сохранение информации о сотруднике в базе данных 162 public void ejbStoreO throws EJBException 163 { 164 // обновление (UPDATE) записи для сотрудника 165 try { 166 167 // получение первичного ключа для сотрудника 168 Integer primaryKey = 169 { integer ) entityContejct.getPriroaryKeyO ! ПО 171 // создание оператора обновления UPDATE 172 String update = "UPDATE Employee SET " + 173 "socialSeeurityNumber - ?, firstName = ?, " + 174 "lastHame = ?, title = ?, salary = ? " + 175 "WHERE employeelD ■ ?"; 176 177 // создание подготовленного оператора FreparedStatement для выполнения обновления 178 FreparedStatement updateStatement = 179 connection.prepareStatement( update ); ieo 181 // задание значений для оператора PreparedStatement 182 updateStatement.setStringt 1, socialSecurityHumber ); 183 updateStatement.setString( 2, firstName ); 184 updateStatement.setString( 3, lastName ); 185 updateStatement.setString( 4, title ); 186 updateStatement.setDouble( 5, salary.doubleValue() );
372 Глава 7 187 updatestatement.setint( 6, primaryKey.intValue() ); 188 189 // выполнение обновления и закрытие объекта PreparedStatement 190 updateStatement.executeUpdate(}; 191 updat&statement.close(); 192 > 193 194 // возбуждение исключения ЕJBException, если обновление заканчивается неудачей 195 catch ( SQLException sqlException ) { 196 throw new EJBException( sqlException ); 197 } 198 } // конец метода ejbStore 199 200 // загрузка информации о сотруднике из базы данных 201 public void ejbLoad{) throws BJBException 202 { 203 // получение записи для сотрудника из таблицы Employee базы данных 204 try { 205 206 // получение первичного ключа для сотрудника 207 Integer primaryKey = 208 ( Integer ) entityContext.getPrimaryKey(); 209 210 // создание оператора SELECT 211 String select = "SELECT * FROM Employee WHERE " + 212 "employeelD = ?"; 213 214 // создание подготовленного оператора PreparedStatement для оператора SELECT 215 PreparedStatement selectstatement = 216 connection.prepareStatementt select ); 217 218 // задание значения employeelD в операторе PreparedStatement 219 selectstatement.ееtint( 1, primaryKey.intvalue() ); 220 221 // выполнение оператора selectstatement 222 ResultSet resultset = selectstatement. executeQuery () ,- 223 224 // получение информации о сотруднике из результирующего 225 // множества и обновление локальных переменных экземпляра для хэширования данных 226 if ( resultset.next{) ) { 227 228 // получение идентификатора employeelD 229 employeelD = new Integer( resultSet.getlnt( 230 "employeelD" ) ); 231 232 // получение номера свидетельства социального страхования 233 socialSeCurityNmober = resultSet.getString( 234 "socialSecurityNumber" ); 235 236 // получение имени 237 firstName = resultSet.getString( "firstMame" ); 238
Компоненты EJB с данными 373 239 // получение фамилии 240 lastName = resultSet.getString( "lastName" ); 241 242 // получение названия должности 243 title = resultSet.getString( "title" ); 244 245 // получение величины оклада 246 salary = new Doublet resultSet.getDouble< 247 "salary" ) ); 248 249 } // конец блока if 250 251 else 252 throw new EJBException( "No such employee." ); 253 254 // закрытие объекта PreparedStatement 255 selectStatement.close{); 256 257 } // конец блока try 25B 259 // возбуждение исключения EJBException, если выборка ваканчивается неудачей 260 catch ( SQLException sqlException ) { 2 61 throw new EJBException( sqlExoeption ); 262 } 263 } II конец метода ejbLoad 264 265 // поиск сотрудника по его первичному ключу 266 public Integer ejbFindByPrimaryKey( Integer primaryKey ) 267 throws FinderException, EJBException 268 { 269 // нахождение сотрудника в базе данных 270 try { 271 272 // создание оператора SELECT 273 String select = "SELECT employeeID FROM Employee " + 274 "WHERE employeelD = ?"; 275 276 // создание подготовленного оператора PreparedStatement для SELECT 277 PreparedStatement selectStatement = 278 connection.prepareStatement( select ); 279 280 // получение значения employeelD в операторе PreparedStatement 281 selectStatement.setlnt( 1, primaryKey.intValue() ); 282 2B3 // выполнение оператора selectStatement 2B4 ResultSet resultSet = selectStatement.executeQuery(); 2B5 286 // возврат первичного ключа, если оператор SELECT возвращает запись 287 if ( resultSet.next О ) { 288 289 // закрытие объектов resultSet и selectStatement 290 resultSet.close(); 291 selectStatement.close О ;
374 Глава 7 292 293 return primaryKey; 294 } 295 296 // возбуждение исключения ObjeetNotFoundException, 297 // если выборка SELECT не возвращает записей 298 else 299 throw new ObjeetNotFoundException(); 300 } 301 302 // возбуждение исключения EJBException, если выборка заканчивается неудачей 303 catch ( SQLException sqlExeeption ) ( 304 throw new EJBException( sqlExeeption ); 305 } 306 } // конец метода ejbFindByPrimaryKey 307 308 // задание контекста и создание соединения с источником данных 309 public void setEntityContext( EntityContext context ) 310 throws EJBException 311 { 312 // задание контекста entityContext 313 entityContext = context; 314 315 // поиск источника данных Employee и создание соединения 316 try I 317 InitialContext initialContext = new InitialContext(); 31B 319 // получение ссылки на источник данных из каталога JNDI 320 DataSource dataSource = ( DataSource ) 321 initialContext.lookup( 322 "java:comp/env/jdbc/Employee" ); 323 324 // получение соединения с источником данных 325 connection = dataSource.getConnectionO; 326 } 327 328 // обработка исключения, если источник данных не найден в каталога 329 catch ( NamingException namingException ) { 330 throw new EJBException{ namingException ); 331 J 332 333 // обработка исключений при получении соединения с источником данных 334 oatch ( SQLException sqlExeeption ) { 335 throw new EJBException< sqlExeeption ); 336 } 337 } // конец метода aetEntityContext 338 339 // сброс контекста EntityContext 340 public void unsetEntityContext() throws EJBException 341 { 342 entityContext = null; 343 344 // вакрьггие соединения с источником данных
Компоненты EJB с данными 375 345 try { 346 connection.close(}; 347 > 34S 349 // возбуждение исключения ЕJBException, если закрытое соединения заканчивается неудачей 350 catch ( SQLException eqlExCeption ) { 351 throw new EJBException( sqlException ); 352 } 353 354 // подготовка повторного соединения 355 finally \ 356 connection = null; 357 } 358 } 359 360 // установка для employeelD значения null при пассивации EJB-компонента контейнером 361 public void ejbEaesivate0 362 { 363 employeelD = null; 364 } 365 366 // получение первичного клича ври активации EJB-компонента контейнером 367 public void ejbActivate() 368 { 369 employeelD = ( Integer ) entityContext.getPrimaryKey(); 370 } 371 } __ Рис. 7.З. Реализация EmployeeEJB удаленного интерфейса Employee, использующая управление персистентностью на стороне компонента В строках 28-91 реализуются бизнес-методы set и get, объявленные в удаленном интерфейсе Employee. При вызове клиентом метода create интерфейса EmployeeHorae контейнер EJB вызывает метод ejbCreate (строки 94-126), В этом EJB-компоненте персистентность данных обеспечивается самим компонентом, поэтому метод ejbCreate должен реализовывать соответствующую логику для создания новой записи о сотруднике в основной базе данных. В строке 97 для переменной экземпляра EmployeelD устанавливается значение аргумента primaryKey. В строках 103 111 создается оператор SQL (объект класса PreparedStatement) для вставки (INSERT) информации о новом сотруднике в базу данных. В строке 111 задается значение идентификатора EmployeelD в операторе SQL, а в строке 114 осуществляется вставка новой записи путем вызова метода executeUpdate класса PreparedStatement. В строке 115 осуществляется закрытие оператора Prepared- Statement, а в строке 117 возвращается первичный ключ EmployeelD. В строках 121-122 перехватывается исключение SQLException, которое может быть возбуждено при создании, выполнении или закрытии оператора PreparedStatement. Исключение SQLException указывает на наличие проблемы при вставке записи, поэтому в строке 122 возбуждается исключение CreateException для указания, что метод ejbCreate не смог создать экземпляр EJB-компонента Employee. Метод ejbCreate объявляет целочисленный (Integer) возвращаемый тип. Все методы ejbCreate в компоненте-сущности EJB должны возвращать класс первичного ключа EJB-компонента. Первичный ключ для таблицы Employee представлл-
376 Глава 7 ет собой целое число, поэтому классом первичного ключа для EJB-компонента Employee является класс Java.lang.Integer. Большинство EJB-компонентов используют в качестве класса первичного ключа стандартный класс Java (например. Integer или String). Если таблица базы данных имеет составной первичный ключ (т.е. первичный ключ, состоящий из нескольких полей), разработчик должен определить собственный класс первичного ключа. Образец собственного класса первичного ключа можно найти в примере EJB-компонента OrderProduct в главе 4. Контейнер EJB вызывает метод ejbPostCreate (строка 127) после вызова метода ejbCreate, чтобы выполнить все необходимые после создания экземпляра EJB-компонента действия. Например, метод ejbPostCreate может изменять формат номера свидетельства социального страхования socialSecurityNumber для включения в него дефисов. В данном случае никаких дополнительных действий не требуется, поэтому в строке 127 указывается пустая реализация метода ejbPostCreate. При вызове клиентом метода remove в удаленном интерфейсе Employee либо в интерфейсе EmptoyeeHome, контейнер EJB вызывает метод ejbRemove (строки 130-159). Удалить (remove) экземпляр компонента-сущности EJB — значит удалить (DELETE) соответствующую запись в базе данных. В строках 136-137 из объекта entityContext, ассоциированного с EJB-компонентом, извлекается первичный ключ для текущего экземпляра EJB-компонента. В строках 140-145 создается оператор SQL для удаления (с помощью операции DELETE) из базы данных записи, относящейся к сотруднику. В строке 148 задается первичный ключ employeelD, используемый в формируемом операторе SQL. В строках 151-152 осуществляется выполнение и закрытие сформированного оператор SQL. В сроках 156-158 перехватывается исключение SQLException, которое может быть возбуждено при создании, выполнении или закрытии сформированного оператора SQL. Если было возбуждено исключение SQLException, в строке 157 возбуждается исключение Remove Exception, указывающее, что удаление EJB-компонента Employee окончилось неудачей. Контейнер EJB вызывает метод ejbStore, чтобы сохранить данные о сотруднике в базе данных. Контейнер EJB определяет наилучший момент для обновления базы данных, поэтому метод ejbStore вызывается только контейнером. В строках 168-169 из объекта entityContext извлекается первичный ключ для текущего экземпляра EJB-компонента. В строках 172-187 создается оператор SQL для обновления (с помощью операции UPDATE) в базе данных записи для сотрудника. В строках 190-191 осуществляется выполнение и закрытие сформированного оператора SQL. В строках 195-96 перехватывается исключение SQLException, которое может быть возбуждено при создании, выполнении и закрытии сформированного оператора SQL. В строке 196 возбуждается исключение EJBException, указывающее, что метод ejbStore не смог должным образом осуществить обновление базы данных. В строке 196 исключение SQLException передается конструктору EJBException, чтобы облегчить отладку приложения. Контейнер EJB вызывает метод ejbLoad для загрузки информации о сотруднике из базы данных и сохранения данных в переменных экземпляра EJB-компонента. Контейнер EJB определяет наилучший момент для загрузки информации из базы данных, поэтому метод ejbLoad вызывается только контейнером, В строках 207-208 Employee из контекста EntityContext EJB-компонента извлекается первичный ключ для текущего экземпляра EJB-компонента. В строках 211-219 создается оператор SQL для выборки (с помощью операции SELECT) информации о сотруднике из базы данных. В строке 226 проверяется, что результирующее множество ResultSet содержит данные, а в строках 229-247 частные переменные экземпляра обновляются данными из результирующего множества. В строках 260-262 перехватывается исключение SQLException, и возбуждается исключение
Компоненты EJB с данными 377 EJBException, указывающее, что метод ejbLoad не смог загрузить информацию о сотруднике из базы данных. При вызове клиентом метода findByPrimaryKey интерфейса EmployeeHome контейнер EJB вызывает метод ejbFindByPrimaryKey (строки 266-306). Метод findByPrimaryKey должен принимать один параметр, тип которого соответствует классу первичного ключа EJB-компонента. В строке 273-281 создается оператор SQL для выборки из базы данных (с помощью операции SELECT) информации о сотруднике с заданным первичным ключом employeelD. В строках 287—294 проверяется, был ли найден сотрудник, и возвращается его первичный ключ. Если сотрудник с заданным первичным ключом в базе данных найден не был, в строках 298-299 возбуждается исключение Object Not F о undException. Если при поиске информации о сотруднике возникла ошибка, в строках 303-305 перехватывается исключение SQLException и возбуждается новое исключение EJBException. Контейнер EJB вызывает метод setEntityContext после первого создания экземпляра EJB-компонента, но до связывания экземпляра EJB-компонента с определенной записью в базе данных. Параметр EntityContext предоставляет методы для получения информации о контейнере EJB, в котором выполняется EJB-компонент. В строке 313 задается значение переменной экземпляра entityContext, чтобы другие методы могли использовать данный объект EntityContext для получения информации о контейнере EJB. На использование метода setEntityContext накладывается несколько ограничений, так как при вызове этого метода экземпляр EJB-компонента еще не связан с определенной записью в базе данных. Например, EJB-компонент не должен вызывать метод getPri meryKey класса EntityContext, поскольку отсутствует первичный ключ, ассоциированный с текущим экземпляром EJB-компонента. Вызов метода getPrimeryKey класса EntityContext приведет к возбуждению исключения IlIegalStateExccption. Метод getEntityContext должен выделять ресурсы, которые потребуются экземпляру EJB-компонента в течение его жизненного цикла. Данная реализация EJB-компонента Employee использует управление персистентностью на стороне самого компонента, поэтому необходимо иметь соединение с базой данных, чтобы обмениваться информацией с ней в течение времени жизни EJB-компонента. В строке 317 создается новый объект InitialContext, который EJB-компонент будет использовать для поиска базы данных Employee в каталоге JNDI. В строках 320-322 вызывается метод lookup класса InitialContext для получения ссылки типа DataSource на базу данных Employee. Приложение J2EE использует специальный контекст идентификации JNDI с именем java:comp/env, который определяет окружение компонента. В контексте идентификации java:comp/env имеются подчиненные контексты для объектов различных типов. Например, EJB-компо- ненты содержатся в подчиненном контексте ejb, а базы данных содержатся в подчиненном контексте jdbc. В строке 322 использует имя JNDI java:comp/env/jdbc/ Employee для нахождения базы данных Employee в каталоге JNDI. В строке 325 устанавливается переменная connection, указывающая на создаваемое соединение с базой данных. Если база данных с указанным именем не может быть найдена, в строках 329—331 перехватывается исключение NamingException, которое возбуждается конструктором InitialContext или методом lookup. -, Типичная ошибка программирования 7.1 Контекст идентификации java:comp/env доступен только из приложения J2EE (например, из сервлета, с JSP-страницы или из EJB-компонента). В случае использования этого контекста идентификации в приложении Java или в апплете, будет возбуждено исключение javax.naming.Na- mingException.
378 Глава 7 Контейнер EJB вызывает метод unset En tityCon text (строки 340-358), если экземпляр EJB-компонента больше не нужен. Метод unsetEntityContext должен освобождать любые ресурсы, которые экземпляр EJB-компонента использовал в течение своего жизненного цикла. В строке 346 закрывается соединение с базой данных. Если при закрытии соединения с базой данных возникла ошибка, в строках 350-352 перехватывается исключение SQLException, и возбуждается исключение EJBExcepiion. В строках 355-357 объект соединения Connection устанавливается в null для последующего его использования. Контейнер EJB содержит пул неактивных экземпляров EJB-компонента, которые контейнер при необходимости может ассоциировать с определенными записями в базе данных. Такая организация пула предотвращает возникновение дополнительных затрат для контейнера EJB, связанных с необходимостью каждый раз создавать новый экземпляр EJB-компонента, когда в нем возникает потребность. Контейнер EJB вызывает метод ejbPassivate (строки 361-364), чтобы поместить активный EJB-компонент обратно в пул неактивных экземпляров. В строке 363 в качестве значения employeelD устанавливает null, поскольку пассивированный EJB-компонент больше не ассоциируется с какой-либо записью базы данных. Контейнер EJB вызывает метод ejbActivate {строки 367-371) для активации экземпляра EJB-компонента, извлеченного из пула неактивных экземпляров EJB-компонента. В строке 369 в качестве значения идентификатора employeelD устанавливается значение первичного ключа PrimaryKey, извлеченного из объекта entityContext EJB-компонента, чтобы связать EJB-компонент с соответствующей записью в базе данных. 7.5.2. Развертывание EJB-компонента Employee Развертывание компонентов-сущностей EJB сходно с развертыванием сеансовых компонентов EJB (см. главу 6), но имеется и несколько отличий. В диалоговом окне General мастера New Enterprise Bean Wizard выберите опцию Entity в поле Bean Type (рис. 7.4). Далее, выберите тип управления персистентностью в диалоговом окне Entity Settings. Щелкните на переключателе Bean-Managed, чтобы ус- Рис. 7.4. Диалоговое окно General мзстера New Enterprise Bean Wizard
Компоненты EJB с данными 379 тановить режим управления персистектносгью на стороне компонента, и введите имя класса для первичного ключа в текстовом поле Primary Key Class (рис. 7.5). Наконец, в диалоговом окне Resource References добавьте ссылку на базу данных Employee. Информация, содержащаяся в ссылке, должна соответствовать той, которая приведена в копии экрана на рис. 7.6. В остальном развертывание осуществляется точно гак же, как и для сеансовых компонентов EJB. В качестве имени JNDI EJB-компонента Employee рекомендуется для версии с управлением перси- стентностью на стороне компонента указать имя BMPEmployee. Рис. 7.5. Выбор типа управления перслстентностью Bean-Managed Persistence в диалоговом окне Entity Settings Рис. 7.6. Диалоговое окно Resource References мастера New Enterprise Bean Wizard
380 Глава 7 7.6. EJB-компонент Employee с персисгентностью, управляемой контейнером На рис. 7.7 представлена реализация EJB-компонента Employee, в которой используется управление персисгентностью на стороне контейнера, что позволяет упростить реализацию EJB-компонента. Класс EmployeeEJB реализует интерфейс EntityBean (строка 12), что определяет этот EJB-компонент как компонент-сущность EJB, В строке 14 объявляется ссылка на объект типа EntityContext, хранящий данные, относящиеся к экземпляру EJB-компонента. В строках 17—22 объявляются управляемые контейнером поля для EJB-компо- нента Employee. Управляемые контейнером поля представляют собой переменные экземпляра, которые EJB-компонент с персистентностью, управляемой контейнером, использует для кэширования данных, извлеченных из базы данных. Поля, управляемые контейнером, должны носить те же имена, что и поля в соответствующей таблице базы данных. Управляемые контейнером поля также должны быть помечены как открытые (public), чтобы контейнер EJB мог напрямую осуществлять доступ к ним. Контейнер ЕЛВ ответственен за синхронизацию управляемых контейнером полей с базой данных. При развертывании EJB-комтюнента администратор развертывания должен предоставить операторы SQL для изменения, удаления и извлечения данных из базы данных. В строках- 25-88 определены реализации методов, объявленных в удаленном, интерфейсе Employee, Метод ejbCreate (строка 91-96) принимает параметр Primary Key типа Integer и сохраняет его в переменной экземпляра employeell) (строка 93). Контейнер EJB выполняет оператор SQL INSEKT (этот оператор должен быть определен администратором развертывания) после завершения метода ejbCreate. Этот оператор SQL INSERT принимает текущие значения управляемых контейнером полей в EJB-компоненте и помещает эти значения в базу данных. Обратите внимание, что хотя в методе ejbCreate для возвращаемого значения задан тип Integer (строка 91), в строке 93 возвращается null. В соответствии со спецификацией EJB, метод ejbCreate должен возвращать тип класса первичного ключа. Для EJB-компонента Employee таким типом является Integer. Однако контейнер EJB игнорирует возвращаемое методом ejbCreate значение при управлении персистентностью со стороны контейнера, поэтому мы возвращаем null. Согласно спецификация EJB, для каждого метода ejbCreate должен иметься соответствующий метод ejbPostCreate. В строке 99 предоставляется пустая реализация метода ejbPostCreate, поскольку этот EJB-компонент не требует дальнейшей инициализации. Методы eetEntityContext (строки 102-105) и unsetEntityContext (строки 108-111) управляют переменной экземпляра EntityContext. Для этой реализации EJB-компонента не имеется каких-либо ресурсов, которые были бы необходимы в течение времени жизни EJB-компонента, поэтому никаких дополнительных действий для этих методов не требуется. 1 // EmployeeEJB.Java 2 // EmployeeEJB - компонент-сущность EJB, в котором используется 3 // контейнерное управление персистентностью для хранения сведений о сотруднике в базе данных. 4 package com.deltel,advjhtpl.ejb.entity.emp; 5 6 // Базовые библиотеки Java 7 import Java. rmi.. ReidotaExcepticm; 8 9 // Стандартные расширения Java 10 import javax.ejb.*;
Компоненты EJB с данными 381 public class EmployeeEJB implements EntityBean { private EntityContext entity-Context; // поля, управляемые контейнером public Integer employeelD; public String socialSecurityNumber; public String firstName,- public String lastName; public String title; public Double salary; // получение идентификатора EmployeelD public Integer getEmployeelD() return employeelD; // задание номера свидетельства социального страхования public void setSocialSecurityNumberf String number ) socialSecurityNumber = number; // получение номера свндегельсгм социального страхования public String getSocialSecurityNumberQ return socialSecurityNumber; // задание имени public void setFirstName( String name ) firstName = name; // получение имени public String getFirstName() return firstName; // задание фамилии public void setLastName( String name ) lastName = name; // получение фамилии public String getLastNaroeО return lastName; // задание должности
382 public void setTitle{ String jobTitle ) title = jobTitle; / получение должности public String getTitleO return title; / задание неличины оклада public void setSalary ( Double amount ) salary = amount; / получение аеличшш оклада public Double getSalaryO return salary; / создание нового экземпляра об-ьекта Employee public Integer ejbCreate{ Integer primaryKey ) employeeID = primaryKey; return null; / выполнение необходимых действий после создания нового об-ьекта Employee public void ejbPostCreate{ Integer primaryKey ) {} / установка контекста EntityContext public void setEntityContext( EntityContext context ) entityContext = context; / сброс контекст» EntityContext public void unsetEntityContextO entityContext = null; / активация экземпляра Employee public void sjbActivatef) employeelD = ( Integer ) entityContext.getPrimaryKey() / пассивация экземпляра Employee public void ejbPassivate{)
Компоненты ЕЗВ с данными 383 122 employeelD = null; 123 } 124 125 // загрузка экземпляра Employee в базу данник 126 public void ejbLoad() {) 127 128 // сохранение экземпляра Employee в базе данных 129 public void ejbStore() I) 130 131 // удаление экземпляра Employee из Сазы данных 132 public void ejbRemoveO {) 133 J Рис. 7.7. Реализация EmployeeEJB удаленного интерфейса Employee, использующая управление персистентностью на стороне контейнера Методы ejbActivate (строки 114-117) и ejbPassivate (строки 120-123) выполняют те же функции, которые они выполняют в версии EJB-компонента Employee с персистентностью, управляемой компонентом. Метод ejb Activate связывает экземпляр EJB-компонента, взятого из пула неактивных экземпляров, с определенной записью в базе данных, А метод ejbPassivate отменяет связывание экземпляра EJB-компонента с записью Сазы данных. Методы ejbLoad (строка 126), ejbStore (строка 129) и ejbRemtrve (строка 132) также выполняют те же самые функции, что в версий EJB-компонента Employee с персистентностью, управляемой компонентом. Однако при управлении персистентностью на стороне контейнера администратор развертывании должен предоставить необходимые операторы SQL SELECT, UPDATE и DELETE при развертывании приложения. Контейнер EJB выполняет эти операторы в процессе исполнения. Эти методы могут при необходимости выполнять дальнейшую обработку данных. Для нашего EJB-компонента Employee никакой дальнейшей обработки не нужно, поэтому эти методы имеют пустые реализации. Типичная ошибка программирования 7.2 Для компонентов-сущностей EJB, использующих управление персистентностью на стороне контейнера, контейнер EJB загружает данные базы данных и сам вызывает метод ejbLoad при первом вызове бизнес-метода клиентом. Однако при этом бизнес-метод должен вызываться в контексте транзакции. Совет по повышению эффективности 7.1 Компоненты-сущности EJB с персистентностью, управляемой контейнером, могут выполняться медленнее, чем компоненты с персистентностью, управляемой компонентом, поскольку все данные из базы данных загружаются до того, как контейнер EJB вызывает метод ejbLoad. Компоненты-сущности EJB, управление персистентностью для которых осуществляется компонентом, могут откладывать загрузку данные из базы данных, пока эти данные не понадобятся, что способствует повышению производительности для EJB-компонентов, оперирующих большими объемами данных. Чтобы осуществить развертывание версии EJB-компонента Employee с персистентностью, управляемой контейнером, следуйте .инструкциям, приведенным в разделе 7.5.2. Рекомендуется использовать для версии EJB-компонента Employee с персистентностью, управляемой контейнером, имя JNDI CMPEmp-
384 Глава 7 loyee. В диалоговом окне Persistence Management (рис. 7.8) щелкните на переключателе Container-Managed и установите флажок рядом с каждым из полей, управляемых контейнером. Введите имя класса первичного ключа в текстовое поле Primary Key Class и выберите имя поля первичного ключа из раскрывающегося меню Primary Key Field Nams. Рис. 7.8. Выбор управления персистентностью на стороне контейнера Container-Managed Persistence в диалоговом окне Entity Settings 7.7. Клиент EJ В-компонента Employee На рис. 7.9 представлен класс EmployeeEJBCHent, предназначенный для взаимодействия с компонентом-сущностью EJB Employee. В графическом интерфейсе пользователя предусмотрены кнопки для поиска, добавления, изменения и удаления EJB-компоиентов Employee и текстовые поля JTeittField для отображения информации о сотруднике. В строках 26-27 объявляется ссылка типа EmployееНоше и ссылка типа Employee для доступа к EJB-коМПоненту Employee. Конструктор EmpLoyeeEJBClient принимает строковый аргумент, содержащий имя JNDI EJB-компонента Employee, который следует загрузить. Этот клиент работает как с версией EJB-компонента Employee с персистентностью., управляемой самим компонентом, так и с версией EJB-компонента Employee с персистентностью, управляемой контейнером, поскольку обе версии используют одни и те же собственный и удаленный интерфейсы. Конструктор использует аргумент JNDIName, чтобы определить, какую версию EJB-компонента Employee использовать, В строке 47 создается новый объект InitialContext для поиска EJB-компонента Employee на основе аргумента JNDIName. В строках 50—51 осуществляется поиск интерфейса Employ ееНоше с помощью метода lookup класса InitialContext. В строках 54-56 исаользуется метод narrow класса PortableRemoveObject для приведения типа Object ссылки, возвращаемой методом [ookup, к типу EmployeeHome. Эта ссылка будет использоваться в ходе программы для создания, поиска и удаления EJB-компонентов Employee. Если при создании объекта Initial-
Компоненты EJB с данными 385 Context или при поиске интерфейса EmployeeHome возникает ошибка, в строках 60—62 перехватывается исключение NamingException. Метод getEmployeelD (строки 74-85) отображает панель JOptionPane, в которой у пользователя запрашивается идентификатор employeelD, Этот идентификатор может быть иеигользовая для нахождения существующего экземпляра EJB-компонента Employee или для создания нового экземпляра EJB-компонента Employee. Метод getEmployee (строки 88-119) используют метод jfetEmployeelD в строке 91, чтобы запросить у пользователя идентификатор employeelD сотрудника, информацию о котором пользователь хотел бы найти. В строках 99-100 используется метод findByPrimaryKey интерфейса EmployeeHome для нахождения экземпляра EJB-компонента Employee для сотрудника с заданным идентификатором employeelD. Метод findByPrimaryKey возвращает удаленную ссылку на экземпляр EJB-компонента Employee. В строке 103 вызывается метод set Current Employee для отображения пользователю информации о сотруднике. Если при взаимодействии с EJB-компонентом Employee возникает ошибка, в строках 107-111 перехватывается исключение BemoteException. Если сотрудника с заданным идептификатором не существует, в строках 114-118 перехватывается исключение FinderException. Метод addEmployee (строки 122-150) осуществляет добавление сотрудника в базу данных, создавая новый EJB-компонент Employee. В строке 126 вызывается метод getEmployeelD для запроса у пользователя идентификатора employeelD. В строке 133 вызывается метод create интерфейса EmployeeHome для создания нового экземпляра EJB-компонента Employee с заданным идентификатором employeelD, В строке 136 вызывается метод setCurrentEmployee для отображения пользователю информации о сотруднике. В строках 140-143 перехватывается исключение CpeateException, которое возбуждается, если при создании EJB-компонента Employee возникает ошибка. Например, если идентификатор employeelD, введенный пользователем, уже используется другим EJB-компонентом, контейнер EJB возбуждает исключение DupHcateKeyException, которое является подклассом класса CreateException. Если при взаимодействии с EJB-компонентом Employee возникает ошибка, в строках 146-149 перехватывается исключение Remote- Exception. Метод updateEmployee (строки 153-184) используют значения, предоставленные в текстовых полях JTextField, для обновления текущей информации о сотруднике. В строках 159-175 вызываются методы set EJB-компонента Employee с передачей им в качестве параметров значений каждого из текстовых полей JTextField. Если при взаимодействии с Et/B-компонентом Employee возникает ошибка, в строках 180-182 перехватывается исключение RemoteExceptioit. Метод deleteEmployee (строки 187-214) удаляет информацию о текущем сотруднике из базы данных, вызывая метод удаления EJB-компонента Employee (строка 191). В строках 194-197 устанавливаются пустые строки в качестве значений для текстовых полей JTextField в пользовательском интерфейсе. Если при взаимодействии е EJB-компонентом Employee возникает ошибка, в строках 204—207 перехватывается исключение RemnteException. Если ошибка возникает при удалении EJB-компонента Employee, в строках 210-213 перехватывается исключение RemoveException. Метод setCurreiitEmployee (строки 217-266) принимают в качестве аргумента удаленную ссылку типа Employee и обновляет пользовательский интерфейс информацией, содержащейся в этом EJB-компонента Employee, В строках 224-258 извлекаются значения для каждого из свойств EJB-компонента Employee, и обновляются текстовые поля JTextField в пользовательском интерфейсе. Если при взаимодействии с EJB-компонентом Employee возникает ошибка, в строках 262-265 перехватывается исключение RemoteException.
386 Глава 7 1 // EmployeeEJBClient.Java 2 // EmployeeEJBClient - пользовательский интерфейс для взаимодействия 3 // с EJB-компонентами Employee с г.ерсистентиость», управляемой контейнером. 4 package com.deitel.advjntpl. ejb.entity.client; 5 6 // Базовые библиотеки Java 7 import java.ewt.*; 8 import java.awt.event,*; 9 inport Java.text,*; 10 import ja.va.util.* ; 11 import java.rmi.RemoteException,- 12 13 // Стандартные расширения Java 14 import javax.swing.*; 15 iinport javax.ejb.*; 1 6 import javax.naming.*; 17 iinport javax.rmi.*; 18 19 // Библиотеки Deitel 20 import com.deitel-advjntpl.ejb.entity.*; 21 22 public class EmployeeEJBGlieTit extends JFrame { 23 24 // переменные для доступа к EJB-компонентам 25 private InitialContext initialContext; 26 private EmployeeHome employeeHome; 27 private Employee currentEmployee; 28 29 // поля JTextField для ввода данных пользователем 30 private JTextField employeelDTextField; 31 private JTextField socialSecurityTextField; 32 private JTextField firstNameTextField; 33 private JTextField lastNameTextField; 3d private JTextField titleTextField; 35 private JTextField salaryTextField; 36 37 // конструктор BMPEmployeeEJBClient 38 public EmployeesJBClient( String JNDIName ) 39 { 40 super( "Employee EJB Client" Jj 41 42 // создание пользовательского интерфейса 43 createGUIО ; 44 45 // получение ссылки на EmployeeHome для EJB-компонента Employee 46 try { 47 initialContext = new InitialContext(); 48 49 // поиск EJB-компонентa Employee no заданному имени JNDI 50 Object homeObject = 51 iaitia-lContext. lookup ( JHDIName }; 52 53 // получение интерфейса EmployeeHome 54 employeeHome = ( EmployeeHome ) 55 PortableRemoteObject.narrow(
Компоненты EJB с данными 387 56 homeObject, EmployeeHome.class ); 57 } 56 59 // обработка исключения при войске EJB-компонента Employee 60 catch ( NamingException namingException ) { 61 namingException.printstackTrace( System.err ); 62 } 63 64 // закрытие приложения при закрытии окна пользователем 65 setDefaultCloseOperation( EXIT_ON_CLOSE ); 66 67 // установка размеров окна приложения, задание видимости окна 68 setSize< 600, 300 ); €9 setVisiole( true ); 70 71 } // конец конструктора EmployeeEJBClient 72 73 // запрос у пользователя идентификатора employeelD 74 private Integer getEmpioyeelD() 75 { 76 String primaryKeyStiring - JOptionPane.sbowlnputDialog( 77 this, "Please enter an employeelD" ); 78 7 9 // проверка, не есть ли primaryKeyString null, 80 // если нет, вернуть целое число 81 if ( primaryKeyString = null ) 82 return null; 83 else 84 return new Integer( primaryKeyString ); 85 } 86 87 // получение ссылки на объект Employee для заданного пользователем значения employeelD 88 private void getEmployee() 89 { 90 // запрос у пользователя идентификатора employeelD и получение ссылки на объект Employee 91 Integer employeelD = getEmployeelDO .' 92 93 // возврат, если employeelD есть null 94 if ( employeelD = null ) 95 return; 96 97 try { 98 // поиск сотрудника по заданному идентификатору 99 Employee employee = 100 employeeHome.findByPrimaryKey< employeelD ); 101 102 // обновление экрана информацией о текущем сотруднике Employee 103 setCurrentEmployee( employee ); 104 J 105 106 // обработка исключения при поиске сотрудника 107 catch ( RemoteException remoteException ) { 108 JOptionPane.showMeasageDialog(
388 109 EmployfeeEJBClient. this, 110 remoteException.getMessageO ); 111 J 112 113 // обработка исключения при поиске сотрудника 114 catch ( FinderException finderException ) { 115 JOptionPane.showMessageDialog( 116 EmployeeEJBClient.this, "Employee not " +■ 117 "found; " + finderException.getMessage<) ); 118 } 119 } // конец метода getEmployee 120 121 // добавление нового сотрудника созданием нового экземпляра EJB-компонента Employee 122 private void addEn$>loyee0 123 { 124 // запрос у пользователя идентификатора employeelD и создание объекта Employee 125 try { 126 Integer employeelD = getEmployeelD(); 127 128 (I возврат, если employeelD есть null 129 if ( eflsployeelD == null ) 130 return; 131 132 // создание нового объекта Employee 133 Employee employee = employeeHome,create( employeelD ); 134 135 // обновление экрана данными о новой сотруднике 136 setCurrentEmployee( employee ); 137 } 138 139 // обработка исключения при создании объекта Employee 140 catch ( CreateException createException ) { 141 JOptionPane,showMessageDialog( this, 142 createException.getMe s sage О ); 143 } 144 145 // обработка исключения при создании объекта Employee 14 6 catch ( RemoteException remoteExeeption ) { 147 JOptionPane.showMessageDialog( this, 148 remoteException.getMessageО ); 149 } 150 } // конец нетода addEmployee 151 152 // обновление данных для текущего сотрудника на основе введенной пользователей информацией 153 private void updateEmployee() 154 { 155 // получение информации из ч:екстовых полей JTextField и обновление объекта Employee 156 try { 157 158 // задание значения socialSeciirityNumber 159 currentflmployee.setSocialSecurityNumber( 160 socialSecuxityTextField.getText(J );
Компоненты EJB с данными 161 162 // задание значения firstName 163 curcentEraployee.setFirstNaroe( 164 firstMameTextField.getText() ); 165 166 // задание значения lastName 167 currentEmployee.setLastHame( 168 lastHameTextField.getText() ); 169 170 // Задание значения title 171 curtentEmployee.eetTitlei titleTextField.getText() ); П2 173 // задание значения salary 174 Double salary = new Doublet salaryTextField.getText{) ); 175 eurrentEinployee. setSalary { salary ); 176 177 } // конец блока try 17B 179 // обработка исключения при вызове бизнес-методов объекта Employee 180 catch ( RemoteExcepticn remoteException ) { 181 JOptionPane.showMessageDialog( this, 182 remoteException.getMessage() ); 1S3 ) 184 } // конец метода updateEmployee 185 186 // удаление текущего объекта Employee 187 private void deleteEmployee() 188 { 189 // удаление текущего EJB-компонента Employee 190 try I 191 currentEmployee.remove(); 192 193 // очистка полей JTextField 194 employeeIDTextField.setText( "" ); 195 socialSeeurityTextField.setText( "" ) ; 196 firstMameTextField.setText( "" ); 197 lastNameTextField.setText( ■'" ) ; 198 titleTextField.setText( "" ); 199 salaryTextField.setText( "" ); 200 201 ) Il конец блока try 202 203 // обработка исключения при удалении обявхта Employee 204 catch ( RemoteException remoteException ) { 205 JOptionPane.showMessageDialog{ this, 206 remoteException.getMessage О ) ; 207 J 208 209 // обработка исключения при удалении объекта Employee 210 catch ( RemoveException removeException ) { 211 JOptionPane.showMessageDialog( this, 212 removeException.getMessage() ); 213 } 214 } // конец метода deleteEmployee 215
390 216 // обновление экрана информацией о текущем сотруднице 217 private void setCurrentEmployee{ Employee employee ) 218 { 219 // получение информации для текущего сотрудника currentEmployee и обновление изображения 220 try { 221 currentEmployee = employee; 222 223 // получение значения employeeID 224 Integer employeelD = ( Integer ) 225 CurrentEmployee.getEn^iloyeelD(); 226 227 // обновление экрана 228 employeelDTextField.setText( employeelD.toString() ); 229 230 // задание значения socialSecurityNtimber для отображения 231 socialSecurityTextField.setText{ 232 currentEnployee.getSocialSecurityNumber{) ); 233 234 // задание значения firstName для отображения 235 firetNaieeTextField.setText( 236 currentEmployee.getFiretName{) ); 237 238 // задание значения lastName для отображения 239 lastSameTextField,setText( 240 currentEraployee.getLastNameO ); 241 242 // задание значения title для отображения 243 titleTextField.setText( currentEmployee.getTitle() ); 244 245 // получение величины оклада salary для сотрудника 246 Double salary = currentEmployee.getSalary(); 247 248 // проверка, что salary не есть null и обновление экрана 249 if { salary »= null ) { 250 NumberFormat dollarFormatter = 251 NumberFormat.getCurrencylnstance( 252 Locale.US ); 253 254 salaryTextField.setText( dollarFormatter.format( 255 salary ) ) ; 256 } 257 else 258 salaryTextField.setText( "" ); 259 } // конец блока try 260 261 // обработка исключения при вызове бизнес-методов объекта Employee 262 catch ( RemoteExceptioit renaoteException ) { 263 JOptionPane.showMeggageDialog( this, 264 remoteException.getMessage() ); 265 } 266 } /I конец метода setCurrentEmployee 2 67 268 // создание GUI приложения 269 private void createGUIU
Компоненты EJB с данными 391 270 { 271 // создание компонента JPanel, содержащего элементы формы для сотрудника 272 JPanel formPanel = new JPanel( new GridLayout( 6, 2 ) ); 273 274 ff создание полей JTextFleld для идентификатора employeeID 275 employeeIDTextFieId = new JTextFieldO; 276 employeelDTextField.setEditable( false ); 211 formPanel.add( new JLabel( "Employee ID" ) >; 278 formPanel.add( employeelDTextField ); 279 280 // создание компонентов JTextFleld и JLabel для номера социального страхования 281 socialSecurityTextField = new JTextFieldQ; 262 formPanel.add( new JLabel( "Social Security #" ) ); 283 formPanel.add( socialSecurityTextField ); 284 285 // создание компонентов JTextField и JLabel для имени 2B6 firstNameTextField = new JTextField(); 287 formPanel.add( new JLabel( "First Name" ) ); 288 formPanel.add( firstNameTextField ); 289 290 // создание компонентов JTextFleld и JLabel для фамилии 291 lastNameTextField = new JTextFieldO; 292 formPanel.add( new JLabel( "Last Name" ) ); 293 formPanel.add( lastNameTextField ); 294 295 // создание компонентов JTextField и JLabel для должности 296 titleTextField = new JTextField(); 297 formPanel.add( new JLabel( "Title" ) ); 298 formPanel.add( titleTextField ); 299 300 // создание компонентов JTextField и JLabel для величины оклада 301 salaryTextField = new JTextField(); 302 formPanel.add< new JLabel( "Salary" J ); 303 formPanel.add< salaryTextField ); 304 305 // добавление панели formPanel в панель содержимого contentPane охна JFrame 306 Container contentPane = getContentPane(); 307 contentPane.add( formPanel, BorderLayout.CENTER ); 308 309 // создание панели JPanel для кнопох JButtons 310 JPanel employeeButtonPanel = 311 new JPanel{ new FlowLayout() ); 312 313 // создание кнопки JButton для добавления сотрудников 314 JButton addButton = new JButton( "Add Employee" ); 315 addButton.addActionListener( 316 new ActionListener() { 317 318 public void actionPerformed( ActionEvent event ) 319 { 320 addEraployee(); 321 > 322 }
392 Глава 7 323 ) ; 324 enrployeeButtonPanel.add( addButton ); 325 326 // создание кнопки JButton для сохранения информации о сотруднике 327 JButton saveButton = new JButton( "Save Changes" ); 328 saveButton.addActionListener( 329 new ActionListener() { 330 331 public void actionPerformed( ActionEvent event ) 332 { 333 updateEmployeeO ; 334 } 335 } 336 ) ; 337 employeeButtonPanel.add< saveButton ); 338 339 /J создание кнопки JButton для удаления сотрудников 340 JButton deleteButton = new JButton( "Delete Employee" ); 341 deleteButton,addActionListener{ 342 new ActionListener() { 343 34 4 public void actionPerformed( ActionEvent event ) 345 { 34 6 deleteEmployee () ,- 347 У 348 } 349 ) ; 350 employeeButtonPanel.add( deleteButton ); 351 352 // создание кнопки JButton для поиска сущесявужщих сотрудников 353 JButton findButton = new JButton{ "find Employee" }; 354 findButton.addActionListener( 355 new ActionListener0 { 356 357 public void actionPerformed( ActionEvent event ) 358 { 359 getEmployee(} ; 360 } 361 } 362 ) ; 363 employeeButtonPanel.add( findButton ); 364 365 // добавление панели employeeButtonPanel в панель содержиого contentPane окна JFrame 366 contentPane.add( employeeButtonPanel, 367 BorderLayout.NORTH >; 368 369 } // конец метода createGUl 370 371 // выполнение приложения 372 public static void main( String[] args } 373 { 374 // проверка, что пользователь предоставил имя JNDI для EJB-компонента Employee 375 if ( args.length != 1 ) {
Компоненты EJB с данными 393 376 System.err.println( 377 "Usage: Java EmployeeEJBClient <JNDI Name>" ); 378 System.exit( 1 ); 379 } 380 381 // запуск приложения с использованием предоставленного имени JNDI 382 else 383 пен EmployeeEJBClient( args[ 0 ] ); 384 } 385 } Рис. 7.9. Класс EmployeeEJBClient для взаимодействия с EJ В-компонентом Employee Метод createGUI (строки 269-369) формирует пользовательский интерфейс для класса BmployeeEJBCIieiit. В строках 275-303 создаются текстовые поля JTextField и надписи JLabel для каждого из свойств объекта Employee. В строках 314-363 создаются кнопки JButton и соответствующие слушатели ActionLis-
394 Глава 7 tener, чтобы дать возможность пользователю добавлять, обновлять и находить информацию о сотрудниках. Метод main (строки 372-384) требует, чтобы пользователь предоставил аргумент командной строки, содержащий имя JNDI EJB-компонента Employee, используемого в приложении. Этот аргумент командной строки дает возможность пользователю задавать имя JNDI ддя версий EJB-компонента Employee либо с персистентностью, управляемой компонентом, либо с персистентностью, управляемой контейнером (например, BMPEmployee или CMPEmpioyee), В строках 376-377 осуществляется вывод указаний для пользователя, если имя JNDI не предоставлено. В строке 383 создается новый экземпляр класса EmployeeEJBCHent с использованием в качестве параметра заданного имени JNDI. При выполнении класса EmployeeEJBCHent проверьте, чтобы клиентский JAR-файл для EJB-компонента Employee располагался в каталоге, задаваемом переменной CLASSPATH для класса EmployeeEJBCHent. 7.8. Ресурсы в Internet и во Всемирной паутине j ava. sun. com/products/a jb/news. hbnl Этот сайт предоставляет статьи и новости, имеющие отношение к технологии Enterprise JavaBeans. www. theserverside. com/rescrurces/pdf /e jbmatrixl 1, pdf Этот документ содержит краткий справочник (в формате PDF) по интерфейсам, классам и методам EJB. java. sun.com/products/ejb/dcics .html Основная страница спецификации Enterprise JavaBeans, содержащая документацию и спецификации по технологии EJB, developer,java.sun.com/developer/tachnicelArticlee/J2EE/build В этой статье на сайте Java Developer Connection (для получения доступа к сайту необходимо зарегистрироваться) обсуждается, как обеспечить переносимость приложений J2EE для серверов приложений различных, производителей. java. sun. com/ j гее/blueprint: На сайте J2EE Blueprints рассматриваются Проверенные стратегии для построения корпоративных приложений с помощью средств J2EE. Здесь можно найти технические статьи, в которых обсуждаются раэличвые проекты и содержатся примеры кода, реализующие эти проекты в реальных системах. Резюме • Компоненты-сущности EJB представляют собой объектно-ориентированное представление постоянных данных, например информации, хранящейся в базе данных. ■ Компоненты-сущности EJB, которые используют управление персистентностью ни стороне самого компонента, требуют, чтобы разработчик реализовал код для хранения и извлечения данных из постоянных источников данных, которые представляет EJB-компонент. • Компоиенты-сущиости EJB используют управление персистентностью на стороне контейнера RJB, чтобы реализовать доступ к данным из постоянных источников. Разработчик должен предоставить информацию об источнике данных при раэаертъзваыйи EJB-компонента. • Компоненты-сущности EJB определяют методы create для создания новых экземпляров EJB-компонентя, методы remove для удаления экземпляров EJB-компонекта и методы finder для поиска экземпляров EJB-компонентов, Каждый метод create выполняет операции вставки INSERT для создания новых записей а базе данных. Каждый метод remove выполняет операции DELETE для удаления записей из базы данных. Каждый метод finder ищет экземпляры EJB-сущностей, которые отвечают определенному критерию, ■ Метод findByPrimaryKey — один из методов поиска (finder) для компонентов-сущностей EJB. Каждая EJB-сущность имеет метод findByPrimaryKey, который принимает в каче-
Компоненты EJB с данными 395 стве аргумента класс первичного ключа BJB-еущности, Компоненты-сущности EJB могут также определять и другие методы поиска. Имя метода поиска должно начинаться с finally и закапчиваться именем свойства, которое будет использоваться в качестве критерия поиска. • Все реализации EJB-сущности должны реализовывать иптерфейс EntityBean. • Класс EntityContext предоставляет для EJB-компонента информацию о контейнере, в котором развернут EJB-компонент. • Все методы ejbCreate EJB-сущности должны возвращать класс первичного ключа EJB-компонента. В большинстве EJB-компонентов в качестве класса первичного ключа используются стандартный класс Java (например, Integer или String). • Если таблица базы данных имеет составной первичный ключ (т.е. первичный ключ, кото- рый состоит из нескольких полей), разработчик должен определить собственный (нестан- дартный) класс первичного ключа. ■ Контейнер EJB вызывает метод ejbPostCreate после вызова метода ejbCreate для выполнения любых необходимых соеле создания EJB-компонента действий. • На использование метода setEntityContext накладываются некоторые ограничения, поскольку при вызове этого метода экземпляр EJB-компонента еще не ассоциируется с определенной записью базы данных. Например, EJB-компонент не должен вызывать метод getPrimaryKey класса EntityContext, поскольку нет первичного ключа, ассоциированного с текущим экземпляром EJB компонента. • Метод setEntityContext должен выделять ресурсы, которые будут нужны экземпляру EJB-компонента в течение его жизненного цикла (например, соединение с базой данных), • Приложения J2EE используют специальный контекст идентификации JNDI java:com.p/env, определяющий окружения компонента. EJB-компоненты содержатся в подчиненном контексте ejb, а базы данных — в подчиненном контексте jdbc. • Поля с контейнерным управлением представляют собой переменные экземпляра, которые экземпляр EJB-компонента с персистентностыо, управляемой контейнером, использует для кэширования информации, извлеченной из базы данных, Поля, управляемые контейнером, также должны быть домечены как открытые (public), чтобы контейнер EJB мог напрямую осуществлять доступ к ним. Терминология application server —сервер приложений bean-managed persistence — персистентность (сохраняемость), управляемая компонентом business methods — бизнес-методы complex primary key —составной первичный ключ container-managed field — поле, управляемое контейнером container-managed persistence — персистентность, управляемая контейнером EntityBean, интерфейс EntityContext, интерфейс Упражнения для самоконтроля 7,1. Ответьте, является ли каждое из следующих высказываний истинным илн ложным. Если высказывание ложно, объясните, почему. a) Данные, ассоциированные с EJB-сущкостью, обычно хранятся в реляционной базе данных. b) Удаленный интерфейс для компонента-сущности EJB представляет собой таблицу базы данных, с которой ассоциируется EJB-компонент. c) Компоненты-сущности EJB, в которых используется управление нерсистектностья) на стороне компонента, требуют, чтобы администратор развертывания определил запросы SQL для вставки, изменения, удаления и выборки информации из базы данных. findByPrimaryKey, метод ejbPassivate, метод ejbRemove, метод entity EJB — компонент-сущность EJB finder methods — методы поиска home interface — собственный интерфейс javax.ejb.EntityBean, интерфейс primary-key class — класс первичнши) ключа remove methods — методы удаления setEntityContext, метод UnsetEutityContext, интерфейс
396 Глава 7 d) Методы создания (create) в собственном интерфейсе для компонента-сущности EJB осуществляют вставку новых записей в основную базу данных, e) Ковдгоаенты-сущности ЕJB дол-лны получать необходимые ресурсы С помощью метода ejbCreate. f) Компоненты-сущности EJB, в которых используется управление персистевтностью на стороне контейнера, должны реализовывать интерфейс CMPEntityBean, тогда как EJB-сущкости, в которых используеягся управление персистентностью на стороне компонента, должны реализовывать интерфейс ЕпШуВеап. 7.2. Заполните пропуски в следующих высказываниях: a) Для компонента-сущности EJB, в котором используется управление персистентно- стью на стороне . для того, чтобы хранить данные в реляционной базе данных, должен определить запросы SQL при развертывании EJB-компонен- та. b) Для компонента-сущности EJB, в котором используется управление персистентно- стью на стороне __, для того, чтобы сохранить данные в реляционной базе данных, должен реализовать код, который синхронизирует данные с базой данных, c) Каждый метод создания в собственном интерфейсе должен иметь соответствующий метод в реализации EJB-компонснта, d) Если компонент-сущность EJB имеет составной первичный ключ, разработчик должен предоставить собственный , который представляет этот составной первичный КЛЮЧ, Ответы на упражнения для самоконтроля 7.1. а) Истинно. Ь) Ложно. Удаленный интерфейс определяет бизнес-методы для EJB-kom- понента. Таблицу базы данных представлнет собственный интерфейс, с) Ложно. Разработчик должен предоставить код для управления синхронизацией с основной базой данных, d) Истинно, е) Ложно. Компоненты-сущности EJB должны получать необходимые ресурсы с помощью метода setEntityContext. f) Ложно. Компоненты-сущности EJB должны реализовывать интерфейс ЕпШуВеап. 7.2. а) ковтейнера, администратор развертывания приложения. Ъ) компонента, разработчик, с) ejbCreate. d) класс первичного ключа. Упражнения 7.3. Какие типы персистентности может использовать компонент-сущность? Какие преимущества имеет каждый из типов? 7.4. Создайте компонент-сущность EJB для таблияы Titles базы данных books из главы 4. Компонент-сущность EJB должен использовать управление персистентностью на стороне компонента для синхронизации своих данных со значениями, хранящимися в базе данных. Представьте методы $et и get для каждого поля в таблице Titles. Представьте метод create, который принимает в качестве параметров ISBN-коД (ISBN) и название книги (Title). 7.5. Модифицируйте компонент-сущность EJB, созданный вами в Упражнении 7,4, чтобы использовать управление иерсистецтноссыо иа стороне контейнера вместо управления персистентностью на стороне компонента.
8 Обмен сообщениями с помощью Java Message Service (JMS) Цели • Понять, что собой представляет промежуточное программное обеспечение, ориентированное на о&ыеа сообщениями. • Понять, что собой представляет модель обмена сообщениями «от точки к точке». • Понять, что собой представляет модель обмена сообщениями «издатель/подписчик». • "Уяснить разницу между двумя моделями обмена сообщениями и узнать, когда уместно применять соответствующую модель. • Понять, как использовать интерфейс прикладного программирования Java Message Service (JMS) для посгроеиия приложений Java реализующих обмен сообщениями. • Познакомиться с компонентами EJB, управляемыми сообщениями. Чтобы ощутить, что такое прожить жизнь еще раз, нужно вспомнить все прожитое и сделать эти воспоминания ка можно более долговечными, изложив их на бумаге. Бенджамин Франклин Сейте добрые дела: из них вырастут приятные воспоминания. Авна Луиза Жермен де Сталь ...и часто получать послания с небес... Роберт Берне
398 Глава 8 8.1. Введение При создании корпоративных приложений часто бывает полезно, чтобы стандартные, не связанные друг с другом компоненты приложений «общались» между собой. Например, клиенты книжного Internet-магазина должны иметь возможность отправлять свои заказы. Одно из решений состоит в том, чтобы установить слабую (косвенную) связь между клиентским приложением для заказа и покупки книг к приложением поставщика, которое исполняет полученные заказы, посредством системы обмена сообщениями, которую иногда называют пролежу точным программным обеспечением, ориентированным на обмен сообщениями (message- oriented middleware — MOM), Системы обмена сообщениями дают возможность компонентам передавать сообщения, которые могут быть прочитаны другими компонентами. Существует две основные модели систем обмена сообщениями: «от точки к точке» и * издатель/подписчик*. Модель обмена сообщениями лот точки к точке* позволяет компонентам отправлять сообщения в очередь сообщений. В этой модели подразумевается, что отправитель адресует сообщения потребителю сообщений: целевому компоненту, который обрабатывает полученные сообщения. Когда этот целевой компонент связывается с очередью для приема сообщений, он получает все сообщения, не востребованные до сих лор. Сообщение считается востребованным после того, как сервер отправляет его целевому компоненту, Заметим, что в модели «от точки к точке» потребителем сообщения может быть только один клиент.
Обмен сообщениями с помощью Java Message Service {JMS) 399 Модель обмена сообщениями «издатель/подписчик» дает возможность компонентам публиковать сообщения в тематическом разделе на сервере. Сервер содержит различные тематические разделы, с которыми могут связываться компоненты. Компоненты, заинтересованные в сообщениях, опубликованных в определенном тематическом разделе, могут подписаться на данный раздел. Когда издатель публикует сообщение в заданном тематическом разделе, текущие подписчики получают это сообщение. Заметим, что в модели «издатель/подписчик», в отличие от модели «от точки к точке», каждое опубликованное сообщение может получить нуль или более подписчиков, В обеих моделях обмена сообщениями сообщение имеет заголовок, свойства (не обязательно) и тело (также не обязательно). Заголовок сообщения содержит такую информацию, как адресат сообщения и время отправки. Свойства сообщения дают возможность получателям выбирать, какие типы сообщений они хотели бы принимать; отправитель сообщения может устанавливать эти свойства. Получатели сообщений используют селекторы сообщений для фильтрации сообщений. Фильтрация выполняется на стороне сервера; сервер не отправляет отсеянные фильтром сообщения получателю. Компоненты, управляемые сообщениями, представляют собой компоненты Enterprise JavaBeans, которые поддерживают обмен сообщениями. Подобно тому, как контейнер KJB может использовать любой экземпляр сеансового EJB-компо- нента без состояния для обработки клиентских запросов, контейнер EJB для компонентов, управляемых сообщениями, может использовать любой экземпляр такого компонента для обработки входящих сообщений для заданной очереди или тематического раздела. Поскольку Контейнер может использовать любой экземпляр, компоненты, управляемые сообщениями, не могут сохранять состояние для конкретного клиента. Используя компоненты, управляемые сообщениями, компонент может принимать сообщения асинхронно, не расходуя ресурсы при ожидании поступления сообщения. В этой главе будет рассмотрен интерфейс прикладного программирования (API) Java Message Service (JMS). JMS стандартизирует API для корпоративного обмена сообщениями к поддерживает обе модели обмена сообщениями: <г от точки к точке» и «издатель/подписчик». JMS поддерживает пять типов сообщений: BytesMessage, MapMessage, ObjectMcssage, StreamMessage и TextMessage. Существует несколько реализаций JMS от различных поставщиков. Более подробную информацию об этих поставщиках и о JMS можно найти по адресу java.sun.coin/jms. 8.2. Установка и настройка J2EE 1.3 Эталонная реализация Java 2 Enterprise Edition 1,3 предоставляет поддержку API Java Message Service. Заметим, что версия J2EE 1.3 поддерживает только операционные системы Windows 2000, Windows NT 4.0, Solaris SPARC 7, Solaris SPARC 8 и RedHat Linux v.6.1. Заметим также, что для работы J2EE 1.3 необходим пакет J2SE 1.3.1. Чтобы установить эталонную реализацию J2EE 1.3, выполните следующие действия: 1. Загрузите и распакуйте соответствующий набор программного обеспечения с сайта java.sun.com/j2ee/j2sdkee-beta/index.html. 2. Задайте для переменных окружения значения, приведенные в таблице на рис. 8.1.
400 Глава 8 Переменная окружения J2EE НОМЕ JAVA HOME PATH Значение Каталог, в котором установлен пакет J2EE 1.3 (например, C:\j2sdkee1.3) Каталог, в котором установлен пакет J2SE 1.3.1 (например. C:\jdk1.3.1) Существующее значение переменной окружения PATH с добавлением каталога bin J2EE 1.3 (например, C:\j2sdkee1.3\bin) Рис. 8,1. Задание переменных окружения для установки J2EE 1.3 8.3. Обмен сообщениями «от точки к точке» Модель обмена сообщениями «от точки к точке» (рис. 8.2) позволяет клиентам отправлять сообщения в очередь сообщений. Получатель связывается с очередью для приема сообщений, которые еще не были востребованы. В общем случае подразумевается, что очередь рассчитана только на одного клиента, поэтому только один клиент устанавливает соединение с очередью в качестве получателя. Если получателя нет, сервер хранит отправленные в очередь сообщения до тех пор, пока получатель ке установит соединение и не востребует эти сообщения. Отправитель Отправитель Очередь Сообщение Получатель Сообщение Рис. 8.2. Модель обмека сообщениями «от точки к точке» 8.3.1 Приложение для голосования Voter: обзор Для демонстрации модели обмена сообщениями «от точки к точке* рассмотрим приложение для проведения голосования «ваш любимый язык программирования». Класс Voter (раздел 8.3.2) отправляет голоса в виде сообщений в очередь Votes, Эти сообщения представляют собой простые объекты TcxtMessage (из пакета javax.jms). Тело сообщения содержит название языка программирования, за который пользователь отдает свой голос. Класс VoteCollector (раздел 8.3.3) получает (востребует) сообщения и подсчитывает голоса. Класс VoteCollector может связываться с очередью Votes перед или после отправки сообщения. По мере поступления в очередь дополнительных голосов класс VoteCollector обновляет счетчик голосов и отображает итоговые результаты. Общая структура приложения представлена на рис. 8.3. 8.3.2. Приложение Voter: серверная сторона Часть приложения, относящаяся к отправителю, состоит из одного класса Voter (рис. 8.4). Класс Voter дает возможность пользователю выбирать свой любимый язык программирования и отправлять отданный за него голос в очередь Votes в виде сообщения TextMessage.
Обмен сообщениями с помощью Java Message Service (JMS) 401 VoteCollector I Рис. 8.З. Общая структура приложения Voter l 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 П 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // Voter.java // Voter - это GUI, который дает возможность клиенту проголосовать // эа свой любимый яэшс программирования. Пользователь отправляет // свой голос в очередь Votes ж виде сообщения TextMessage, package com.daitel.advjhtpl.jms.voter; // Набор базовых пакетов Java impor t 3 ava,awt.*; import java.awt.event.*; // Пакеты расширений Java import j avax. swing, *,- import j avax.jms.*; import javax.naming.*; public Class Voter extends JFrame { private String selectedXanguage; // Переменные JMS private QueueConnection queueConnection; private QueueSession queueSession; private QueueSender queueSender; // Конструктор Voter public Voter() i // компоновка пользовательского интерфейса super( "Voter" ) ,- Container container = getContentPane(); container.setLayout( new BorderLayout() ); JTextArea voteArea = new JTextArea( "Please vote for yourNn" + "favorite programming language" ); voteArea.setEditablet false ); container.add( voteArea, BorderLayout.NORTH );
Глава 39 40 JPanel languagesPanel = new JPanelO; 41 languagesPanel.setLayout( new GridLayout( 0, 1 ) ); 42 43 // добавление каждого иэ языков как об*ьекта JChockBox в группу 44 // ButtonGroup гарантирует, что будут выбран только один язык 45 ButtonGroup langvagesGroup = new ButtonGroup(); 46 CheckBoxHandler CheckBoxHandler = new Che.ckBoxHaTvdl.er О ; 47 String languages[] = 48 { "C", "C++", "Java", "Lisp", "Python" ); 49 selectedLanguage = ""; 50 51 // создание переключателей JCheckBox для каждого из языков 52 // и добавление их в труппу ButtonGroup и в панель JSanel 53 for ( int i = 0; i < languages.length; i++ ) { 54 JCheckBox checkBox = new JCheckBox{ languages[ i ] ); 55 checkBox.addItemListener( CheckBoxHandler ) ; 56 languagesPanel.add( checkBox ); 57 languagesGroup.add{ checkBox ); 58 } 59 60 container .add ( languagesPanel, BorderLayout. CENTER ); 61 62 // создание кнопки для голосования 63 JButton submitButton - new JButton( "Submit vote!" ); 64 container,add( submitButton, BorderLayout.SOUTH ); 65 66 // вызов метода submitVote при нажатии кнопки submitButton 67 submitButton.addActionListener ( 68 69 new ActionListene.ro { 70 71 public void actionPerformed ( ActionEvent event ) { 72 submitVote(); 73 } 74 } 75 }; 76 77 // вызов метода quit при Закрытии окна 78 addWindowLiatener( 79 вО new Window Adapter 0 i SI 82 public void windowClosing{ WindowEvent event ) { 83 quit{) ; 84 J 85 } 86 ) ; 87 88 // соединение с очередью сообщений 89 try { 90 91 // создание контекста JNDI 92 Context jndi.Cont.ext. = new InitialContext () ,- 93 94 // извлечение мастера соединения с очередью
Обмен сообщениями с помощью Java Message Service (JMS) 40; 95 //из контекста JNDI 96 QueueCoruiecfcionFactory queueConnectionFactory = 97 ( QueueConnectionFactory ) 98 jndicontext.lookup( "VOTE_FACTORY" ); 99 Queue queue = ( Queue ) jndiContext.lookup( "Votes" ); 100 101 // создание соединения, сеанса и отправителя 102 queueConnection = 103 queueConnectionFactory.createQueueConnection(); 104 queueSession = 105 queueConnection.createQueueSession( false, 106 Session.AUTO_ACKN0WLEDGE ); 107 queueSender = queueSession.createSender( queue ); 108 ) 109 110 // обработка исключения JNDI при идентификации 111 catch ( NamingException namingException ) { 112 namingException.printstackTrace(); 113 System.exit( 1 ); 114 } 115 116 // обработка исключения JMS при соединении с очередью или cosдании сеанса 117 catch ( JMSException jmsException ) { 118 jmsException.printStackTrace(); 119 System.exit( 1 ); 120 } 121 122 ) // конец конструктора Voter 123 124 // отправка голоса в очередь Votes как сообщения TextMessage 125 public void submitVoteO 126 { 127 if ( seleetedbanguage != "" } 1 128 12 9 // создание текстового сообщения, содержащего сведения о выбранном языке 130 try 1 131 TextMessage voteMessage = 132 queueSession.createTextMessage(); 133 voteMessage.setText( seleetedbanguage ); 134 135 // отправка сообщения в очередь 136 queueSender.send( voteMessage ) ; 137 } 138 139 // обработка исключения JMS 140 catch ( JMSException jmsException ) { 141 jmsException.printStackTrace () ; 142 } 143 } 144 145 ) // конец метода submitVote 146 147 // закрытие клиентского прилокения 148 public void quit О
404 Глава 8 Ю I 150 if ( queueConnection ! = null ) { 151 152 // закрытие соединения с очередью, если оно существует 153 try { 154 qu&ueConneetion.close(); 155 } 156 157 // обработка исключения JMS 158 catch ( JMSException jmsException ) { 159 jjftaExoeption.printStackTraceO ; 160 } 161 } 162 163 System.exit( 0 ); 164 165 } // конец метода quit 166 167 // запуск приложения Voter 168 public static void main( String args[) ) 169 { 170 Voter voter = new Voter{); 171 voter.pack(); 172 voter.setVisible( true ); 173 } 174 175 // CheckBoxHandler обрабатывает событие при выборе переключателя 176 private class CheckBoxHandler Implements ItemListener ( 177 178 // событие, связанное с выбором переключателя 179 public void itemStateChanged( ItemEvent event ) 180 { 181 // обновление переменной selectedLanguage 182 JCheckBox source = ( JCheckBox } event.getSource(); 183 selectedLanguage = source.getText(}; 184 ) 185 > 186 } _____ Рис. 8.4. Класс Voter отправляет голоса в очеаедь в виде сообщений В строке 13 импортируется пакет javax.jms, который содержит классы и интерфейсы API JMS. В строках 29-86 настраивается графический интерфейс пользователя (GUI) для клиента Voter. В строках 53—58 создаются объекты JCheckBox, с помощью которых клиент осуществляет голосование. Обратите внимание, что они добавляются в группу переключателей Button Group (строки 45-58), чтобы пользователь мог выбрать только одного кандидата. В строках 89-120 настраиваются соединения JMS. В строке 92 создается контекст JNDI, из которого программа извлекает объекты типа QueucConnection- Factory и Queue, идентифицируемые, соответственно, по именам VOTE_FACTO- RY и Votes. Заметим, что администратор сервера должен создать очередь и мастера для соединения С очередью (раздел 8.3.4). Мастер для соединения с очередью создает объект QueueConnection (строки 102-103). Объект QueueConnection создает сеанс QueueSession (строки 104-106). Наконец, объект QueueSession создает либо объект отправителя QueucScnder, либо объект получателя QueueReceiver.
Обмен сообщениями с помощью Java Message Service (JMS) 405 В данном случае QueueSession создает объект QueucSender для очереди Votes (строка 107); теперь объект QueueSender может посылать сообщения в очередь. После того как пользователь выбрал язык и щелкнул на кнопке Submit vote (рис. 8.5), метод submit Vote создает объект TextMessage и устанавливает значение selectedLanguage в качестве текста сообщения (строки 131-133). В строке 136 сообщение отправляется в очередь Votes. Программа вызывает метод quit (строки 148-165), когда пользователь закрывает окно приложения. В строках 150-161 осуществляется закрытие соединения с очередью. 8,3.3. Приложение Voter: принимающая сторона В рассматриваемом нами примере — приложении Voter — класс VoteCollector (рис. 8.6) является предполагаемым получателем сообщений, отправленных в очередь Votes. Класс VoteCollector получает голоса из очереди и отображает количество полученных голосов. Заметим, что класс Voter может отправлять сообщения в очередь до установки соединения с классом VoteCollector; класс VoteCollector получит эти сообщения после того, как соединение будет установлено. lOBMKlEftit Please vote for your favorite prog ram m»A§ language □c - ■■ ;■' new lgJa»»l. . ,....:.;■ ' □ 1Л» 4 □ Python ■ . Рис. 8.5. Приложение Voter осуществляет Web-опрсс «ваш любимый язык программирования» и подсчитывает количество голосов 1 // VoteCollector.Java 2 // Класс VoteCollector подсчиталает голоса,- отправленные как сообще- 3 // ния TeJctMes sages в очередь Votes, и отображает количество голосов. 4 package com.deitel.advjhtpl.jms.voter; 5 6 // Набор базовых пакетов Java 7 import j ava.awt.*; 6 import Java.awt.event.*; 9 import java.util.*; 10 11 // Пакета расширений Java 12 import 3avax.swing.*; 13 import javax.jms.*; 14 import javax.naming.*• 15 16 public class VoteCollector extends JFrame { П lfl private JPanel displayPanel; 19 private Map tallies = new HashMapO ; 20 21 // Переменные JMS 22 private QueueConnection queueConnection;
24 // Конструктор VoteCollector 25 public VoteCollector[J 26 { 27 super( "Vote Tallies" ); 28 29 Container container = getContentPane(); 30 31 // подсчитанные результаты будут отображаться в панели di splауРапе1 32 dispiayPanel = new jPanelO; 33 displayPanel.setLayout( new GridLayoutf 0, 1 ) ); 34 container.add( new JScrollPanef displayPanel ) ); 35 36 // вызов метода quit при закрытии окна 37 addWindowListener( 38 39 new WindowAdapter(} ( 40 41 public void windowClosingf WindowEvent event } { 42 quit<); 43 } 44 } 45 >; 46 47 // соединение с очередью Votes 48 try ( 49 50 // создание контекста JHDI 51 Context jndiContext = new InitialContextO; 52 53 // извлечение мастера соединения с очередью и 54 // очереди из контекста JNDI 55 QueueConnectionTactory queueConnectionFactory = 56 ( QueuaConnectionJactory } 57 jndiContext.lookup( "VOTE^FACTORI" >; 58 Queue queue = ( Queue ) jndiContext,lookup( "Votes" 59 60 // создание соединения, сеанса и получателя 61 queueConnection = 62 queueConnectionFactory.createQueueConnection(>; 63 QueueSession queueSession = 64 queueConnection.createQueueSession( false, 65 Session.AUT0_ACKNOWLEDGB ); 66 QueueReceiver queueReceiver = 67 queueSession,createReceiver( queue ); 68 69 // икициалиаация и установка слушателя сообщения 70 queueReceiver.setMessageListener( 71 new VoteListener( this } >; 72 73 // установка соединения 74 queueConnection.start(); 75 } 76 7 7 // обработка исключения JNDI при идентификации
Обмен сообщениями с помощью Java Message Service (JMS) 407 7в catch ( HamingException namingException ) { 79 namingException.printStaekTraceO; 80 System.exit( 1 ); 81 ) 82 83 // обработка исключения JMS при соединении с очередью или создании сеанса 84 catch ( JMSException jmsException ) { 85 jmsException.printStacMraceO ; 86 System.exit( 1 ); 87 J 88 89 } // конец конструктора VoteCollector 90 91 // добавление голоса к соответствующей сумме голосов 92 public void addVote( String vote ) 93 i 94 if ( tallies containsKey( vote ) ) { 9S 96 // если голос уже учтен 97 TallyPanel tallyPanel = 98 I TallyPanel ) tallies.get{ vote ); 99 tallyPanel.updateTallyO; 100 } 101 102 // добавление в GUI данных по количеству набранных голосов 103 else { 104 TallyPanel tallyPanel = new TallyPanel ( vote, 1 ) ,' 105 displayPanel.add( tallyPanel ) ; 106 tallies.put( vote, tallyPanel ); 107 validated ; 108 } 109 } 110 111 // выход из приложения 112 public void quit{) 113 { 114 if { queueConnection != null ) { 115 116 // закрытие соединения с очередью, если оно существует 117 try { 118 queueConnection.close(); 119 } 120 121 // обработка исключения JMS 122 catch ( JMSException jmsExeeption ) { 123 j maException. prill tS tacJeTrace ( > ; 124 System.exit( 1 ); 125 } 126 127 } 128 129 System.exit( 0 ); 130 131 ) // конец метода quit 132
408 Глава 8 133 // запуск приложения VoteCollector 134 public static void main( String args[] ) 135 { 136 VoteCollector voteCollector = new VoteCollector<); 131 VoteCollector.setSize( 200, 200 ); 138 VoteCollector.setvisible( true ); 139 ) 140 } Рис. 8.6. Класс VoteCollector получает и подсчитывает голоса, отданные за любимый язь,к программистов в кия Класс VoteCollector осуществляет сбор и подсчет голосов, извлеченные из очереди "Votes. Переменная tallies (строка 19) представляет собой карту соответствий (объект Map) между именами кандидатов и объектами TaJlyPaoel (рис. 8.9); в соответствии со значением этой переменной обновляется счетчик голосов при поступлении нового голоса. В строках 27-45 осуществляется компоновка GUI. Обратите внимание, что если класс VoteCollector не получил каких-либо голосов, никакая информация в GUI не отображается. В строках 48—87 устанавливается соединение с очередью Votes. В строке 51 создается контекст JNDI для извлечения объектов QueneCotuiectioiLFactory и Queue (строки 55-58). Объект QueueConnectionFactory позволяет программе создавать соединение с очередью QueueConnection (строки 61-62). Объект QueueCormection создает сеанс QueueSession (строки 63-65). Объект QueueSession, в свою очередь, создает объект QueueReceiver (строки 66-67), который получает голоса из очереди Queue. Б строках 70-71 создается новый объект VoteListener (рис. 8.8), а объект VoteListener устанавливается в качестве слушателя сообщений для объекта QueueReceiver. В строке 74 начинается установка соединения queueConnectian; с этого момента VoteListener будет обрабатывать сообщения, полученные из очереди queue. Метод addVote (строки 92-109) обновляет количество голосов и отображает его (рис. 8.7). Если имеется объект TallyPanel, соответствующий языку, за который отдан голос vote, в строках 97-99 количество голосов инкрементируется путем вызова метода updateTally. Если соответствующего объекта TallyPanel кет, в строках 104-106 в таблицу talliesMap добавляется новая запись. Программа вызывает метод quit (строки 112-131), когда клиент закрывает окно приложения. В строках 114-127 осуществляется закрытие соединения queueConnev.tion. Рис. 8.7. Класс VoteCollector подсчитывает количество голосов и отображает число голссоа отданных за различные языки программирования Класс VoteListener (рис. 8.8) реализует интерфейс MessageListener. Когда объект QueueReceiver получает сообщение, слушатель сообщения QueueReceiver обрабатывает его. Интерфейс MessageListener определяет единственный метод
Обмен сообщениями с помощью Java Message Service (JMS) 409 onMessage (строки 22-49), который программа вызывает, когда поступает новое сообщение. В строке 29 проверяется, имеет ли сообщение тип Text Message. Если да, в строках 30-31 извлекается текст сообщения; затем в строке 32 осуществляется вызов метода addVote класса VoteCollection для обновления счетчика голосов. 1 // VoteListener.java 2 // VoteListener - это слушатель сообщений для 3 // очереди Votes. Он реализует заданный метод 4 // onMessage для обновления GUI при получении 5 // очередного голоса. 6 package com.deitel.advjhtpl.jms.voter; 7 8 // Пакеты расширений Java 9 import javax.jms.*; 10 11 public class VoteListener implements Me&sageListenec \ 12 13 private VoteCollector voteCollector; 14 15 // Конструктор VoteListener 16 public VoteListener( VoteCollector collector } 17 { 18 voteCollector = collector; 19 J 20 21 // прием нового сообщения 22 public void onMessage{ Message message ) 23 ( 24 TextMessage voteHessage; 25 26 // извлечение и обработка сообщения 27 try { 28 29 if ( message instanceof TextMessage ) { 30 voteMessage = ( TextMessage ) message; 31 String vote = voteMessage.getText(); 32 voteCollector. addVote ( vote ) ; 33 34 System.out.println( "Received vote: " + vote ); 35 } 36 37 else { 38 System.out.println( "Expecting " + 39 "TextMessage object, received " + 40 message.getClass().getUame0 ); 41 } 42 } 43 44 // обработка исключения JMS при передаче сообщения 45 catch ( JMSException jmsException ) { 4 6 jmsException.printStacXTracet); 47 } 48 49 } // конец метода onMessage 50 } Рис. 8.8. Класс VoteListener принимает сообщения из очереди
410 Глава 8 Класс TallyPanel (рис. 8.9) хранит и отображает количество голосов tally для языка программирования, за который отдается голос. Метод updateTally (строки 37-41) инкрементирует значение tally на единицу. 1 // TallyPanel.java 2 // Та11Panel - это компонент GUI, который отображает 3 // имя кандидата и набранное им число голосов. 4 package com.deitel.advjhtpl. jms.voter; 5 6 // Набор базовых пакетов Java 7 import java.awt.*; 8 9 // Пакеты расширений Java 10 import javax.swing.*; 11 12 public class TallyPanel extends JPanel { 13 14 private JLabel nameLabel; 15 private JTextField tallyField; IS private String name; 17 private int tally; 18 19 // Конструктор TallyPanel 20 public TallyPanel{ String voteHame, int voteTally ) 21 { 22 name = VOteName; 23 tally = voteTally; 24 25 nameLabel = new JLabel( name ); 26 tallyField = 27 new JTextField( Integer.toString{ tally ), 10 ); 28 tallyField.setEditablet false ); 29 tallyField.setBackground( Color,white ); 30 31 add( namaLabel ); 32 add I tallyField ); 33 34 \ // конец конструктора TallyPanel 35 36 // прибавление единицы к числу набранных голосов 37 public void updateTally() 38 ( 39 tally++; 40 tallyField.setText( integer.toString( tally ) ); 41 } 42 } Рис. 8.9, Класс TallyPanel отображает имя кандидата и набранное им количество голосов 8.3.4. Приложение Voter: настройка и выполнение Чтобы выполнить приложение, введите следующие команды в приглашение командной строки: 1. Запустите сервер J2EE в окне команд: j2ee -verbose
Обмен сообщениями с помощью Java Message Service (JMS) 411 2. В новом окне команд создайте очередь Votes: }2eeadmin —adcUJmsDestination Votes queue 3. Проверьте, что очередь была создана: j2eeadmin -listJmsDestination 4. Создайте мастер соединений: j2eeadmin -addJmsFactory VOTE_FACTORY queue 5. Запустите класс VoteCollector: Java -classpath %J2EE_HOME%\lib\j2ee.jar;. -Djms.properties = %J2EE_HOME%\config\jms_client.properties com.deitel.advjhtpl.jms.voter.VoteCollector 6. Запустите класг. voter в новом окне команд: java -classpath %J2EE_H0ME%\lib\j2ee.jar;. -Djms.properties = %J2EE_HQME%\config\jms_client.properties com.deitel.adv^htpl.jms.voter.Voter После выполнения приложения мастер соединений может быть удален с помощью команды: j2eeadmin -removeJmsFactory VOTE_FACTORY Чтобы удалить тематический раздел, воспользуйтесь командой: j2eeadmin -removeJmsDestination Votes Чтобы завершить работу сервера J2EE, воспользуйтесь командой: j2ee -stop 8.4. Обмен сообщениями в модели «издатель/подписчик» Модель обмена сообщениями «издатель/подписчик» (рис. 8.10) дает возможность нескольким клиентам соединяться с тематическим разделом на сервере и отправлять и принимать сообщения. Установив соединение, клиент может публиковать сообщения или подписаться на тематический раздел. Когда клиент публикует сообщение, сервер отправляет это сообщение тем клиентам, которые подписались на данный тематический раздел. Клиент может иметь подписку двух типов: кратковременную и долгосрочную. При кратковременной подписке сообщения получаются только во время активного состояния подписчика. При долгосрочной подписке сообщения могут быть получепы и при неактивном состоянии: сервер хранит сообщения, отправленные в тематический раздел, когда подписчики неактивны, и отправляет эти сообщения клиенту, когда тот вновь активизирует подписку. Заметим, что если клиент указывает селектор для фильтрадии сообщений, сервер хранит только те сообщения, которые соответствуют задаваемому этим селектором условию. Издатель Сообщение Издатель Сообщение Тема Сообщение Подписчик Сообщение Подписчик Рис. 8.10. Модель обмена сообщениями «издатель/подписчик»
412 Глава 8 8.4.1. Приложение Weather: обзор Рассмотрим пример обмена сообщениями в модели «издатель/подписчик», в котором используется кратковременная подписка. Следующее приложение публикует сообщения в тематическом разделе Weather на сервере. Эти сообщения содержат информацию о погоде для различных клиентов на территории США. Информация извлекается со страницы Национальной метеорологической службы США (NWS) (iwin.Jiws.uoaa.gov/iwin/us/tTaveler.html). Класс WeatherPublisher (раздел 8.4.2) извлекает последние сведения о погоде и публикует их в виде сообщений в тематическом разделе. Класс WeatherSnbscriber (раздел 8.4.3) определяет графический пользовательский интерфейс, который дает возможность пользователю выбирать города, сведения о погоде в которых его интересуют. Класс WeatherS ubscriber осуществляет подписку на тематический раздел Weather и принимает сообщения, относящиеся к выбранным городам, используя селектор сообщений. Структура приложения представлена на рис. 8.11. Национальная метеорологическая служба HTML flea the г Publishe r ОЬ э еctMaa»адя Тематический раздел Weather ObjectMessage ОЬj ectMessage Weather-Subscriber WeatherSubscrib»r Рис. 8.11. Схема приложения Weather 8.4.2. Приложение Weather: часть, относящаяся к издателю Класс Weather Publisher (рис. 8.L2) извлекает последние сведения о погоде с сайта Национальной метеорологической службы США и публикует их в виде сообщений в тематическом разделе Weather. Сообщение типа ObjectMessage содержит информацию о погоде в каждом из городов. Свойство City объекта ObjectMessage определяет соответствующий город. Класс WeatherSubscriber использует свойство City в своем селекторе сообщений. 1 // Weather-Publisher, Java 2 // Weather/Publisher извлекает информацию о погоде с сайта Национальной 3 // метеослужбы (NWS) и публикует их в тематическом разделе Weather 4 // в виде сообщений ObjectMessage, содержащих компоненты WeatherBeans. 5 // Название города используется в свойстве City в заголовке сообщения, 6 package com.deitel.advjhtpl.jms.weather; 7 8 // Набор базовых пакетов Java
Обмен сообщениями с помощью Java Message Service (JMS) 413 9 import java.io-*," 10 import Java.net. *,- 11 import java.util.*; 12 13 // Пакеты расширений Java 14 import javax.jms.*; 15 import javax.naming.*; 16 17 // Пакеты Deitel 18 import com.deitel.advjhtpl.rmi.weather.WeatherBean; 19 20 public class HeatherPublisher extends TimerTask { 21 22 private BufferedReader in; 23 private TopicConnection topicConnection; 24 25 // Конструктор WeatherPublisher 26 public WeatherPublisher() 27 { 28 // обновление информации о погоде каждую минуту 29 Timer timer = new Timer(); 30 timer.scheduleAtFixedRate( this, Qr 60000 ); 31 32 // разрешить пользователю выйти иа приложения 33 InputStreamReader inputStreamReader = 34 new InputStreamReader( System.in ); 35 char answer = '\0',- 36 37 // ожидание ввода пользователем символа q или Q 38 while ( ! ( ( answer == 'q' ) I I ( answer = 'Q' ) ) } { 39 40 // чтение символа 41 try { 42 answer = ( char ) inputStreamReader.read(); 43 } 44 45 // обработка исключения при вводе/выводе 4 6 catch ( lOException ioException ) ( 47 ioException,printstaclcTrace () ; 48 System, exit ( 1 ) ,- 49 } 50 51 ) // конец цикла while 52 53 // закрытие соединений 54 try ( 55 56 // закрытие соединении topicConnection, если оно существует 57 if ( topicConnection != null ) ( 58 topicConnection.close (); 59 ) 60 61 in.closet); // закрытие соединения с ИеЬ-сервером NWS 62 timer, cancel {) ,- // останов таймера 63 } 64
414 Глава 8 65 // обработка исключения JMS при закрытии соединения с тематическим разделом 66 eetch ( JMSException jmsException ) ( 67 jmsExeeption.printstackTrace(); €8 System.exit( 1 ); 6» ) 70 71 // обработка исключения ввода/вывода при закрытии 72 // соединения с Web-сервером NWS 73 catch ( lOExoeption ioException ) { 74 ioException.printStackTrace(); 75 System.exit( 1 ); 76 } 77 78 System.exit t 0 ') ; 79 80 } // конец конструктора WeatherPublisher 81 82 // получение информации о породе с сайта HWS 83 public void run() 84 { 85 // соединение с тематическим разделом Weather 66 try { 87 System.out.printing "Update weather information..." ); 88 89 // создание контекста JHDI 90 Context jndiContext = new initialContext(); 91 String topicNaroe = "Heather"; 92 93 // получение мастера соединения с тематическим 94 // разделом и раздела иэ контекста JNDI 95 TopicConnectionFactory topicConnectionFactory - 96 ( TopicConnectionFactory ) 97 jndiContext.lookup( "WEATHER_FACTORY" ); 9$ 99 Topic topic = 100 ( Topic } jndiContext.lookup( topicName ),- 101 102 // создание соединения, сеанса, издателя и сообщения 103 topicConnection = 104 topicConnectionFactory.createTopicConnection(); 105 106 TopicSession topicSession = 107 topicConnection.createTopicSession( false, 108 Sessiun.AUTO_ACKHOWLEDGE ); 109 110 TopiePublisher topicPublisher = 111 topicSession.oreatePublisber( topic ); 112 113 ObjectMeasage message = 114 topicSession.createObjectMessage(); 115 116 // соединение с сайтом Национальной метеослужбы и публикация 117 // информации о погоде в тематическом разделе 118 119 // страница сайта метеослужбы National Heather Service Travelers Forecast
Обмен сообщениями с помощью Java Message Service (JMS) 415 120 URL url = new ORL( 121 "http://iwin.nws.rtoaa.gov/iwin/us/traveler.html" ); 122 123 // настройка текстового потока ввода для чтения содержимого Web-страницы 124 in = new BufferedReader( 125 new InputstreamReadar( url.openStream() ) >,- 126 127 // разделитель, помогавший определить начало данных на Web-странице 128 String separator = "TAV12"; 129 130 // нахождение символа-разделителя на Web-странице 131 while ( !in.readLine().startsWith( separator ) ) 132 ; // отсутствие действий 133 134 // строки, представляющие на Web-странице Travelers 135 // Forecast заголовки для погоды в дневное и ночное время 136 String dayHeader = 137 "CITY WEA HI/LO WEA HI/LO"; 138 139 String nightHeader = 140 "CITY WEA LO/HI WEA LO/HI"; 141 142 String inputLine = "",- 143 144 // нахождение заголовка, представляющего начало данных О породе 145 do { 146 inputLine = in.readLine 0; 147 } 148 149 while ( !inputLine.equals( dayHeader ) ЬЬ 150 !inputLine.equals( nightHeader ) ); 151 152 // создание объектов WeatherBean с данными по каждому из 153 // городов и публикация их в разделе Heather с использованием названия города в качестве типа сообщения 154 inputLine = in.readLine(); // получение информации для первого города 155 156 // Часть строки inputLine, содержащая нужные нам данные, 157 // должна иметь длину, равную 28 символам. Бели длина 158 // строки превышает 28 символов, выполнить обработку данных. 159 while ( inputLine.length() > 28 ) ( 160 161 // создание объекта WeatherBean для города 162 // первые 16 символов относятся к названию города 163 // следующие шесть символов относятся к описанию погоды 164 // следующие шесгь символов относятся к ночной и дневной температуре HI/L0 165 WeatherBean weather — new WeatherBean( 166 inputLine.substring( 0, 16 ).trim(), 167 inputLine.substring( 16, 22 ).trim(), 168 inputLine.substring( 23, 29 ).trim() ); 169 170 // Публикация объекта WeatherBean с указанием названии
416 Глава 8 171 // города в качестве свойства сообщения. Это свойство 172 // будет использоваться клиентами при выборе города. 173 message.setObject{ weather ); 174 message.setStringProperty( "City", 175 weather.ge tCityName() ); 176 topicPublisher.publish( message ); 177 П8 System.out.pxintlnt "published message for city: " 179 + weather. getCityNameO ); 1B0 181 inputLine = in.readLine(); // получение данных для еледужщего города 182 J 183 184 System.out.println{ "Weather information updated." ); 185 186 } // конец блока try 187 188 // обработка исключения JNDI при идентификации 189 catch ( NamingException namingException ) { 190 namingException.printStackTrace(); 191 System.exit( 1 ); 192 } 193 194 // обработка исключения JMS при соединении, 195 ff создании сеанса, публикации или сообщении 196 catch ( JMSException jmsException ) { 197 j msException.prints tackTrace(); 198 System.exit( 1 ) ; 199 J 200 201 // обработка ошибки при соединении с сайтом Национальной мете ослужбы 202 catch ( java.net.ConnectException connectException ) { 203 connectException.printStackTrace(); 204 System.exit( 1 ); 205 } 206 207 // обработка других исключений 208 catch ( Exception exception } { 209 exception.printStackTrace(J; 210 System, exit ( 1 > ; 211 } 212 213 } // конец метода run 214 215 // запуск приложения WeatherPubiisher 216 public static void main( String args[] ) 217 { 218 System.err,println( "Initializing server..An" + 219 "Enter 'q' or 'Q' to quit" ); 220 221 WeatherPubiisher publisher = new WeatherPubiisher0; 222 } 223 } Рис. 8.12. Класс WeatherPubiisher публикует сообщения в тематическом разделе Weather
Обмен сообщениями с помощью Java Message Service (JMS) 417 Класс WeatherPublisher публикует информацию о погоде в городах США в тематическом разделе Weather на сервере. Каждую минуту класс WeatherPublisher получает информацию о погоде с сайта Национальной метеорологической службы и публикует отдельные сообщения о погоде в каждом городе. На рис. 8.13 показана работа сервера в действии. Метод run (строки 83—213), определяемый классом TimerTask, осуществляет соединение с тематическим разделом и публикует сообщения. В строке 90 создается контекст JNDI, в котором класс WeatherPublisher ищет объекты TopicConnectionFactory и Topic (строки 95-100). Объект TopicCon- nectionFactory — его должен создать администратор сервера —■ создает объект TopicConnection (строки 103-104). Затем объект TopicConnection создает объект Topic Session (строки 106-108). В строках 110-111 из объекта Topic Session извлекается объект TopicPublisher. Объект TopicSfission также создает объект Object- Message (строки 113-114); объект ObjectMessage будет содержать объекты Wea- therBean, которые хранят информацию о погоде для заданного города. В строках 165-168 создается объект WeatherBean, содержащий данные, полученные с сайта Национальной метеорологической службы. Метод setObject класса ObjectMessage помещает объект WeatherBean в сообщение. В строках 174-175 используется метод setStringProperty класса Message для задания в качестве значения строкового свойства City названия города. Подписчик может использовать это свойство для фильтрации сообщений. Наконец, метод TopicPuhlisher публикует сообщение в тематическом разделе topic (строка 176). Рис. 8.13. Класс WeatherPublisher публикует сообщения с обновленной информацией о погоде 8.4.3. Приложение Weather: часть, относящаяся к подписчику Югасс WeatherSubscriber (рис. 8.14) осуществляет подписку на тематический раздел Weather для получения свежей информации о погоде для выбранных городов. Класс WeatherSubscriber создает графический интерфейс пользователя, который дает возможность пользователю выбирать города, и отображает погодные условия в указанных городах. Как и класс WeatherPublisher, класс WeatherSubscriber использует контекст JNDI для получения объектов TopicConnectionFactory и Topic (строки 55-65). Объект TopicConnectionFactory создает объект TopicConnection (строки 68-69), который создает объект TopicSession (строки 72-73). В строке 76 новый экземпляр класса WeatherListener (рис. 8.17) инициализируется в качестве слушателя сообщения для получателя информации иа тематического раздела (который был
418 Глава 8 создан в методе getWeather). В строках 90-135 настраивается пользовательский интерфейс; погодные условия отображаются с помощью класса WeatherDisplay (рис. 8.18). Графический интерфейс клиента представлен на рис. 8.15. 1 // WeatherSubscriber.java 2 // Класс WeatherSubscriber определяет GDI для клиента, 3 // запросившего информацию о погоде в различных городах. 4 // WeatherSubscriber извлекает информацию о погоде из раздела Weather. 5 // Тело каждого сообщения содержит объект WeatherBean. 6 // Заголовок сообщения содержит строковое свойство City, 7 // которое позволяет клиентам выбирать нужные города, в package com.deitel.advjhtpl,jms.weather; 9 10 // Набор базовых пакетов Java 11 import Java.awt.*; 12 inport java.awt,event,*; 13 14 // Пахеты расширений Java 15 import javax.swing.*; I б import j avax.naming.*; II import jarvax. jms. * ; 18 19 public class WeatherSubscriber extends JFrama { 20 21 // переменные GDI 22 private WeatherDisplay weatherDisplay; 23 private JList citieeList; 24 25 // города, для которых в тематической разделе Weather 26 // имеется свежая информация о погоде 27 private String cities £J = { "ALBANY NY", "ANCHORAGE", 28 "ATLANTA", "ATLANTIC CITY", "BOSTON", "BUFFALO", 29 'BURLINGTON VT", "CHARLESTON WV", "CHARLOTTE", "CHICAGO", 30 "CLEVELAND", "DALLAS FT WORTH", "DENVER", "DETROIT", 31 "GREAT FALLS", "HARTFORD SPGFLD", "HONOLULU", 32 "HOUSTON INTCNTL", "KANSAS CITY", "LAS VEGAS", 33 "LOS ANGELES", "MIAMI BEACH", "MPLS ST PAUL", "NEW ORLEANS", 34 "NEW YORK CITY", "NORFOLK VA", "OKLAHOMA CITY", "ORLANDO", 35 "PHILADELPHIA", "PHOENIX", "PITTSBURGH", "PORTLAND ME", 36 "PORTLAND OR", "RENO" ); 37 38 // переменные JMS 39 private TopieConnection topicConnection; 40 private TopicSession topicSession; 41 private Topic topic; 42 private TopicSubscriber topicSubscriber; 43 private WeatherListener topicListener; 44 45 // конструктор WeatherSubscriber 46 public WeatherSubscriber{) 47 { 48 super( "JMS WeetherSubscribet..." ); 49 weatherDisplay = new WeatherDisplayО; 50 51 // настройка контекста JNDI и соединений JMS 52 try {
Обмен сообщениями с помощью Java Message Service (JMS) 419 53 54 // создание контекста JHDI 55 Context. jndiContext = new InitialContext () ; 56 57 // получение мастера соединения с разделом 58 //из контекста JNDI 59 TopicConnectionFactory topicConnectionFactory = 60 { TopicConnectionFactory ) jndiContext.lookup( 61 "WEATHER__FACTORY" ) ; 62 63 // извлечение хеиы из контекста JNDI 64 String topicName = "Weather"; 65 topic = ( Topic ) jndiContext.lookup( topicName ); 66 67 // создание соединения с тематическим разделом 68 topicConnection = 69 topicConnectionFactory.createTopicConnection(); 70 71 // создание сеанса 72 topicSession = topicConnection.createTopicSession( false, 73 Session.AUTO_ACKNOWLEDGE ); 74 7 5 // инициализация слушателя 76 topicListener = new WeatherListener( treatherDisplay ); "Л } 78 79 // обработка исключения JHDI при идентификации SO catch ( NamingException namingBxception ) { 81 namingException.printstackTrace(); 82 } 83 84 // обработка исключения JMS при соединении с разделом или создании сеанса 85 catch ( JMSException jmsException ) { 86 jmsException.printStackTra.ceO; 87 } 88 89 // компоновка интерфейса пользователя 90 Container container = getContentPane(); 91 container.setLayout( new BorderLayoutО ); 92 93 JPanel selectionPanel = new JPanel(); 94 selectionPanel.setLayout( new BorderLayout() ); 95 96 JLabel selectionLabel = new JLabel( "Select Cities" ); 97 selectionPanel.add( selectionLabel, BorderLayout.NORTH ); 90 99 // создание списка городов, для которых пользователи 100 // могут запрашивать свежу» информацию о породе 101 citiesList = new JList{ cities ); 102 selectionPanel.add{ new JSего11Pane{ citiesList ), 103 BorderLayout.CENTER ); 104 105 JButton gstWeatherButton = new JButton( "Get Weather..." }; 106 selectionPanel.add( getWeatherButton, BorderLayout.SOUTH ); 107
420 Глава 8 108 // вызов метода getWeather при нажатии кнопки getWeatherButton 109 getWeatherButton.addActionListener { 110 111 пен ActionListener () { 112 113 public void aetionperformed ( ActionEvent event ) 114 { 115 getWeather () ,- 116 } 117 } 118 119 } ; // конец обращения к addActionListener 120 121 container.add( selectionPanel, BorderLayout.WEST ); 122 container.»dd( weatherDisplay, BorderLayout.CENTER ); 123 124 // вызов метода quit при закрытии окна 125 addWindowListener( 126 127 new WindowAdapterO ( 128 129 public void windowClosing( WindowEvent event ) 130 { 131 quit(); 132 } 133 } 134 135 ); // конец обращения к addWindowListener 136 137 } // конец конструктора WeatherSubscriber 138 139 // получение информации о породе в выбранных городах 140 public void getWeather() 141 { 142 // извлечение индексов для выбранных городов 143 int seleetedlndices[] = citiesList.getSelactedlndices{); 144 145 if ( seleetedlndices.length > 0 ) { 146 147 // если подписчик раздела существует, 148 // значит метод ранее уже вызывался 149 if ( topicSubscriber ! = null ) ( 150 151 // закрытие предыдущего подписчика на раздел 152 try { 153 topicSubscriber.close(); 154 J 155 156 // обработка исключения JMS 157 catch ( JMSException imsException ) { 158 jmaException.printStacJcTrace () ; 159 } 160 161 // очистка экрана, содержащего данные для предыдущих городов 162 weatherDisplay.clearCities();
Обмен сообщениями с помощью Java Message Service (JMS) 421 163 ) 164 165 // создание селектора сообщений для извлечения заданных городов 166 StringBuffer messageSelector = new StringBufferО; 167 messageSelector.append( 168 "City = ' " +■ cities [ selectedlndices [ 0 ]]+'"" ) 169 170 for ( int i = 1; i < selectedlndices.length; i++ ) { 171 messageSelector.append{ " OR City = '" + 172 cities[ selectedlndices[ i ] ] + "'" ); 173 } 174 175 // создание подписчика на раздел и подписки 176 try { 177 topicSubscriber = topicSession.createSubscriber( 178 topic, messageSelector.toString(), false ); 179 topicSubscriber.setMessageListener( topicListener ) 180 topicConnection.start(); 181 182 JOptionPane.showMessageDialog( this, 183 "A weather update should be arriving soon..." ); 184 } 185 186 // обработка исключения JMS 187 catch ( JMSException jmsException ) { 188 jmsException.printStackTr.ace () ; 189 } 190 191 } // конец блока if 192 193 } // конец метода getWeather 194 195 // выход из приложения WeatherSubscriber 196 public void quitO 197 { 198 // закрытие соединения и подписки на тематический раздел 199 try { 200 201 // закрытие подписчика на раздел 202 if t topicSubscriber (= null J { 203 topicSubscriber.close 0 ; 204 } 205 206 // закрытие соединения с разделом 207 topicConnection.close(}; 208 } 209 210 // обработка исключения JMS 211 catch ( JMSException jmsException ) { 212 jmsException.pirirttStacicTrace() ; 213 System.exitt 1 ) ; 214 } 215 216 System.exit( 0 ); 217
422 Глава 8 218 } // конец метода quit 219 220 // запуск приложения WeatherSubscriber 221 public static void main{ String args [] ) 222 { 223 WeatherSubecriber subscriber — new WeatherSubecriber(); 224 subscriber.pack(); 225 subscriber.setVisible{ true ); 226 } 227 } Рис. 8.14. Класс WeatherSubscriber дает возможность пользователю принимать свежую информацию о погоде Рис. 8.15. Выбор городов для получения свежей информации о погоде Программа вызывает метод getWeather (строки 140-193), когда пользователь нажимает кнопку Get Weather. Если метод ранее уже вызывался (т.е. пользователь до этого уже нажимал эту кнопку), в строках 149—163 осуществляется закрытие предыдущего объекта TopicSubscriber, чтобы новый подписчик мог выполнять фильтрацию для выбранных городов с помощью селектора сообщений. Если клиент задал селектор сообщений для подписки, сервер будет отправлять только те сообщения, которые удовлетворяют условиям фильтрации, установленным клиентом. В строках 16G-173 создается селектор message Selector, чтобы отображать погодные условия для выбранных городов. В основу синтаксиса селектора message Set ее tor положен синтаксис SQL92 (подробности содержатся в разделе Message документации javadoc). В строках 177 178 из объекта TopicSession создается объект TopicSubscriber, которому в качестве параметров передаются объект Topic и селектор messageSelector. Третий параметр, имеющий значение false, указывает, что подписчик может принимать сообщения, опубликованные в течение его собственного соединения. В строке 179 в качестве слушателя сообщений для TopicSubscriber устанавливается topicListener. Слушатель сообщений для TopicSubscriber обрабатывает новые сообщения по мере их поступления. Наконец, в строке 180 открывается соединение TopicConnection; после открытия соединения класс TopicSubscriber будет принимать сообщения, опубликованные в этом тематическом разделе. Когда пользователь закрывает окно приложения, в строках 202-204 осуществляется закрытие объекта TopicSubscriber, если таковой существует. В строке 207 закрывается соединение TopicConnection. Класс Weather-Listener (рис. 8.17) реализует интерфейс MessageListener. В этой связи он определяет метод onMcssage (строки 26-57) для получения входящих сообщений. Когда поступает новое сообщение, в строке 32 проверяется, имеет ли сообщение надлежащий тип: ObjcctMessage. Если да, метод getObject извлека-
Обмен сообщениями с помощью Java Message Service (JMS) 423 ет объект WeatherBean из объекта Object Message. Объект WeatherBean затем передается объекту WeatherDisplay, который отображает соответствующую информацию пользователю. На рис. 8.16 показано окно приложения после получения обновленной информации о погоде. Рис. 8.16. Получение классом WeatherSubscrtber обновленной информации о погоде 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 П 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // WeatherListener.Java // WeatherListenei: - это слушатель типа MessageListener для // подписки на тематический раздел Weather. Он реализует // указанный метод onMessage для обновление GC7I // информацией о погоде в заданной городе. package com.deitel.advjhtpl.jms.weather; // Пакеты расширений Java import javax.jms.*; import javax.swing.*; // Пакеты Deitel import com.deitel.advjhtpl. rail.weather.WeatherBean; public class HeatherListener implements MessageListener { private WeatherDisplay WeatherDisplay; // Конструктор WeatherListener public WeatherListener( WeatherDisplay display ) { ) WeatherDisplay = display; // прием нового сообщения public void onMessage( Message message ) i II извлечение и обработка сообщения try t // проверка, что сообщение имеет тип ObjectMessage if ( message instanceof ObjectMessage ) { // получение компонента WeatherBean из сообщения ObjectMessage ObjectMessage ObjectMessage = ( ObjectMessage ) message;
424 Глава 8 3*7 WeatherBean weatherBean = 38 ( WeatherBean ) objectMessage.getobject(); 39 40 // добавление компонента WeatherBean для его отображения 41 weatherDisplay.addltem( weatherBean ); 42 43 } // конец блока if 44 45 else { 46 System.out.println( "Expected ObjectMessage," + 47 " but received " + message.getclass{).getName {) }; Л8 } 49 50 } // конец блока try 51 52 // обработка исключения JMS от сообщения 53 catch ( JMSException jrosException ) { 54 jmsException.printstackTraee(); 55 } 56 $7 } // хонец метода onMessage 58 } __ Рис, 8.17. Класс WeatherUstener осу теста ляет подписку на тематический раздел Weather для получения прогноза погоды Класс WeatherDisplay (рис. 8.18) отображает объекты WeatherBean в списке weatherList. Список weatherList использует классы WeatherListModel и Wea- tberCellRenderer для отображения объектов WeatherBean. Метод addltem (строки 47-65) добавляет указанный объект WeatherBean для его отображения, если информация о соответствующем городе в данный момент не отображена. Если информация о городе уже отображена, в строках 56-58 удаляется предыдущий объект WeatherBean для города и добавляется обновленный объект WeatherBean. 1 // WeatherDisplay.Java 2 // WeatherDisplay расширяет класс Jpanel для отображения результатов 3 // клиентского запроса не получение информации о погоде. 4 package com.deitel.advjhtpl.jus,weather; 5 6 /V Набор базовых пакетов Java 1 import j ava.awt.*; 8 import java,awt.event,*; 9 import java.util,*; 10 11 // Пакеты расширений Java 12 import javax.swing.*; 13 14 // Пакета Deitel 15 import com.deitel.advjhtpl.rmi.weather.*; 16 11 public class WeatherDisplay extends JPanel { 18 19 // Объекты WeatherListModel и Нар дли хранения компонентов WeatherBean 20 private WeetherListModel WeatherListModel; 21 pri.va.tA Hap ve&therltems;
Обмен сообщениями с помощью Java Message Service (JMS) 425 22 23 // Конструктор HeatherDisplay 24 public WeatherDisplay(J 25 { 26 setLay<rat( new BorderLayoutf) ); 27 28 Image Icon header-image = new ImageIcon( 29 HeatherDisplay,class.getResource{ 30 "images/header.jpg" ) ); 31 add( new Jbabel( headerlmage ), BorderLayout.NORTH ); 32 33 // использование списка JList для отображения 34 // свежей информации о погоде для заданных городов 35 WeatherListModel = new WeatherListModel(); 36 Jbist weatherJList = new JList( WeatherListModel ); 37 weatherJList.setCellRenderer( new WeatherCellRenderer() ); 3B 39 add( new JScrollPane( weatherJList ), BorderLayout.CENTER ); 40 41 // сохранение компонентов WeatherBean в хэш HasnMap 42 weatherltems = new HashHap()• 43 44 } // конец конструктора WeatherDisplay 45 4 6 // добавление компонента WeatherBean для его отображения 47 public void addltetn( WeatherBean weather ) 48 { 49 String city = weather.getCityName(); 50 51 // проверка, не отображена ли уже информация для города 52 if ( weatherltems.containsKey( city ) ) { 53 * 54 // если город присутствует в таблице Иар, и, следовательно, 55 // информация для него уже отображается, удалить объект WeatherBean 56 WeatherBean previousWeather = 57 ( WeatherBean ) weatherltems,remove( city ); 58 WeatherListModel.remove( prsviousWeather ); 59 } 60 61 // добавление компонента WeatherBean в таблицу Иар и в модель WeatherListModel 62 WeatherListModel.add( weather ); 63 weatherltems.put{ city, weather ); 64 65 } // конец метода addltero 66 67 // очистка отображаемой информации для всех городов 68 public void clearCities{) 69 { 70 weatherltems .ciear(); 71 WeatherListModel.clear(); 72 } 73 ) Рис. 8.18. Класс WeatberDisplay отображает объекты WeatherBean в списке JList с использованием объекта WeatherCellRenderer
426 Глава 8 8.4.4. Приложение Weather: настройка и выполнение Чтобы выполнить приложение, введите следующие команды в приглашение командной строки: 1. Запустите сервер J2EE в окне команд: j2ee —verbose 2. В новом окне команд создайте тематический раздел Weather: j2eeadmin -addJmsDestination Heather topic 3. Проверьте, что этот тематический раздел был создан: j2eeadmin -listJmsDestination 4. Создайте мастер соединений: j2eeadinin -addJmsFactory WEATHERJFACTORY topic 5. Запустите класс WeatherPublisher: Java -classpath %J2BE_HOME%\lib\j2ee.jar;. -Djms.properties = %J2EE_HOME%\oonfig\jms_client.properties com.deitel.advjhtpl,jms.weather.WeatherPublisher 6. Запустите класс WeatherSubscriber в новом окне команд: Java -classpath, %J2EE_HOME%\lib\j2ee.jar;. -Djms.properties = %J2EE_H0ME%\config\jms_client.properties com.deitel.advjhtpl.jms.weather.WeatherSubscriber После выполнения приложения мастер соединений может быть удален с помощью команды: j2eeadmin -removeJmsFactory WEATHER_FACTOBY Чтобы удалить тематический раздел, воспользуйтесь командой: j2eeadmin -removeJmsDestination Heather Чтобы завершить работу сервера J2EE, воспользуйтесь командой: j2ee -stop 8.5. Компоненты Enterprise JavaBeans, управляемые сообщениями EJВ-компоненты, управляемые сообщениями представляют собой новый тип компонентов Enterprise JavaBeans, предусмотренных в пакете Enterprise Java- Beans версии 2.0, который входит в состав Java 2 Enterprise Edition 1.3. Компоненты, управляемые сообщениями, способны обрабатывать сообщения JMS, помещенные в очередь или в тематический раздел. При получении сообщения контейнер EJB использует любые доступные экземпляры конкретного компонента, управляемого сообщениями, чтобы обработать сообщение. Здесь есть аналогия с тем, как контейнер EJB использует любые экземпляры сеансового компонента EJB без состояния для обработки клиентского запроса. Поскольку может быть использован любой экземпляр EJB-компонента, управляемого сообщениями, компоненты, управляемые сообщениями, не могут быть отнесены к конкретному клиенту и не должны хранить информацию о состоянии клиента. Заметим, что любой заданный экземпляр EJB-компонента может обрабатывать сообщения от нескольких клиентов. В отличие от сеансовых компонентов и компонентов-сущностей (для которых разработчики должны предоставить собственный и удаленный интерфейсы), для компонентов, управляемых сообщениями, разработчики должны предоставить только класс реализации компонента.
Обмен сообщениями с помощью Java Message Service (JMS) 427 8.5.1. Приложение Voter: обзор В этом разделе представлена реализация приложения Voter кз раздела 8.3, в которой для подсчета количества голосов, поступивших в очередь Votes, используются компоненты, управляемые сообщениями. Класс Voter (рис. 8.4), который помещает сообщения с отданными голосами в очередь, остается точно таким же — это важное преимущество слабосвязанных приложений. Отправитель просто посылает сообщения в очередь вне зависимости от реализации получателя. В данном случае принимающая часть приложения (см. раздел 8.5.2) представляет собой компонент, управляемый сообщениями. Компонент-сущность Candidate (рис. 8.20, рис. 8.21 и рис. 8.22) представляет определенного кандидата, за которого могут проголосовать пользователи. Компонент-сущность хранит количества голосов в базе данных Voting. Компонент VoteCoIiectorEJB, управляемый сообщениями (рис. 8.23), использует EJB-компонент Candidate для обновления количества голосов при поступлении в очередь Votes нового сообщения с отданным за определенного кандидата голосом. Класс TallyDisplay (рис. 8.24) осуществляет доступ к EJB Candidate, чтобы отобразить GUI с текущими значениями подсчитанных голосов, извлеченными из базы данных. Структура приложения показана на рис. 8.19. Voter Voter TextMessage Тех tMessage Votes очередь TextHessage VoteColleсtorEJB Рис. 8.19. Структура приложения Voter 8.5.2. Приложение Voter: принимающая сторона EJB сущность Candidate представляет определенного кандидата, за которого могут голосовать пользователи, и общее количество голосов, отданных за этого кандидата. Собственный интерфейс CandidateHome (рис. 8.20) определяет методы findByPri- maryKey (строки 15-16) и findA 11 Candidates (строки 19-20) для нахождения, соответственно, указанного кандидата Candidate или коллекции Collection всех кандидатов Candidate. Метод create (строки 23-24) создает новый EJB-компонент Candidate с указанным именем CandidateName и с нулевым количеством голосов. Удаленный интерфейс Candidate (рис. 8.21) определяет метод incrementVote- Count для добавления нового голоса за кандидата. Метод getVoteCount (строка 18) возвращает текущее количество голосов, набранное кандидатом. Метод getCandi- dateName (строка 21) возвращает имя кандидата. 1 // CandidateHome.Java 2 // CandidateHome - это собственный интерфейс для компонента Candidate. 3 package cojn.deitel.advjhtpl.jms.mdb; 4 5 // Набор базовых библиотек Java
428 Глава 8 6 import java.rmi.*; 7 import java.util,*; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public interface CandidateHome extends EJHHome { 13 14 // поиск кандидата с заданный именем 15 public Candidate findByPrimaryKey( String candidateName ) IE throws RemoteException, FinderException; 17 IS // поиск всех кандидатов 19 public Collection f indAHCandidates () 20 throws RemoteException, FinderException; 21 22 // создание нового EJB-компонента Candidate 23 public Candidate create( String candidateName ) 24 throws RemoteException, CreateException; 25) Рис 8,20. Собственный интерфейс CandidateHome для EJB-компонента Candidate 1 // Candidate.Java 2 // Candidate - это удаленный интерфейс для EJB-компонента 3 // Candidate, который хранит количество набранных голосов. 4 package com.deitel.advjhtpl.jms.mdb; 5 6 // Набор базовых библиотек Java 7 import Java.rmi.RemoteException; 8 9 // Стандартные расширения Java 10 import javax.ejb.EJBObject; 11 12 public interface Candidate extends EJBQbject { 13 14 // добавление голоса за этого кандидата 15 public void incrementVoteCount() throws RemoteException; 16 17 // получение общего количества голосов для этого кандидата 18 public Integer getVoteCount() throws RemoteException; 19 20 // получение имени кандидата 21 public String getCandidateNameO throws RemoteException; 22 J Рис, 8,21. Удаленный интерфейс Candidate для EJB-компонента Candidate Класс CandidateEJB (рис. 8.22) реализует EJB-сущность Candidate. EJB-ком- понент Candidate использует персистентность, управляемую коптейиером, для хранения информации о кандидате в базе данных. В строках 17-18 осуществляется обновление управляемых контейнером полей voteCoimt и name для сохранения, соответственно, общего количества голосов, набранных кандидатом, и его имени. Метод IncrementVoteCount (строки 21-25) инкрементнрует число голосов, набранных кандидатом. Метод getName (строки 34-37) возвращает имя кандида-
Обмен сообщениями с помощью Java Message Service (J MS) 429 та. Метод ejbCreate (строки 40-47) создает новый EJB- компонент Candidate, устанавливает имя паше кандидата и инициализирует счетчик voteConnt, сбрасывая его в нуль. 1 J/ CandidateEJB.Java 2 // CandidateEJB - эхо компонент-сущность EJB, который использует 3 // персистентность, управляемую контейнером, для хранения имени , кандидата и навранного им числа голосов 4 package com.deitel.advjhtpl.jms.radb; 5 6 // Набор базовых пакетов Java 7 import java.xmi.RemoteException; 8 9 // Стандартные расширения Java 10 import javax.ejb.*; 11 12 public class CandidateEJB implements EntityQean ( 13 14 private EntityContext entityContext; 15 16 // поля, управляемые контейнером 17 public Integer voteCount; 19 public String name; 19 20 // добавление голоса за этого кандидата 21 public void incrementVoteCount() 22 ( 23 int newVoteCount = voteCount.intValue() + 1; 24 voteCount = new Integer( newVoteCount ); 25 } 26 27 // получение общего количества голосов, отданных за этого кандидата 28 public Integer getVoteCountO 29 ( 30 return voteCount; 31 } 32 33 // получение имени кандидата 34 public String getCandidateHame() 35 ( 36 return name; 37 \ за 39 // создание нового кандидата (объект Candidate) 40 public String ejbCreate( String candidateName ) 41 throws CreateException 42 { 43 name = CandidateName; 44 voteCount = new Integer( 0 ); 45 46 return null; 47 J 48 49 // выполнение стандартных действий при создании нового кандидата 50 public void ejbPostCreate( String candidateName ) {) 51
430 Глава 8 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 ■33 74 75 76 77 78 79 80 81 82 83 84 ) // задание контекст» EntityContext public void setEntityContext( EntityContext context ) entityContext = context; // сброс коятгексч-а EntityContext public void unsetEntityContext() entityContext = null; / активация экземпляра объекта Candidate public void ejbActivate() name = ( String ) entityContext.getPrimaryKey() // пассивация экземпляра объекта Candidate public void ejbPassivatef) name = null; // загрузка сведений о кандидате из базы данных public void ejt>Load() i) // сохранение сведений о кандидате в базе данных public void ejbStore() {} // удаление сведений о кандидате из базы данных public void ejbRemove() {) Рис. 8.22. Класс CandidateEJB хранит количество голосов, избранных кандидатом Для обработки входящих сообщений, поступивших из очереди Votes, контейнер использует класс VoteColIectorEJB (рис. 8.23) компонента, управляемого сообщениями. Когда VoteColIectorEJB получает новый голос, контейнер вызывает метод DnMessage (строки 21-48). 1 // VoteColIectorEJB.java 2 // VoteColIectorEJB - это EJB-компонент, управляемый сообщениями, который ведет подсчет голосов. 3 package com.deitel.advjhtpl.jms.mdb; 4 5 // Набор базовых пакетов Java 6 import Java.util.*; 7 import Java. rmi .* ,- S 9 II Пакеты расширений Java 10 iroport javax.ejo.*; 11 import javax.rmi.*; 12 import javax. jms.*; 13 iwport Javax.naming.*; 14
Обмен сообщениями с помощью Java Message Service (JMS) 431 15 public class VoteCollectorEJB 16 implements MessageDrivenBean, MessageListener { 17 18 private MessageDrivenContext messageDrivenContext; 19 20 // получение нового сообщения 21 public void onMessage( Message message ) 12 ( 23 TextMessage voteMessage; 24 25 // извлечение и обработка сообщения 26 try { 27 28 if { message instanceof TextMessage ) { 29 voteMessage = ( TextMessage ) message; 30 String vote = voteMessage.getText(); 31 countVote( vote ); 32 33 System,out.println( "Received vote: " + vote ); 34 } // конец блока if 35 36 else { 37 System,out.println( "Expecting " + 38 "TextMessage object, received " + 39 message.getClass().getName[) ); 40 } 41 42 } // конец блока try 43 44 // обработка исключения JMS от сообщения 45 catch { JMSException jmsException > { 46 jmsException.printStackTraceO; 47 } 48 } 49 50 // добавление голоса к соответствующей сумме голосов 51 private void countVote( String vote ) 52 { 53 // CandidateHome осуществляет поиск/создание кандидатов 54 CandidateHome candidateHome = null; 55 56 // нахождение кандидата и увеличение для него числа голосов 57 try { 53 59 // поиск EJB-компонента Candidate 60 Context initialContext = new initialContextO; 61 62 Object object = initialContext.lookup( 63 "Java:comp/env/ejb/Candidate" ); 64 £5 candidateHome = 66 ( CandidateHome ) PortableRemoteGbject.narrow( 67 object, CandidateHome.class ); 68 69 // поиск кандидата, за которого проголосовал пользователь 70 Candidate candidate =
432 Глава 8 71 eandidateHome.findByPrimaryKey( vote ); 72 73 // увеличение числа голосов для кандидата 74 candidate.incrementVoteCount(); 75 76 J // конец блока try 77 78 // если кандидат не найден, создать нового кандидата 79 catch { FinderException finderException ) I 80 81 // создание нового кандидата и инкремент числа r-олосов для него 82 try { 83 Candidate newCandidate = caxididateHome.create{ vote ); 84 newCandidate.incrementVoteCount <); 85 } 86 87 // обработка исключения при создании нового кандидата 88 catch ( Exception exception ) { 89 throw new EJBException( exception ); 90 } 91 92 V // конец блока catch FinderException 93 94 // обработка исключения при поиске EJB-компонента OrderProducts 95 catch ( NamingException namingException ) { 96 throw new EJBExceptionС namingException ); 97 } 98 99 // обработка исключения при вызове методов OrderProducts 100 catch ( RemoteException remoteException ) { 101 throw new EJBException( remoteException ); 102 } 103 104 } // конец метода countVote 105 106 // задание контекста для управления через сообщения 107 public void setMessageDrivenContext( 108 MessageDrivenContext context ) 109 { 110 meSsageDrivenContext = context; 111 } 112 113 // создание экземпляра компонента 114 public void ejbCreate 0 {} 115 116 // удаление экземпляра компонента 117 public void ejbRemoveО О 118 ) Рис. 8.23. Класс VoteCollectorEJB подсчитывает количество голосов, поступивших из очереди Votes Класс VoteCollectorEJB реализует интерфейсы MessageDrivenBean и Message- Listener. Контейнер вызывает метод ejbCreate перед реализацией нового экземпляра компонента и метод ejbRemove перед уничтожением экземпляра. Интерфейс MessageDrivenBean определяет метод setMessageDrivenContext. Заметим, что ин-
Обмен сообщениями с помощью Java Message Service (JMS) 433 терфейс MessageDrivenBean также определяет, что контекст управления посредством сообщений должен храниться в качестве переменной экземпляра (Message- Dri venCon te xt). После получения сообщения контейнер вызывает метод onMessage, определяемый интерфейсом MessageListener. В строке 28 проверяется, имеет ли полученное сообщение тип TextMessage. Если да, в строке 31 осуществляется вызов метода countVote для учета получеиного голоса. Метод countVote (строки 51-104) ищет EJB-компонент Candidate (строки 60-67) и вызывает метод findByPrimaryKey интерфейса CandidateHome для нахождения кандидата (объект Candidate), за которого пользователь отдал iwioc. Если такой объекг Candidate найден, н строке 74 вызывается метод incrementVoteCount интерфейса Candidate для добавления голоса, отданного за кандидата. Если объект Candidate не найден, в строках 79-92 перехватывается исключение FinderException. Класс TallyDisplay (рис. 8.24) отображает -«моментальный снимок» кандидатов и набранных ими голосов. Класс TallyDisplay использует EJB-компоиент Candidate для извлечения данных голосования. В строках 36~46 осуществляется поиск EJB-компонента Candidate, а также извлекается коллекция Collection всех кандидатов Candidate. Для каждого объекта Candidate в строках 51-58 создается и добавляется новая панель TallyPanel (рис. 8.26). При этом конструктору TallyPanel передается имя кандидата и число набранных им голосов. 1 // TallyDisplay.Java 2 // TallyDisplay отображает количества голосов, хранящиеся в базе данных. 3 package com.deitel.advjhtpl.jros.mdb,- 4 5 // Набор базовых пакетов Java 6 import Java.awt.*; 7 import j ava.awt.event.*; 8 import java.xmi.*; 9 import j ava.uti1. *; 10 import Java.util.List; 11 12 // Пакет*: расширений Java 13 import javax.swing.*; 14 import javax. ejb.*; 15 import j &vax. rmi . *; 16 import javax.naming.*; 17 18 public class TallyDisplay extends JFrame ( 19 20 // Конструктор TallyDisplay 21 public TallyDisplay() 22 { 23 super{ "Vote Tallies" ); 24 25 Container container = getContentFane(); 26 27 // сумма подсчитанных голосов отображается в панели displayPanel 28 JPanel displaypanel = new JPanelO; 29 • displayPanel.setLayout( new GridLayout( 0, 1 ) ); 30 container.add( new J5crollPane( displayPanel ) ); 31 32 // нахождение кандидатов и отображение числа набранных ими голосов
434 Глава 8 33 try { 34 35 // поиск EJB-компонента Candidate 36 Context initialContext = new InitialContext{) ; 37 38 Object object = initialContext.lookup{ 39 "Candidate" >; 40 CandidateHome candidateHome = 41 ( CandidateHome ) PortableRemoteObject.narrow( 42 object, CandidateHome,class ); 43 44 // нахождение всех кандидатов 45 Collection candidates = 46 CandidateHome.findAllCandidates(); 47 48 // добавление панели TallyPanel с именами кандидатов 49 //и числом набранных каждым кандидатом голосов 50 Iterator iterator = candidates.iterator(); 51 while ( iterator.hasNext() ) { 52 Candidate candidate = ( Candidate ) iterator.next(); 53 54 // создание панели TallyPanel для кандидата 55 TallyPanel tallyPanel = 56 new TallyPanel( candidate,getCandidateNarae(), 57 candidate.getVOteCountO.intValue() ); 58 displayPanel,add( tallyPanel ); 59 } 60 61 } // конец блока try 62 63 // обработка исключений при поиске кандидатов 64 catch ( FinderException finderException ) { 65 finderException-printStackTrace(); 66 > 67 // обработка исключений при поиске EJB-комвонента Candidate 68 catch ( HamingExeeption namingException ) { 69 namingException.printStackTrace(); 70 } 71 72 // обработка исключений при взаимодействии с объемом Candidate 73 catch ( RemoteException remoteException ) { 74 remoteException.printstackTrасе(); 75 } 76 77 } // конец койструиторь TallyDisplay 78 79 // ваяуск приложения TallyDisplay 80 public static void inain( String args[) ) 81 { 82 TallyDisplay tallyDisplay = new TallyDisplay(); 83 tallyDisplay.setDe£aultCloseCperation( EXIT_ON_CL0SE ) ; 84 tallyDisplay.pack(); 85 tallyDisplay.setVisible( true ); 86 ) 87 } Рис. 8.24. Класс TallyDisplay отображает число гслосов, набранных кандидатами, извлекая их из базы данных
Обмен сообщениями с помощью Java Message Service (JMS) 435 На рис. 8.25 представлена панель TallyDisplay. Обратите внимание, что в панели отображаются только уже учтенные голоса; содержимое панели TallyDisplay не обновляется по мере поступления новых голосов. Класс TaUyPanel (рис. 8.26) отображает имя и число набранных голосов для отдельного кандидата в панели JPanel. <jtllA!>fl'ff ■ ср" ГО' г—- ■ 'Us(r;i2 ,iy>Hin[l6 1С»>-Ш| '•"-"~r— &i:- ■■ Рис. 8.25. Класс TallyDisplay отображает число голосов, набранных кандидатами 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // TallyPanel.java // Tallpanel - это компонент GUI, который отображает имя // кандидата и количество набранных им голосов, package com.deitel .advjhtpl. jms.mdb; // Набор базовых пакетов Java Import j ava.awt.*; // Пакета расширений Java import ^avax.swing.*; public class TallyPanel extends JPanel { private JLabel nameLabel; private JTextField tallyField; private String name; private int tally; // Конструктор TallyPanel public TallyPanel( String voteName, int voteTally ) < name = voteName; tally = voteTally; nameLabel = new JLabel( name ); tallyField = new JTextField( Integer,toString( tally ), 10 ]; tallyField.setEditablef false ); tallyField.setBaekground( Color.white ); add( nameLabel ); add( tallyField ); } // конец конструктора TallyPanel Рис. 8.26. Класс TallyPanel отображает имя и число набранных голосов длр кандидата
436 Глава 8 8.5.3. Приложение Voter: настройка и выполнение В этом разделе описываются действия, необходимые для развертывания и выполнения версии приложения Voter, в которой используются компоненты, управляемые сообщениями. Поскольку приложение использует базу данных Cloudscape, в файл свойств resource.properties в каталоге config, расположенном в основном каталоге J2BE (например, C:\j2sdkeel.3\config\resource.properties), должны быть добавлены следующие строки: jdbeDataSource.5.name == jdbc/Voting jdbcDataSouirce.5.url = jdbc:cloudscape:rmi:VotingDB; cteate»tirue Заметим, что в этом файле свойств может быть несколько записей jdbcData Source. В приведенных вып1е примерах вам следует заменить 5 на номер последней записи в таблице jdbcData Source плюс 1. Например, если последней записью в jdbcData- Source является запись номер 3, вам следует указать jdbcDataSource.4.name и jdbcData Source.4. url. Добавив эти строки, запустите Cloudscape. Затем запустите сервер J2EE, воспользовавшись командой j2ee —verbose В новом окне команд создайте очередь и мастер соединений (если они были созданы в разделе 8.3.4, то уже должны существовать): j2eeadmin -adcUmsDeatination Votes queue jZee&dmin -listJmsDestination j2eeadmin -addJmsFactory VOTE_FACTORY queue Для развертывания приложения VoteCoIlector запустите инструментальное средство deploytool. Создайте новое приложение, выбрав File->New Application из строки меню. В диалоговом окне щелкните на Browse и перейдите к каталогу, расположенному на один уровень выше каталога com. Введите VoteCollectorApp.ear в качестве имени файла и щелкните на New Application. Затем щелкните на ОК. Теперь добавьте EJB-компонент Candidate, выбрав New Enterprise Bean из меню File. В диалоговом окне EJB JAR выберите Create new EJB File in Application и выделите VoteCollectorApp в появившемся раскрывающемся меню. Введите VoteCollectorJAR в качестве имени EJB Display Name (см. рис. 8.27). Щелкните на кнопке Edit, чтобы добавить файлы классов. В диалоговом окне Edit укажите каталог, содержащий структуру пакета com.deitel.advjhtpl, в качестве начального каталога Starting Directory. Перейдите к каталогу mdb (com/deitel/ advjhtpl/jms/mdb) и добавьте (щелкнув на кнопке Add) классы Candidate.class, CandidateEJB.class и CandidateHome.class (рис. 8.28). Щелкните на OK, чтобы выйти из диалогового окна Edit. Щелкните на Next, чтобы перейти к диалоговому окну General. В диалоговом окне General выберите Entity в качестве типа компонента Bean Туре. Выберите com.deitel.advjbtpl.jms.mdb.CaudidateEJB из раскрывающегося меню для Enterprise Bean Class. Введите Candidate в качестве имени Enterprise Bean Name. В разделе Remote Interfaces выберите com.deitel.advjhtpl.jms.mdb. CandidateHome и com.deitel.advjhtpl.jms.mdb.Candidate в качестве, соответственно, собственного интерфейса Home Interface и удаленного интерфейса Remote Interfaces (см. рис. 8.29). Щелкните на кнопке Next, чтобы перейти к диалоговому окну Entity Settings. В диалоговом окне Entity Settings выберите опцию Container managed persistence (1.0). Установите оба флажка — и voteCount, и паше. Введите java.lang.String в качестве класса первичного ключа Primary Key Class и выберите паше из раскрывающегося меню Primary Key Field (см рис. 8.30), Щелкните на Finish.
Обмен сообщениями с помощью Java Message Service (JMS) 437 ШВШ1 .Ш ^ФЙОЙ^ Se-'АЫ 104 tea StttOst« JAR Яе, unnlltiiiilit imj этд^внфм^ё!) tgft» -§ЛЙ^1виЛ#^*ч-^тЦ "— -*^' ** 4j'*.'i' " ■■"[■ »A>gj»*A^^'.......t..;, ~»i»»— ;»- ■•¥*< T# **•: 'jfej Tz^m^-\ \ ". SjtNwaWW^ J-[T;i«mj^(^w«m~-|;Fv **r. 5U^j;'isO^ s:* ■'■■ r" |»ЗШ|р!1рР' СЗах T^^^^§^v\i Рис 8.27. Настройки JAR-архива EJB для приложения VoteCollectorApp ф t3 torn ф СЭ derteJ 9 C3 advjhipt Ф (Slims ф ЕЗгппЬ Q candidatexia^s Q Candidale.iswa Q CandidateEJ&.c43ss □ C*tdidateEJu.iaf« Q CaniJiriiateHgrne.ciags I •йшиман*' rj**v EE3 META-H4F ^ ЁЭеот t Л flelai f G3 ad^hcpi G Qrndh ■a^V^д^||"'" i} j—.т4.1.'Ил.^^]" Рис. 8.28. Добавление файлов классов для EJB-компонента Candidate
438 Глава 8 Рис. 8.29. Общие настройки для EJB Candidate Рис. 8.30. Настройка данных для EJB Candidate В основном окне инструментального средства deploytool выберите Candidate и щелкните на вкладке Entity (рис. 8.31). Щелкните на кнопке Deployment Settings, В появившемся диалоговом окне щелкните на Database Settings. Введи те jdbc/voting в качестве имени Database JNDI Name базы данных (рис. 8.32), Щелкните на ОК. В диалоговом окне Deployment Settings щелкните на кнопке General Default SQL. В появившемся диалоговом окне с сообщением SQL Generation complete («Генерирование кода SQL завершено») щелкните на ОК (рис. 8.33). В диалоговом окне Deployment Settings щелкните на ОК. Появится окно предупреждения, информирующее об отсутствии оператора WHERE для метода findAHCandidates; игнорируйте предупреждение, щелкнув на ОК (рис. 8.34).
Обмен сообщениями с помощью Java Message Service (JMS) 439 Рис. 8.31, Вкладка Entity для EJB Candidate Рис. 8.32. Параметры настройки базы данных для EJB-компонента Candidate Рис. 8.33. Формирование кода SQL для EJB Candidate
440 Глава 8 Рис. 8.34. Предупреждение SQL дли EJB Candidate Теперь создайте EJB-компонент VoteCollector, выбрав File—>New Enterprise Bean из строки меню. Щелкните на кнопке Add to Existing EJB File и выберите VoteCollectorJAR(VoteColIectorApp) из появившегося раскрывающегося меню (рис. 8.35). Щелкните на Edit и добавьте (Add) Класс VoteCollectorEJB.class из структуры com/deitel/advjhtpl/jms/mdb (рис. 8.36). Щелкните на ОК, чтобы выйти из диалогового окна Edit. Щелкните на Next, чтобы перейти к диалоговому окну General. Рис, 8.35. Настройки JAR-архива EJB для EJB- компонента VoteColtector В диалоговом окне General (рис. 8.37) выберите Message-Driven Bean в качестве типа Bean Туре компонента. Выберите com.deitel.advjhtpl.jms.mdb.VoteCollectorEJB в качестве класса Enterprise Bean class. Введите VoteCollector в качестве имени Enterprise Bean Name компонента. Щелкните на кнопке Next, чтобы перейти к диалоговому окну Transaction Management. В диалоговом окне Transaction Management (рис. 8.38) выберите Container- Managed. Проверьте, чтобы для метода on Mess age для атрибута транзакции Transaction Attribute было задано значение Required. Щелкните на Next, чтобы перейти к диалоговому окну Message-Driven Bean Settings. В диалоговом окне Message-Driven Bean выберите Queue (очередь) в качестве типа места назначения Destination Type (рис. 8.39). Выберите Votes и VOTEJFACTORY из раскрывающихся меню Destination и Connection Factory, соответственно. Щелкните на Next, чтобы перейти к диалоговому окну Environment Entries. В этом диалоговом окне не вводите никакой информации; снова щелкните на Next, чтобы перейти к диалоговому окну Enterprise Bean Reference.
Обмен сообщениями с помощью Java Message Service (JMS) 441 О CanadalaEJS class Q Candid altEJB jaw Q (.snneaieHome class □ camMaleHomajava Q Tal^Oisplaytfasa □ TalryCisplaijava □ TalifFanei class D TalfjFanel.Java [} vote CotltctDfE J B*cla з s QvoleColieclnrEJBlsvs ^««ccrt.XVotaCirftetiorJAll S; *-ПЗ HETft-INF ^ C3 carri ? C3 dHIt l * Giros QcandieaKtass О Can«0are6ja.clas* Q candnaaleH&ma class ci>^i fi?r^l pi Рис. 8.36. Добавление файла класса для EJB-компонента VoteCollector ,j$r?-. !^Д^ Рис. 8.37. Общие настройки для EJB-компонента VoteCollector
442 Глава 8 Рис. 8.38. Настройка параметров управления транзакциями для UB-комлонента VoteCollector Рис. 8.39. Задание параметров управления через сообщения для EJB VoteCollector В диалоговом окне Enterprise Bean Reference (рис. 8.40) щелкните на кнопке Add. Введите ejb/Candidate в качестве имени Coded Name. Выберите Entity для Туре и Remote для Interfaces. Введите com.deiteLadvjhtpl.jms.mdb.CandidateHome и corn.deitel.advjhtpl.jms.mdb.Candidate в качестве, соответственно, собственного интерфейса Home Interface и мести ого/удаленного интерфейса Local/Remote Interface. Выберите опцию JNDI Name и введите Candidate в соответствующее текстовое поле. Щелкните на Finish. В основном окне средства deploytool выберите VoteCollector Арр из иерархической структуры и щелкните на вкладке JNDI Names. Введите Candidate в качестве имени JNDI Name для Candidate. Убедитесь, что Votes задано в качестве имени JNDI Name для VoteCollector (рис. 8.41).
Обмен сообщениями с помощью Java Message Service (JMS) 443 Рис. 8.40. Ссылки на компоненты Enterprise Beans для VoteCollector Наконец, осуществите развертывание приложения, выбрав Deploy из меню Tools. Выберите VoteCoIlectorApp в качестве объекта для развертывания Object То Deploy. В диалоговом окне щелкните на Return Client JAR (рис. 8.42). Щелкните на Next и проверьте имена JNDI, Щелкните на Next, а затем на Finish. После развертывания приложения VoteCollector выполните клиентское приложение Voter: Java -classpath %J2EE_HOME%\lib\j2ee.jar;. -Djms.prt>pertiea=%J2EE_H0ME%\config\jms_elient.properties com.deibsl.advjhtpl.jms.mdb.Voter Чтобы увидеть текущие результаты голосования, выполните класс Tally Display (учтите, что клиентский JAR-файл должен быть включен в описание пути к классам): java -classpath %J2EE_HOME%\lib\j2ee.jar;VoterCollectorAppClient.jar;. com.deltel.advjhtpl.jms.mdb.TallyDisplay Рис. 8.41. Задание имен JNDI для приложения VoteCoIlectorApp
444 Глава 8 Рис. 8.42. Развертывание приложения VoteCollectorApp Резюме • Система обмена сообщениями осуществляют слабое (косвенное) связывание компонентов. ■ Системы обмена сообщениями дают возможность компонентам передавать сообщения для чтения их другими компонентами. • Имеется две основные модели систем обмена сообщениями: «от точки к точке» и «издатель/подписчик». Модель обмена сообщениями «от точки к точке» дает возможность компонентам отправлять сообщения в очередь сообщений. Потребитель сообщения — это компонент-адресат, который обрабатывает полученные сообщения, • В модели «от точки к точке» потребителем сообщения является один и только один клиент;; сервер сохраняет сообщения, которые не были востребованы. • Модель обмена сообщениями «издатель/подписчик» дает возможность компонентам публиковать сообщения в тематическом разделе. Компоненты, заинтересованные в опубликованных в определенном тематическом разделе сообщениях, могут подписаться на этот тематический раздел. • Когда издатель публянует сообщение в заданном тематическом разделе, текущие подписчики получают это сообщение. • В модели «издатель/подписчик» потребителями опубликованного сообщения могут быть нуль или более подписчиков. ■ Сообщение состоит из заголовка, свойств (необязательно) и тела (также необязательно). Заголовок сообщения содержит такую информацию, как адрес назначения сообщения и время отправки сообщения. • Свойства сообщения позволяют получателям сообщений выбирать, какие типы сообщений они хотели бы получать; эти свойства могут устанавливаться отправителем сообщения. Получатели сообщении используют селекторы сообщений для их фильтрации: фильтрация выполняется на сервере. ■ Компоненты, управляемые сообщениями, представляют собой разновидность корпоративных компонентов (Enterprise Beans), которые хорошо согласуются с промежуточным программным обеспечением, ориентированным на обмен сообщениями (MOM). • Контейнер EJB может использовать любой экземпляр компонента, управляемого сообщениями, чтобы обрабатывать входящие сообщения для заданной очереди или тематического раздела. Используя компоненты, управляемые сообщениями, компонент может принимать сообщения асинхронно, • Технология Java Message Service (JMS) стандартизирует корпоративный обмен сообщениями, предоставляй интерфейс прикладного программирования для моделей «от точки к точке» и «издатель/подписчик».
Обмен сообщениями с помощью Java Message Service (J MS) 445 • JMS поддерживает пять типов сообщений: BytesMcssage, MapMessage, Object Message, StreamMessage и Text Message. • Администратор сервера создает мастера соединений, очереди и тематические разделы. « Объект QueueConnectionFactory дает возможность клиентам создавать объект соединения с очередью QueueConnection, • Объект QneueCoimecUon создает объект сеанса QueueSession. Объект QneneSession создает либо объект QneneSender, либо объект QueneReceiver. • Когда очередь или подписчик на тематический раздел получает сообщение, слушатель сообщения обрабатывает его. • Интерфейс MessageListener определяет метод onMessage, который вызывается, когда поступает новое сообщение. • Клиент может получать подписку двух типов: кратковременную и долгосрочную. При кратковременной подписке сообщения принимаются только при активном состоянии подписки. • При долгосрочной подписке сообщения могут быть получепы и в случае цеазстивпого состояния: сервер хранит сообщения, отправленные в тематический раздел, пока подписка находится в неактивном состоянии, и отправляет их клиенту, когда подписка снова становится активной. Заметим, что если для подписки установлен селектор, чтобы осуществлять фильтрацию сообщения, сервер будет хранить только те сообщения, которые удовлетворяют условию, задаваемому селектором, • Объект TopicConnectionFactory, который был создан сервером, создает объект Topic- Connection. Объект TopicConnection создает объект TopicSession. Объект Topic Session создает объект TopicPublisber или TopicSubscriber. • Подписчик на тематический раздел (или получатель в очереди) может фильтровать сообщения с помощью селектора сообщений. Если клиент задал селектор сообщений, сервер будет отправлять клиенту только те сообщения, которые пропускаются этим фильтром. В основу синтаксиса селектора сообщений положен синтаксис SQL92. • EJB-компоненты, управляемые сообщениями, представляют собой новый тин компонентов Enterprise JavaBeans, предусмотренный в пакете Enterprise JavaBeans, версии 2.0, который входит в состав Java 2 Enterprise Edition 1,3. • Компоненты, управляемые сообщениями, способны обрабатывать сообщения JMS, помещенные в очередь или в тематический раздел. • После получения сообщения контейнер EJB использует любой имеющийся экземпляр конкретного компонента, управляемого сообщениями, для обработки сообщения. • Поскольку может быть использован любой экземпляр EJB-компонента, управляемого сообщениями, такие компоненты не связаны с конкретным клиентом и не должны хранить информацию о состояаии клиента. • Любой заданный экземпляр EJB-компонента может обрабатывать сообщения от нескольких клиентов. • В отличие от сеансовых компонентов и компонентов-сущностей (для которых требуются интерфейсы), для компонентов, управляемых сообщениями, разработчики должны предоставить лишь класс реализации компонента. • Интерфейс MessageDrivenBean определяет метод setMessageDrivenContext и указывает, что контекст управления через сообщения следует хранить как переменную экземпляра MessageDrivenContext. Терминология BytesMessage, интерфейс durable subscription — долгосрочная иод- писка Java Message Service (JMS) MapMessage, интерфейс message — сообщение message body — тело сообщения message consumer — потребитель сообщения message-driven bean — компонент, управляемый сообщениями message header — заголовок сообщения message oriented middleware (MOM) — промежуточное программное обеспечение, ориентированное на обмен сообщениями message property — свойство сообщения message selector — селектор сообщений messaging system — система обмена сообщениями nondurable subscription — кратковременная подписка ObjectMessage, интерфейс point-to-point messaging model — модель обмена сообщениями «от точки к точке»
446 Глава 8 publish — публиковать sender — отправитель publish/subscribe messaging mode\ — модель StrearaMessage, интерфейс обмена сообщениями «издатель/иодпис- subscribe — подписываться чик» subscriber — подписчик publisher — издатель subscription — подписка queue — очередь TextMcSsage, интерфейс QueueConnection, интерфейс topic — тематический раздел QueueConnection Factory, интерфейс TopicConnection, интерфейс QueneReceiver, интерфейс TopicConnectionFactory, интерфейс QueueSender, интерфейс TopicPuMisher, интерфейс Queue Session, интерфейс TopicSession, интерфейс receiver — получатель Topic Subscriber, интерфейс Упражнения для самоконтроля 8.1. Ответьте, являются ли следующие иысказывания истинными или ложными. Если высказывание ложно, объясните, почему. a) Сообщения в модели обмена, сообщениями «от точки к точке» предназначены для нуля или более получателей. b) Сообщения в модели обмена сообщениями «издатель/подписчик» предназначены для нуля или одного получателя. c) Если указан селектор сообщений, фильтрация осуществляется на стороне сервера. d) При отсутствии получателя сервер хранит сообщения, опубликованные в тематическом разделе, пока получатель не устааовит соединение. e) При отсутствии получателя сервер хранит сообщения, отправленные в очередь, пока получатель не установит соединение. f) Компоненты, управляемые сообщениями, хранят состояние для конкретного клиента. 8.2. Заполните пропуски в следующих предложениях: a) Существует две модели обмена сообщениями: и . b) В модели обмена сообщениями клиент отправляет сообщение в . Подразумевается, что получателем этого сообщения является один и только один клиент. c) В модели обмена сообщениями клиент отправляет сообщение в . Подразумевается, что это сообщение предназначено для нуля или более клиентов. d) Сервер будет хранить сообщения для подписки, пока подписка является неактивной. e) Компонент представляет собой разновидность корпоративного компонента (Enterprise Bean), хорошо согласующегося с промежуточным программным обеспечением, ориентированным на обмен сообщениями (MOM). Ответь» на упражнения для самоконтроля 8.1. а) Ложио. Сообщения в модели обмена сообщениями «от точки к точке» предназначены только для одного получателя. Ь) Ложно. Сообщения в модели обмена сообщениями •издатель/подписчик» предназначены для нуля или более получателей, с) Истинно. А) Ложно, Если текущей подписки не существует, сервер не будет хранить входящие сообщения; следует заметить, однако, что в случае, если долговременная подписка является неактивной, сервер будет сохранять сообщения для этой подписки, е) Истинно. f) Ложно. Экземпляр компонента, управляемого сообщениями, может обрабатывать сообщения от нескольких клиентов; компоненты, управляемые сообщениями, не могут хранить состояние для конкретного клиента. 8.2. а) *от точки к точке», «издатель/подписчик». Ь) «от точки к точке», очередь, с) «издатель/подписчик», тематический раздел, d) долгосрочной, е) управляемый сообщениями.
Обмен сообщениями с помощью Java Message Service (JMS) 447 Упражнения 8.3. Каково предназначение системы обмена сообщениями? 8.4. Сравните и противопоставьте модели обмена сообщениями ют точки к точке* и «издатель/подписчик*. В каких случаях следует предпочесть ту или иную модель? 8.5. Воспользовавшись моделью обмена сообщениями «от точки к точке*, создайте приложение, которое дает возможность продавцу получать предложения цены выставляемого на продажу товара. В сообщении с предложением должен содержаться адрес e-maj] покупателя, участвующего в торгах, и предлагаемая им цена. (Подсказка. Продавец должен выступать в качестве принимающего сообщений с предложениями цены, а участвующий в торгах покупатель должеп выступать в качестве отправителей сообщений с предложениями.) 8.6. Модифицируйте ваше решение Упражнения 8.5, чтобы продавец мог осуществлять фильтрацию предложений с ценой, которая является меньше определенной. (Подсказка. Установите предлагаемую цену в качестве свойства типа double сообщений с предложениями и используйте это свойстзо в селекторе сообщений.) 8.7. Создайте приложение, использующее модель «издатель/подписчик», которое принимает заказы от клиентов. Заказ должен быть опубликован в виде сообщения в тематическом разделе Domestic-orders, если клиент имеет местный адрес доставки, либо в тематическом разделе International-orders, если клиент имеет международный адрес доставки. 8.8. Модифицируйте ваше решение Упражнения 8.7, чтобы дать возможность подписчикам тематических разделов Domestic-orders и International-orders осуществлять фильтрацию заказываемых товаров по категориям. Например, если компонент ответственен за все * книжные* заказы, ему будет позволено отфильтровывать все заказы, не относящиеся к категории «book*. (Подсказка. Используйте строковое свойство для задания категории заказа и селектор сообщений для выполнения фильтрации по этому свойству.)
Q Практический пример корпоративного приложения. Обзор архитектуры Цели • Уяснить архитектуру учебного приложения книжного Internet-магазина Deitel Bookstore, реализованного с помощью средств Enterprise Java. • Познакомится с основными решениями, принятыми при разработке приложения Deitel Bookstore. • Познакомиться с применением архитектуры модель—вид—контроллер (MVC) в контексте приложения Enterprise Java. • Понять, каким образом технологии XML и XSLT дает возможность генерировать содержимое для клиентов различных типов. • Понять роль, которую играют сервлеты и компоненты EJB в корпоративных приложениях. • Уяснить принципы построения многоуровневых приложений в инфраструктуре J2ЕЕ. Вселенная шире, чем наши представления о ней. Генри Дэвид Торо С горы легче спускаться, чем подниматься на нее, зато, сколько можно увидеть с вершины! Арнольд Беннет Не утруждайтесь созерцанием вида. Он уже представлен в моей композиции. Густав Малер Целое — это то. что имеет начало, середину и конец. Аристотель Основой хорошего закона является не логика, а его практическая польза. Оливер Уэнделл Холмс, младший
450 Глава 9 9.1. Введение Технологии, которые входят в состав Java 2 Enterprise Edition (J2EE), дают разработчикам возможность создавать устойчивые, масштабируемые, корпоративные приложения. В этом практическом примере мы создадим приложение для электронного бизнеса, реализующее книжный Internet-магазин. В приложении будет использован ряд технологий J2EE, таких как сервлетьг, компоненты Enterprise JavaBeans, XML, XSLT, XHTML, WML и cHTML. В данном практическом примере приложения Java 2 Enterprise Edition применяется архитектура модель—вид—контроллер (Model—View—Controller — MVC), чтобы отделить данные и бизнес-логику (модель) от логики представления данных (вид) и логики управления (контроллер). Модель приложения представлена реляционной базой данных и компонентами-сущностями EJB. Сервлеты Java реализуют логику управления для обработки вводимой пользователем информации, а таблицы стилей XSL (XSLT-трансформации) реализуют логику внешнего представления данных приложения. Логика внешнего представления данных посредством XSLT позволяет приложению формировать содержимое для клиентов нескольких различных типов. При XSLT-трансформации данные приложения (размеченные с помощью XML) обрабатываются таким образом, чтобы динамически генерировать XHTML, WML и другие виды представления содержимого. Приложение может быть расширено, чтобы обеспечить поддержку дополнительных типов клиентов и осуществлять настройку выводимых данных для клиентов нового типа путем реализации дополнительных XSLT-трансформаций. Например, можно разработать мидлет J2ME для карманных устройств и реализовать ряд таблиц стилей XSL, которые формируют выходные данные в формате, подходящем для этого мидлета. В этой главе будет рассмотрена архитектуры учебного приложения Deite] Bookstore. В последующих главах мы обсудим логику управления, реализованную на еервлетах (глава 10), а также бизнес-логику и абстракцию данных, реализованные с помощью EJB (главы 11 и 12), В главе 12 будут также предоставлены инструкции по развертыванию учебного приложения Deitel Bookstore на эталонной реализации сервера приложений J2EE Sun Microsystems. В главе 13 мы познакомимся с тремя наиболее популярными коммерческими серверами приложений, совместимыми с J2EE: ВЕА Web Logic, IBM WebSphere и iPlanet Application Server. Мы обсудим возможности каждого из серверов приложений, а затем выполним развертывание учебного приложения Deitel Bookstore на серверах ВЕА WebLogic и IBM WebSphere.
Практический пример корпоративного приложения. Обзор архитектуры 451 9.2. Приложение книжного Internet-магазина Deitel Bookstore Приложение, которое будет разработано в этом практическом примере, реализует ряд функциональных возможностей, характерных для коммерческих Internet-магазинов. Приложение предоставляет каталог товаров, с помощью которого покупатели могут найти нужную им книгу или просмотреть список имеющихся в магазине книг. Приложение также предоставляет «магазинную тележку», в которую покупатели могут добавлять приобретаемые товары. Покупатели могут просматривать содержимое магазинной тележки, удалять товары, изменять количество любого товара в тележке или осуществлять покупку товаров. В приложении предусмотрены средства регистрации потребителя, позволяющие покупателям вводить информацию для оплаты и организации доставки товара. Покупателям также предоставляется возможность просматривать подробную информацию по предыдущим заказам и восстанавливать забытые пароли. Покупатели могут осуществлять доступ в Internet-магазин через стандартные Web-браузеры, Web-браузеры Wireless Markup Language (WML) и браузеры cHTML (i-mode). 9.3. Общая архитектура системы Приложение Deitel Bookstore является многоуровневым приложением. В многоуровневых приложениях (их еще называют п-уровневыми приложениями) функциональные возможности разнесены на разные уровни. Каждый уровень может физически размещаться на отдельном компьютере. В приложении Deitel Bookstore используется трехуровневая архитектура. Базовая структура трехуровневого приложения представлена на рис. 9.1. Клиентский уровень Средний уровень Информационный уровень Рис. 9.1. Модель трехуровневого приложений Deitel Bookstore Информационный уровень (его также называют уровнем данных, или нижним уровнем) содержит данные, используемые приложением. В корпоративных приложениях для хранения данных информационного уровня обычно используются реляционные базы данных. В приложении Deitel Bookstore база данных содержит информацию о товарах (книгах), например, описание, цена и имеющееся на складе количество, а также информацию о потребителях, такую как имя пользователя, адрес для доставки и номер кредитной карты. Средний уровень реализует бизнес-логику и логику управления, которая координирует взаимодействия между клиентами приложения и данными приложения. Средний уровень действует в качестве посредника между данными с информационного уровня и клиентами приложения. Логика управления среднего уровня обрабатывает клиентские запросы (например, запрос на просмотр каталога товаров) и извлекает данные из базы данных. После этого логика создания внешнего представления обрабатывает данные, полученные с информационного уровня, и предоставляет содержимое клиенту.
452 Глава 9 Общая методическая рекомендация 9.1 Web-сервер в многоуровневом приложении может рассматриваться как отдельный уровень, т.е. приложение становится четырехуровневым. В данном практическом примере Web сервер считается частью среднего уровня, поскольку Web сервер лишь делегирует запросы серверу приложений и пересылает ответы на клиентский уровень. Бизнес-логика определяет бизнес-правила и обеспечивает сохранность информации перед обновлением базы данных или представлением данных пользователю. Бизнес-правила определяют, как клиенты приложения могут и как не могут осуществлять доступ к данным, и каким образом данные обрабатываются в приложении. Например, в приложении книжного Internet-магазина может действовать бизнес-правило, которое требует, чтобы эмитент кредитной карты покупателя проверял кредитную карту перед отправкой заказа покупателю. Бизнес-логика может реализовывать это бизнес-правило путем получения от потребителя номера кредитной карты, срока ее действия и адреса для доставки счета с последующей проверкой этой информации. Если проверка завершается успешно, бизнес-логика обновляет базу данных и указывает, что магазин может отправить заказ покупателю. Средний уровень также реализует логику внешнего представления данных. Web-приложения обычно предоставляют клиентам информацию в виде XHTML- документов. Учитывая последние достижения в технологиях беспроводной связи, многие Web-приложения также предоставляют информацию клиентам с беспроводным доступом в виде документов WML и cHTML. На среднем уровне приложения Deitel Bookstore используются возможности технологий XML и XSLT для динамического генерирования содержимого, отправляемого клиентам различных типов, предоставляя поддержку для Web-брауэеров (XHTML), WAP-браузеров (WML) и браузеров i-mode (cHTML). Клиентский уровень, или верхний уровень, представляет собой пользовательский интерфейс приложения. В Web-приложениях клиентский уровень обычно представлен Web-браузером. Пользователи просматривают в Web-браузере итоговый результат, сформированный приложением, и используют гипересылки и кнопки, предусмотренные в форме, для взаимодействия с приложением. После этого Web-браузер взаимодействует со средним уровнем, чтобы выдавать запросы и извлекать данные с информационного уровня. Приложение Deitel Bookstore поддерживает на клиентском уровне браузеры Web, WML и cHTML. Разработчики могут добавлять поддержку и для других типов клиентов, предоставляя соответствующие таблицы стилей XSL для этих клиентов. На рис. 9.2 представлена подробная схема архитектуры корпоративного приложения Deitel Bookstore. В последующих разделах будет рассмотрена каждая из составных частей этой схемы. 9.4. Компоненты Enterprise JavaBeans Компоненты Enterprise JavaBeans (EJB) реализуют в приложении Deitel Bookstore бизнес-логику и уровень абстракции базы данных. 1'лавным элементом бизнес-логики является сеансовый EJB-компонент с состоянием, который представляет магазинную тележку покупателя. Модель приложения Deitel Bookstore реализует компонент-сущность EJB, который предоставляет объектно-ориентированный интерфейс для взаимодействия с информационным уровнем. Любая программа, которая способна осуществлять взаимодействия через RMI-HOP, может использовать EJB-компоненты бизнес-логики. Например, может быть разработано административное инструментальное средство в виде автономного приложения, использующего EJB-компоненты бизнес-логики, чтобы модифицировать данные приложении. Сер влеты в приложении Deitel Bookstore используют В JB- компоненты бизнес-логики для реализации Internet-магазина.
Практический пример корпоративного приложения. Обзор архитектуры 453 Клиенты вза и мо действуют с Web-сервером, который пересылает запросы сервгету, выполняющемуся в контейнере сералетое сервера приложении. Сервлеты реализуют логику работы приложения и взаимодействуют с LIB-компонентами через KIWI-HOP ^Клиент) <Шй^ fflgiSj# j Клиент IV 1 i-rnode Г ■'# Web- браузер '0? .*т> айг- Web-cepeep Информационный уровень и EJ В-компоненты образуют модель приложения "s3gg^ •®1Ш Контейнер сервлетов XSLT- преобразователь (WMU XSLT- преобразователь (cHTML) XML XSLT- преобразовзтель (XHTML) XML XML XSLT-трамсформации реализуют внешнее представление приложения. настраивая содержимое для каждого типа клиен-ов. Контейнер сервлетов управляет жизненным циклом сервлетов и взаимодействием с Web-сервером. Контейнер EJB предоставляет EJ В-компонентам сервисы выполнения, такие как соединения с базой данных и средства управления жизненнь1м циклом Рис. 9.2. Развернутая схема приложения Deitel Bookstore 9.4.1. EJB-сущности EJB-компоненты с данными (компоненты-сущности) обеспечивают объектно- ориентрованную абстракцию информационного уровня приложения. Каждая EJB-сущность представляет определенный объект, хранящийся в реляционной базе данных приложения. Экземпляры каждой из EJB-сущностей представляются отдельными строками в базе данных. Например, экземпляр EJB-компонента Customer представляет одного потребителя. В базе данных хранится имя, фамилию, адрес доставки счета, адрес доставки товара и информация о кредитной карте для каждого потребителя. Каждый экземпляр EJB-компонента Customer представляет определенного покупателя и предоставляет методы для извлечения и сохранения информации о покупателе. Для упрощения передачи данных каждая EJB-сущность в приложении имеет соответствующий класс модели, содержащий свойства для каждой из EJB-сущностей. Например, EJB-компонент Product, который представляет товар в базе данных, имеет соответствующий класс Product Model со свойствами, описывающими ISBN-код книги, цену, автора и т.д. Каждый класс модели реализует интерфейс Serializable и, следовательно, подходит для передачи через RML-ПОР. Инкапсулирование данных в классы модели помогает набежать заторов в сети за счет сокра-
454 Глава 9 щеиия количества удаленных вызовов методов, необходимых для получения информации от EJB-сущности. Например, сервлет может вызвать метод getProdnct- Model для получения информации о товаре (объект Product), вместо того, чтобы осуществлять отдельные вызовы методов, таких как getlSBN, getPrice, getAuthor и т.д. Каждый класс модели также реализует интерфейс XMLGenerator, который определяет метод getXML для извлечения XML-представления определенного экземпляра класса модели, Сервлеты в приложении Deitel Bookstore используют этн элементы XML для построения XML-документов, например, каталога товаров и архива заказов. 9.4.2. Сеансовые EJ В -компоненты с состоянием Сеансовый EJB-компонент с состоянием ShoppingCart, который реализует магазинную тележку покупателя, является главным элементом бизнес-логики в приложении Deitel Bookstore. Иногда бывает, что покупатели просматривают предлагаемый сетевым магазином ассортимент, добавляют товары в тележки, а затем решают не приобретать эти товары. Такие магазинные тележки можно считать отставленными. Вместо того, чтобы хранить отставленную тележку в базе данных, приложение Deitel Bookstore использует сеансовый EJB-компонент с состоянием, что позволяет достигнуть максимальной аналогии с реальным магазином. Если покупатель отставляет магазинную тележку, контейнер EJB удаляет экземпляр EJB-компонента ShoppingCart. 9.5. Логика управления, реализуемая сервлетами Сервлеты обеспечивают интерфейс среднего уровня между клиентом и EJB-компонентами бизнес-логики. Сервлеты в приложении Deitel Bookstore реализуют логику управления (контроллер) в архитектуре MVC. Сервлеты обрабатывают клиентские запросы (поступающие через протоколы HTTP и WAP) и взаимодействуют с EJB-компонентами бизнес-логики для удовлетворения этих запросов. Далее сервлеты обрабатывают данные, извлеченные из EJB-компонентов, и генерируют XML-документы, которые представляют эти данные. Такие XML-документы действуют в качестве промежуточных моделей данных приложения. После этого сервлеты подвергают ати XML-документы XSLT-трансформациям, которые формируют представление данных для каждого из типов клиентов. 9.6. Логика внешнего представления данных посредством XSLT Каждый сервлет в приложении Deitel Bookstore применяет XSL-преобразова- тель (объект Transformer) и XSLT-трансформации для генерирования соответствующих представлений для каждого тика клиентов. Для каждого поддерживаемого типа клиента приложению требуется отдельный набор XSLT-трансформаций, Например, мы предоставляем один набор XRT/Г-трянсформаций для формирования XHTML-кода, второй набор для формирования WML-кода -и третий набор для формирования cHTML-кода. Сервлеты используют файл конфигурации для определения, какую именно XSLT-трансформацию применить для данного типа клиента. Сервлет GetProductServlet получает XML-описание товара из модели Product- Model для данного товара. XSL-яреобразователь (объект Transformer) использует XSLT-трансформацию для извлечения данных из XML-документа и создания их внешнего представлении для клиента. Если клиентом является Web-браузер,
Практический пример корпоративного приложения. Обзор архитектуры 455 XSL-преобразователь Transformer использует XSLT-трансформацию, которая формирует XHTML-код. Если клиентом является WAP-браузер (например, для сотового телефона), XSL-преобразователь Transformer использует XSLT-трансформацию, которая формирует WML-код. На рис. 9.3 представлен образец XML-документа, сформированного сервлетом GetProductServlet. Информация о товаре (книге), такая как ISBN-код, название, автор, издатель, цена и т.д., размечена в виде XML-документа. 1 <?хи1 version = "1.0" encoding="UTF-8"?> 2 <catalog> 3 <product> 4 <isbn>Q130284173</isbn> 5 <publisher>Prentice Ha11</publisher> 6 <auth.ot>Deitel, Deitel, Nieto, Lin Samp; Sadhu</anthor> 7 <title>XML How to Program</title> 8 <price>$69.95</price> 9 <pages>1200</pages> 10 <image>images/xra3htpl-jpg</image> 11 <media>CD</media> 12 <quantity>500</guantity> 13 </product> 14 </catalog> Рис. 9.З. XML-файл, сформированный сервлетом GetProductServlet XSL-документ, представленный на рис. 9.4, трансформирует XML-документ, сформированный сервлетом GetProductServlet, в XHTML-код, который воспроизводится Web-браузером (рис. 9.5). При трансформации из XML-документа просто извлекаются соответствующие фрагменты информации, и создается нужное XHTML-представление. Структуры соответствующих таблиц стилей XSLT будут рассматриваться в главе 10. 1 <?xml version = "1,0"?> 2 3<!-- ProductDetails. xsl --> 4 <!-- Таблица стилей XSLT для трансформации содержимого, —> 5 <!— сформированного сервлетом GetProductServlet, в XHTML-код. —> 6 7 <ks1:stylesheet version = "1.0" 8 xmlnsixsl = "http://www.w3.org/1999/XSL/TransformM> 9 10 <xsl .-output method = "xml" omit-xml-declaration = "no" 11 indent = "yes" doctype-system = "DTD/xhtmll-strict.dtd" 12 doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/> 13 14 <!-- включение шаблона для обработки элементов error --> 15 <xsl:include href = "/XSLT/XHTML/error.xsl"/> 16 17 <>— шаблон для элемента product --> 18 <xsl:template match = "product"> 19 <html xmlns = "http://www.w3.org/1999/xhtml" 20 xml:lang = "en" lang = "en"> 21 22 <head> 23 <title> 24 <xsl;value-of select = "title"/> — Description
456 Глава 9 25 </title> 26 27 «Clink rel = "Stylesheet" href = "styles/default.css"/> 28 </head> 29 30 <body> 31 32 <!— копирование заголовка со средствами навигации по XHTML-документу --> 33 <xsl:for-each select = 34 "document( '/XSLT/XBTML/navigation.xml1 )"> 35 <xsl:copy-of select = "."/> 36 </xsl:for-each> 37 38 <div class = "header"> 39 <xsl:value-of select = "title"/> 40 </div> 41 42 <div class = "author"> 43 by <xsl:value-of select = "author"/> 44 </div> 45 46 <!— создание элемента div со сведениями о товаре Product —> 47 <div class = "productDetails"> 48 <table style = "width: 100%;"> 49 <tr> 50 <td style = "text-align: center;"> 51 <img class = "bookCover" 52 arc = "images/{image}" 53 alt = "{title} cover image."/> 54 </td> 55 56 <td> 57 <p style = "text-align: right;"> 58 Price: <xsl:value-of select = "price"/> 59 </p> 60 61 <p style = "text-align: right;"> 62 ISBN: <xsl:value-of select = "ISBN"/> 63 </p> 64 65 <p style = "text-align: right;"> 66 Pages: <xsl:value-of select = "pages"/> 67 </p> 68 69 <p style = "text-align: right;"> 70 Publisher: 71 <xsl:value-of select = "publisher"/> 72 </p> 73 74 <!— кнопка добавления в тележку AddToCart —> 75 <fonn method = "post" action = "AddToCart"?- 7 6 <p style = "text-align: center;"> 77 <input type = "submit" 78 value = "Add to cart"/> 79 80 <input type = "hidden" name = "ISBN2
Практический пример корпоративного приложения. Обзор архитектуры 457 81 82 83 84 85 86 87 88 89 90 91 value = </р> </£огш> </td> </tr> </table> </div> </body> </htrol> </xsl:template> "{ISBN}"/> 92 <xsl:stylegheet> Рис. 9.4. Таблица стилей XSL для генерирований XHTWL-кода из данных, выдаваемых сервлетом GetProductServlet 1 <?xml version = "1.0" encoding = "PTF-8"?> 2 <!D0CTYPE html POBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "DTD/xhtmll-strict.dtd"> 4<html xmlns="http://www.w3.org/1999/xhtml" 5 lang="eit" xml:lang="en"> 6 <head> 7 <title>XML How to Program — Description</title> 8 <link href="styles/defaiilt.e5S" rel="Stylesheet" /> 9 </head> 10 <body> 11 <div> 12 <div class="logo"> 13 <table style="width: 100%;"> 14 <tr> 15 <td style="text-align: left;"> 16 <img src="images/logotiny.gif" 17 alt="Deitel &amp; Associates, Inc. logo." /> 18 </td> 19 20 <td style="text-align: right;"> 21 <div style= 22 "position; relative; bottom: -50px;"> 23 <form action="ProductSearch" raethod="get"> 24 <pXinput type="text" size="15" 25 name="searchString" /> 26 <input type="submit" value="Search" /> 27 </p> 28 </form> 29 </div> 30 </td> 31 </tr> 32 </table> 33 </div> 34 35 <div class="navigation"> 36 <table class="menu"> 37 <tr> 38 <td class="menu"> 39 <a href="GetAllProducts">Product Catalog</a> 40 </td>
458 Глава 9 41 42 <td cLass="taen.u"> 43 <a href="registration.html">Create Account</a> 44 </td> 45 46 <td class="menu"> 47 <a href="login.html">Log in</a> 48 </td> 49 50 <td class="menu"> 51 <a href="ViewCart">Shopping Cart</a> 52 </td> 53 54 <td class="mfinu"> 55 <a hre£="ViewOrderHistory">Order History</a> 56 </td> 57 </tr> 58 </table> 59 </div> 60 61 </div> 62 <div class="header">XML How to Program</div> 63 <div class="author"> 64 by Deitel, Deitel, Hieto, Lin Samp; Sadhu</div> 65 div class="productDetails"> 66 <table style="width: 100%;"> 67 <tr> 68 <td style="text-align: center;"> 69 <img alt="XML How to Program cover image." 70 src="images/xralhtpl.jpg" 71 class="bookCover" /X/td> 72 <td> 73 <p> style="text-align; right;"> 74 Price: $69.95</p> 75 <p> style="text-align: right;"> 76 ISBN: 0130284173</p> 77 <p> style="text-align: right;"> 78 Pages; HQQ</p> 79 <p> style="text-align: right;"> 80 Publisher: Prentice Hall</p> 81 82 <form action="AddToCart" method="post"> 83 <p style="text-align: center;"> 84 <input value="Add to cart" 85 type="submit" /> 86 <input value="0130284173" 87 name="ISBN" type="hidden" /X/p> 88 </form> 89 </td> 90 </tr> 91 </table> 92 </div> 93 </body> 94 </html> Рис, Э.5. XHTML-документ, сформированный при XSLT-трансформации данных, выдачных сервлетом Get Product Serviet {часть 1)
Практический пример корпоративного приложения. Обзор архитектуры 459 Ll.ii.i.iiii.iunaiM.ijy.i.iii.i.BJumMLj.MWwi) Ffc E* К» ГянеНШ Т«*г Lygfe , ./ .... .. ЛИ 3 DeiteI XML Haw to Program by Dpi lei, DeiteI, Meto, Lin & Sjdhni ^i^aach | t nnfcrHiut&rv sgj»°ffgiip^"?"' " ajFfBiiiji" д$- Рис. 9.5. XHTML-документ, сформированный при XSL -трансформации данных, выданных сервлетом GetProduct5ervlet. (часть 2) Таблица стилей XSL (рис. 9.6) трансформирует XML-документ, сформированный сервлетом GetProductServlet, в WML-код, который воспроизводится WML- браузером (рис. 9.7), Обратите внимание, что WML-документ содержит очень мало информации по форматированию, WML-код воспроизводится небольшими устройствами, например, сотовыми телефонами, имеющими ограниченные возможности по отображению. Эти устройства также имеют ограниченные возможности сетевых соединений, поэтому объем отправляемых данных должен быть минимальным. Например, WML-документ не содержит изображения для обложки книги, поскольку его загрузка при беспроводном соединении сопровождается большими затратами времени. 1 <?xml version = "1.0"?> 2 3 <xsl:stylesheet version = "1.0" 4 xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"> 5 6 7 a 9 10 11 12 13 14 15 <xsl:output method = "xml" omit-xml-declaration = "no" doctype-system = "http://www.wapforum.org/DTD/wml_l.1.xml' doctype-public ■ ■'-//WAPFORUM//DTD WML l.l//EN"/> <xsl:include href ■ /XSLT/WML/error.xsl"/> <xsl:template match = "product"> <wml> <card id = "product" title = "(title)">
460 Глава 9 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <do type = "accept" label = "Add To Cart"> <go href = "AddToCart" method = "post"> <postfield name = "ISBN" value = "{ISBW}"/> </go> </do> <do type = "prev" label = "Back"> <prev/> </do> <p>Description:</p> <pxxsl:value-of select = "title"/></p> <p>by <xsl:value-of select = "author"/X/p> <P> <table columns = "2" title = "info"> <tr> <td>ISBH-. <xsl:value-of select = "ISBN"/> </td> <td>Price: $<xsl:value-of select = "price"/> </td> </tr> <tr> <td>Publisher: <xsl:value-of select = "publisher"/> </td> <td>Pages: <xsl:value-of select = "pages"/> </td> </tr> </table> </p> </card> </wml> </xsl:template> 54 </xsl :stylesheet> Рис. 9.6. Таблица стилей XSL, трансформирующая выданные сер влетом GetProductServlet данные в WML-код 1 <?xml version = "1.0"?> 2 <!D0CTYPE wml PUBLIC "-//WAPFOBUM//DTD WML 1.1//EN" 3 "http://www.wapforum.org/DTD/vnnl_l.l.xml"> 4 5 <wml> 6 <card title="XML How to Program" id="product"> 7 <do label="Add To Cart" type="accept"> 8 <go method="pcst" hre£="AddToCart"> 9 <postfield value="0130284173" naroe="ISBN"/x/go> 10 </do> 11 <do label="Back" type="prev"Xprev/x/do> 12 13 <p>Description:</p>
Практический пример корпоративного приложения. Обзор архитектуры 461 14 15 16 17 18 19 20 21 22 23 2d 25 26 27 28 <р>ХВД» How to Program</p> <р>Ьу Deitel, Deitel, Nieto, Lin lamp; Sadhu:</p> <p> <table title="info" columns="2"> <tr> <td>ISBH: 0130284173</td> <td>Prioe: $$69.65</td> </tr> <tr> <td>Publisher: Prentice Hall</td> <td>Pages: 1100</td> </tr> </table> </p> </card> 29 <Ляп1> ?ш mWM(l'>a Рис. 9.7. WML-документ, сформированный в результате XSLT-трансформации дакных, выданных сервпеюм GetProductServlet. (Использовано изображение Image © 2001 Nokia Mobile Phones.) 1 <?xml version = "1.0"?> 2 3 <xsl:stylesheet version = "1.0" 4 xmlns:xsl = "http://www.w3.org/1999/xSL/Transform"> <xsl:output method = "html" omit-xml-declaration = "yes" indent = "yes" doctype-system =
462 Глава 9 10 "http: //www. w3. org/MarxUp/btml-spec/html-spec_toe. html" 11 doctype-public = "-//W3C//DTD HTML 2.0//EN"/> 12 13 <xsl:include href = "/XSLT/cHTML/error.xsl"/> 14 15 <xsl:template match = "product"> 16 <html> 17 18 <head> 19 <title> 20 <xsl:value-of select = "title"/> — Description 21 </title> 22 </head> 23 24 <body> 25 <&iv class = "header"> 26 <xsl:value-of select = "title"/> 27 </div> 28 29 <div class = "author"> 30 by <xsl:value-of select = "author"/> 31 </div> 32 33 <P— создание элемента div со сведениями о товаре Product —> 34 <div class = "productDetails"> 35 <table> 36 <tr> 37 <td style = "text-align: center;"> 38 <img class = "bookCover" 39 src = "images/{image}"/> 40 </td> 41 42 <td> 43 <p style = "text-align: right;"> 44 Price: <xsl:value-of select = "price"/> 45 </p> 46 47 <p style = "text-align: right;"> 48 ISBN: <xsl:value-of select = "ISBN"/> 4 9 </p> 50 51 <p style = "text-align: right;"> 52 Pages: <xsl:value-of select = "pages"/> 53 </p> 54 55 <p style = "text-align: right;"> 56 Publisher: 57 <xsl:value-of select = "publisher"/> 58 </p> 59 60 <!— кнопка добавления товара в тележку AddToCart --> 61 <form method = "post" action = "AddToCart"> 62 <p style = "text-align: center;"> 63 <input type = "submit" 64 value = "Add to cart"/> 65 </p>
Практический пример корпоративного приложения. Обзор архитектуры 463 66 67 <input type = "hidden" name = "ISBN" 68 value = "{T-SBN}"/> 69 </form> 70 </td> 71 </tr> 72 </table> 73 </div> 74 </body> 75 76 </html> 77 </xsl:ternplate> 78 </xsl:stylesheat> Рис. 9.8. Таблица стилей XSL, трансформирующая выданные сервлетом GetProductServlet данные в cHTML-код 1 <!DOCTYPB HTML PUBLIC "-//W3C//DTD HTML 2.0//EH" 2 "http://mfww.w3c.otg/Markup/html-spec/htinl-spec_toc .html"> 3 <html> 4 <head> 5 <META http-equiv="Content-Type" 6 contents"text/html; charset=UTF-8"> 7 <titls>XML How to Program — Description</title> 8 </haad> 9 <body> 10 <div class="header">XML How to Program</div> 11 <div class="author"> 12 by Deitel, Deitel, Nieto, Lin Samp; Sadhu: 13 <div claas="productDetails"> 14 <table> 15 <tr> 16 <td style-"text-align: center;"> 17 <img src="image«/xmlbtpl.jpg" claese"bookCover"/> 18 </td> 19 20 21 22 23 24 25 26 27 28 29 <forro action="AddToCaxt" method="post"> 30 <p style-"text-align: center;"> 31 <input value="Add to cart" type="s__nit"/> 32 </p 33 <inpyt value="OI30284l73" na-e="ISBN" 34 type="hidden"> 35 </forra> 36 </td> 37 </tr> 38 </table> <td> <P> <P> <P> <P> style-"text-align: right;">Price: $69.95</p> style-"text-align: right:"> ISBN: 01302B4l73</p> style-"text-align: right;">Pages: 1100</p> style-"text-align: right;"> Publisher: Prentice Hall</p>
464 Глава 9 39 </6iv> 40 </body> 41 </html> Рис. 9.9. cHTML-документ, полученный в результате XSLT-трансформации данных, выданных сервлетом GetProductServlet (Изображение публикуется с разрешения компании Pixo, Inc.) В этой главе был представлен обзор архитектуры учебного приложения Deitel Bookstore, в котором используются мощные корпоративные возможности Java, такие как сервлеты, EJB, RMI, XML и XSLT. Реализации каждого из уровней приложения рассматриваются в главах 10, 11 и 12. В главах 12, 13 будут приведены инструкции по развертыванию приложения Deitel BookstOTe на эталонной реализации J2EE и на серверах приложений BEA Web Logic и IBM WebSphere. Резюме • Паттерн проектирования MVC (модель—вид—контроллер) применительно к этому учебному корпоративному приложению отделяет данные и бизнес-логику (модель) от логики внешнего представления данных (вид) и логики управления (контроллер), • Многоуровневые приложения (их также называют ft-уровкевьши приложениями) имеют несколько составных модульных частей, называемых уровнями. Каждый уровень может физически размещаться на отдельном компьютере, • Информационный уровень, или уровень данных, содержит данные для приложения. Хранение э-1'их данные, как правило, организуется с помощью системы управлении реляционными базами данных (RDBMS). База данных может содержать информацию о товаре, например, описание, цену и имеющееся на складе количество, а также информацию о потребителе, такую как имя пользователя, адрес доставки счета и номер кредитной карты. • Средний уровень реализует бизнес-логику и логику внешнего представления данных для организации взаимодействий между клнект&ми приложения и данными приложения. Средний уровень выступаем в качестве посредника между данными е информационного уровни и клиентами приложения.
Практический пример корпоративного приложения. Обзор архитектуры 465 • Логика управления среднего уровня обрабатывает клиентские запросы (например, запрос на просмотр каталога товаров) и извлекает данные из базы данных. После этого логика внешнего представления среднего уровня обрабатывает данные, полученные с информационного уровня, и отправляет содержимое клиенту. • Бизнес-логика применяет бизнес-правила и обеспечивает сохранность и достоверность данных перед обновлением базы данных или представлением данных пользователю. Бизнес-правила определяют, как клиенты приложения могут и как не могут осуществлять доступ к данным, и каким образом данные обрабатываются в приложении. • Средний уровень также реализует логику внешнего представления данных приложения. Средний уровень принимает клиентские запросы, извлекает данные из информационного уровня и представляет содержимое клиентам. • Web-приложения обычно представляют информацию клиентам в виде XHTML-документов. Многие Web-приложении также представляют информацию клиентам с беспроводным доступом в виде документов Wireless Markup Language (WML) или документов Compact HyperText Markup Language (cHTML). • На среднем уровне приложения Deitel Bookstore используются XML и XSLT для динамического формирования содержимого клиентам различного типа, предоставляя поддержку для XHTML, WML, cHTML и, в принципе, для клиентов любого другого типа. • Клиентский уровень представляет собой пользовательский интерфейс приложения. Пользователи взаимодействуют с приложением через пользовательский интерфейс. Клиент взаимодействует со средним уровнем, чтобы посылать запросы и извлекать данные из приложения. После этого Клиент отображает пользователю данные, полученные со среднего уровня. • Билнес-пряниля приложения Deitel Bookstore реализуются с помощью компонентой Enterprise JavaBeans (EJB). В приложении Deitel Bookstore компоненты-сущности EJB совместно с базой данных, которую они представляют, образуют модель приложения. • Любая программа, которая способна осуществлять коммуникационные взаимодействия через RMI-IIOP, может использовать EJB-компоненты бизнес-логики и объекты абстракции базы данных. Например, может быть разработано административное инструментальное средство в виде автономного приложения Java, которое использует EJB-компоненты бизнес-логики для обработки заказов. • EJB-еущиости обеспечивают объектно-ориентированное представление информационного уровня приложения. Каждая EJB-сущность представляет определенный объект, хранящийся в базе данных. Экземпляры каждой EJB-сувдности представляют отдельные строки в базе данных. • Покупатели, посещающие Internet-магазин, порой просматривают каталог продукции и добавляют выбранные ими товары в магазинную тележку, но впоследствии принимают решение не покупать эти товары. Магазинная тележка, таким образом, оказывается отставленной. Вместо того, чтобы хранить такие отставленные тележки в базе данных, лучше воспользоваться сеансовыми EJB-компонентами, чтобы пользователь ощущал себя почти так же, как и при посещении реального магазина. • Сервлеты обеспечивают на среднем уровне интерфейс между клиентом и EJB-компонентами биэиес-логики. Сервлеты в приложении Deitel Bookstore реализуют в архитектуре MVC логику управления (контроллер). Сервлеты обрабатывают пользовательские запросы (поступающие через протоколы HTTP или WAP) и взаимодействуют с EJB-компонентами бизнес-логики. • Сер1влеты обрабатывают данные, извлеченные из EJB-комяояентов, и генерируют XML-представления, которые выступают в качестве промежуточных моделей данных приложения. • Таблицы стилей XSL осуществляют внешнее представление данных приложения, преобразуя модель XML в соответствующие форматы для клиентов различного типа. • Каждый сервлет в приложении Deitel Bookstore применяет XSL-преобразователь (объект Transformer) и XSLT-трансформации для генерирования содержимого для клиентов каждого из типов. Для формирования содержимого для соответствующего типа клиента предусмотрен отдельный набор XSLT-транСформаЦиЙ. • Сервлеты формируют XML-документы, к которым XSL-преобразоватеяь (объект Transformer) применяет XSLT-трансформации для создания соответствующего содержимого. Для поддержки других типов клиентов разработчик может создать дополнительный набор XSLT-трансформаций.
466 Глава 9 Терминология abandoned shopping cart — отставленная магазинная тележка application server — сервер приложений bottom tier — нижний уровень brick-and-mortar store — реальный магазин (супермаркет) business logic — бизнес-логика business rules — бизнес-правила business logic component — компонент бизнес-логики client tier — клиентский уровень Compact HyperText Markup Language (cHTML) container-managed persistence — персистент- ность, управляемая контейнером controller logic — логика управления controller logic component — компонент логики управления data tier — уровень данных delegate — делегировать design patterns — паттерны проектирования e-business — электронный бизнес e-business application — приложение для электронного бизнеса e-commerce — электронная коммерция e-commerce application — приложение для электронной коммерции Enterprise JavaBeans (EJB) entity EJB — компонент-сущность EJB four-tier application — четырехуровневое приложение information tier — информационный уровень middle tier — средний уровень model—view—controller (MVC) — архитектура модель—вид—контроллер multi-tier application — многоуровневое приложение presentation logic — логика представления данных shopping cart e-coramerce model — модель магазинной тележки для электронной коммерции three-tier architecture — трехуровневая архитектура top tier — верхний уровень Wireless Access Protocol (WAP) Wireless Markup Language (WML) XHTML XML XSL transformation — XSLT-трансформация XSL Transformer — XSL-преобразователь (объект Transformer) XSLT Упражнения для самоконтроля 9.1. Какой архитектурный паттерн проектирования играет главную роль в структуре приложения Deitel Bookstore? Какие преимущества дает применение этого паттерна проектирования? 9.2. Какая часть в архитектуре приложения Deitel Bookstore может быть вынесева в отдельный уровень? Почему? 9.3. В разделе 9.3 описывается гипотетическое бизнес-правило, требующее проверки кредитной карты. Опишите еще одно бизнес-правило, которое могло бы использоваться для приложения Deitel Bookstore, 9.4. Какие компоненты логики управления имеются в приложении Deitel Bookstore? Какие имеются компоненты бизнес-логики? Как компоненты логики управления взаимодействуют на сервере приложений с компонентами бизнес-логики? 9.5. Какой тип компонентов EJB в приложении Deitel Bookstore делает его наиболее близким к. реальному магазину? 9.6. Каким образом приложение Deite! Bookstore способно представлять содержимое клиентам практически любого типа? 9.7. Как может партнер в структуре отношений В2В (предприятие — предприятие) (например, корпоративный клиент) взаимодействовать с приложением Deitel Bookstore? Например, партнер В2В может захотеть заказывать на периодической основе большое количество экземпляров определенной книги, используемой для обучения новых сотрудников. Каким образом этот партнер В2В может программным образом осуществить заказ книг?
Практический пример корпоративного приложения. Обзор архитектуры 467 Ответы на упражнения для самоконтроля 9.1. В приложении Deitel Bookstore используется архитектура модель—вид—контроллер (Model—View.—Controller — MVC). В архитектуре MVC бизнес-логика (модель) отделяется о? логики управления (контроллер) и логики внешнего представления данных (вид). Реализации бизнес-логики, логики управления и логики внешнего представления данных могут меняться независимо друг от друга, без необходимости внесения изменений в другие компоненты. 9.2. Web-сервер может быть выделен в виде отдельного, четвертого уровня. Выделение Web-сервера как отдельного, четвертого уровня обеспечит модульность приложения и даст возможность осуществлять поддержку клиентов, использующих другие протоколы, отличные от HTTP. 9.3. Дополнительное бизнес-правило может требовать, чтобы потребители предоставляли свои адреса e-mail при регистрации. Бизнес-логика может обрабатывать формы для регистрации и отвергать регистрационные записи, в которых не указан адрес e-mail. 9.4. Компонентами логики управления в приложении Deitel Bookstore являются сервлеты. Компонентами бизиес-логики являются компоненты Enterprise JavaBeans. Сервлеты взаимодействуют с EJB-компонентами через контейнер EJB посредством RMI-IIOP. 9.5. EJB-компонент ShoppingCart реализован как сеансовый компонент EJB с состоянием, имитирующий магазинную тележку в реальном супермаркете. 9.6. Приложение Deitel Bookstore использует XSLT-трансформапии для представления содержимого клиентам каждого из типов. Сервлеты генерируют XML-документы, а разработчик предоставляет таблицы стилей XSL для преобразования сформированных серв- летами XML-документов в содержимое, подходящее для клиентов каждого из типов.
ю Практический пример корпоративного приложения. Логика представления данных и логика управления Цели • Понять роль логики представления данных в многоуровневом приложении. • Понять роль логики управления в многоуровневом приложении. • Научиться применять сервлеты при реализации среднего уровня приложений Enterprise Java. • Понять, каким образом технологии XML и XSLT позволяют осуществлять поддержку клиентов различных типов в Web* приложении. • Понять, каким образом параметры инициал и зад и и сервлета способствуют повышению гибкости сервлетов. Первым достается лучшее. Народная мудрость Расстояние придает виду особую прелесть. Томас Кзмпбел Изобретение — всего лишь выдающееся отклонение от эталона или же расширение области применения хорошего эталона... Эдвард Дж. Балвер-Литтон Не становитесь рабом, шаблона. Винсент Ван Гог Пока Гений растрачивал свою силу на эксцентричные порывы, мне увиделось лицо совершенно иного склада, имя которому — Применение. Анна «Петиция Барболд
470 Глава 10 В этой главе мы рассмотрим логику внешнего представления даииых и логику управления для приложения Deitol Bookstore. Логика управления отвечает в приложении за обработку пользовательских запросов. Например, когда покупатель посылает запрос на добавление книги в магазинную тележку, логика управления обрабатывает запрос и вызывает соответствующие методы бизнес-логики для выполнения запрашиваемого действия. После этого логика представления данных отображает полученный результат пользователю. Логика управления в приложении Deitel Bookstore реализована на сервлетах Java. После вызова методов бизнес-логики для обработки клиентских запросов сервлеты генерируют XML-документы, содержащие данные, отправляемые клиенту. Эти XML-документы не являются специфичными для конкретного типа клиента (например, Web-браузер, сотовый телефон и т.д.), они всего лишь размечают данные, предоставленные бизнес-логикой. Логику представления реализуют таблицы стилей XSL (XSLT-трансформации), образующие в архитектуре MVC уровень представления данных (вид). Таблицы стилей XSL преобразуют XML-доку- ментов в формат, подходящий для клиента соответствующего типа. Например, XSLT-трансформация может генерировать XHTML-докуменТ для представления его в Web-браузере стационарного компьютера, либо WML-документ для представления его в WAP-браузере сотового телефона. Применение XSLT-трансформаций в качестве логики внешнего представления данных дает возможность разработчикам расширять набор типов клиентов, которые поддерживаются приложением, не внося при этом изменений в логику управ-
Практический пример корпоративного приложения 471 ления (контроллер) или в бизнес-логику (модель). Для добавления поддержки нового типа клиента разработчик просто предоставляет дополнительное множество XSLT-трансформаций, которые формируют соответствующий выходной результат для нового типа клиента. Затем разработчик модифицирует XML-файл конфигурации, чтобы подключить поддержку нового типа клиента. [Замечание. Для приложения Deite] Bookstore требуется XSL-преобразователь Xalan от Apache Software Foundation. Загрузите Xalan версии 2.1 с сайта xml.apache.org/disi/xalan-} и скопируйте файлы xalan.jar и xerces.jar в ваш каталог jre/lib/ext SDK J2SE1.] Эта глава является первой из трех глав, в которых представлена реализация учебного приложения Deitel Bookstore. В главах 11 и 12 будет рассматриваться бизнес-логика и компоненты-сущ ноет и EJB, необходимые для обработки заказов и организации взаимодействия с базой данных. 10.2. Базовый класс XMLServlet Каждый сервлет в учебном приложении Deitel Bookstore расширяет класс XMLServlet (рис. 10.1), который обеспечивает стандартную инициализацию и предоставляет утилитарные методы. Чтобы обеспечить поддержку нескольких типов клиентов, каждый сервлет в приложении Deitel Bookstore создает XML-документ, содержащий исходные данные, полученные с уровня бизнес-логики и информационного уровня приложения. XSLT-трансформация обрабатывает этот XML-документ и отправляет полученный результат клиенту. Класс XMLServlet реализует эти стандартные функциональные возможности. Метод init (строки 41-85) инициализирует сервлет. Контейнер сервлетов предоставляет аргумент ServletConfig при инициализации сервлета. В строке 49 извлекается имя таблицы стилей XSL, которая будет использоваться для формирования содержимого для клиентов каждого из поддерживаемых приложением типов. Это имя файла передается в качестве параметра инициализации сервлета, поэтому разработчик должен определить этот параметр при развертывании сервлета. В строке 52 создается экземпляр объекта DocumentBuilderFactory, который будет использоваться для создания объектов DocumentBuilder, формирующих XML-документы. В строке 55 создается объект-мастер TransformerFactory, с помощью экземпляров которого класс XMLServlet выполняет XSLT-трансформации. В строках 58-81 задается интерфейс UKlResolver для TransformerFactory, который позволяет преобразователям Transformer вычислять относительные URI в XSL-доку мент ах. Например, если XSL-документ ссылается на другой XSL-доку- мент с использованием элемента xsl:include с указанием относительного XJRI (например, /XSLT/XHTML/error.xsl), объект Transformer вызывает метод интерфейса URIResolver для определения, откуда объекту Transformer следует загрузить включаемую XSL-таблицу. В строке 67 вычисляется относительный URI путем вызова метода getResource интерфейса ServletContext, который возвращает объект URL. В строке 71 возвращается объект Stream Source для потока ввода Input St ream объекта URL. Объект Transformer использует этот объект Stream- Source для чтения содержимого включенного XSL-документа. 1 // XMLServlet.Java 2 // XMLServlet - базовый класс для сервлетов, которые 3 // генерируют XML-документы и выполняют XSLT-трансформации. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 б // Набор базовых пакетов Java 1 На момент подготовки к изданию перевода книги актуальной была версии 2.4.1 Xalan. — Прим. ред.
472 Глава 10 7 import java.io.*; 8 Import java.util.*; 9 import j ava,net.ORL; 10 11 // Пакеты расширений Java 12 import javax.servlet.*; 13 import 3avax.servlet.http.*; 14 import j avax.xml.parsers.*; 15 import javax.xml.transform.*; 16 import j avax. xml. transform, dom. *,- 17 import javax.xml.transform.stream.*; 18 19 // Пакеты сторонних поставщиков 20 import org.w3c.dom.*; 21 import org.xml.sax.SAXException; 22 23 // Пакеты Deitel 24 import com,deitel.advjlntpl.bookstore.model,*; 25 26 public class XMLServlet extends HttpServlet { 27 28 /( мастер дли создания объектов. Docvubent&uilder 29 private DocumentBuildexFactory builderFactory; 30 31 // мастер для создания преобразователей Transformer 32 private TransformerFactory transformerFactory; 33 34 // XSL-файл, представляющий содержимое сервлета 35 private String XSLFileName; 36 37 // список объектов ClientModel для определения типа клиента 38 private List clientList; 39 40 // инициализация сервлета 41 public void init{ ServletConfig config ) 42 throws ServletException 43 { 44 // вызов метода init суперкласса для инициализации 45 super,init( config ); 46 47 // использование параметра InitParameter для задания XSL-файла, 48 // используемого при трансформации сформированного содержимого 49 setXSLFileName( config.getlnitParaweter( "XSL FILE" \ ); SO 51 // создание нового объекта DocumentBuilderFactory 52 builderFactory = DocumentBuilderFactory.newInstance(); 53 54 // создание нового объекта TransformerFactory 55 transformerFactory = TransformerFactory.newlnsfcance(); 56 57 // настройка объекта DRIResolver для вычисления относительных путей в XSLT-таблице 58 transformerFactory.setORIResolver( 59 60 new URIResolver() { 61
Практический пример корпоративного приложения 473 62 // вычисление ссылки href как относительного пути к ServletContext 63 public Source resolve( String href, String base ) 64 { 65 try 1 66 ServletContext context = getServletContext{); 67 URL url = context.getResource( href J; 68 69 // создание объекта StreamSource для чтения документа 70 return new StreamSource[ -url.openStream() ); 71 } 72 73 // обработка исключения при получении заданного документа 74 catch ( Exception exception ) ( 75 exception.printStackTrace0; 76 77 return null; 78 } 79 } BO > 81 ); // конец обращения к setURIResolver 82 83 // создание списка объектов ClientModel 84 clientLiat = buildClientList(); 85 } 86 B7 // получение экземпляра класса DocumentBuilder для построения XML-документов 88 public DocumentBuilder getDocumentBuilder( boolean validating } B9 { 90 // создание нового объекта DocumentBuilder 91 try { 92 93 // установка режима проверки 94 builderFactory.setValidating{ validating ); 95 96 // возврат нового объекта DocumentBuilder вызвавшему процессу 97 return builderFactory.newDocumentBuilder{>; 98 ) 99 100 // обработка исключения при создании объекта DocumentBuilder 101 catch ( ParserConfigurationException parserException ) ( 102 parserException.printStackTrace(); 103 104 return null; 105 ) 106 107 } // конец иетода getDocumentBuilder 10B 109 // получение синтаксического анализатора, не проверяющего на допустимость 110 public DocumentBuilder getDocumentBuilder() 111 i 112 return getDocumentBuilder( false ); 113 }
474 Глава 10 114 115 // установка имени XSL-файла для преобразования содержимого сервлета 116 public void setXSLFileName ( String fileName ) 117 { 118 XSLFileName = fileName; 119 } 120 121 // получение имени XSL-файла для преобразования содержимого сервлета 122 public String getXSLFileName() 123 1 124 return XSLFileName; 125 } 126 127 // запись XML-документа клиенту с использованием предоставленного 128 // объекта ответа после преобразования XML-документа с 129 // применением специфичной для клиента таблиц» стилей XSL 130 public void writeXML( HttpServletRequest request, 131 HttpServletResponse response. Document document ) 132 throwB XOException 133 { 134 // получение текущего сеанса, создание его, если сеанс не существует 135 HttpSession session = request.getSession[ true ); 136 137 // получение объекта CHentModel из данных сеанса 138 ClientModel client = ( ClientModel ) 139 aession.getAttribute( "ClientModel" ); 140 141 // если клиента нет (null), получить новый объект ClientModel 142 // для заданного заголовка User-Agent и сохранить его в данных сеанса 143 if { client = null ) { 144 String userAgent = request.getHeaderl "User-Agent" ); 145 client = getClientModel( userAgent ) ,- 146 session.setAttribute{ "ClientModel", client }; 147 } 148 149 // задание соответствующего типа содержимого Content-Type для клиента 150 response.setContentTypeС client.getCoiitentType.() ); 151 152 // получение объекта PrintWriter для записи данных клиенту 153 PrintWriter output = response.getWriter(>; 154 155 // формирование имени файла для XSLT-документа 156 String xslFile = client.getXSLPatM) + getXSLFileName(J; 157 158 // открытие потока ввода Inputstream для XSL-документа 159 Inputstream xsIStream = 160 getServletContext().getResOurceAsStream( xslFile ); 161 162 // преобразование XML-документа с применением XSLT-трзнсформации 163 transform( document, xslStream, output ); 164 165 // освобождение и закрытие объекта PrintWriter 166 output.close[); 167 168 J // конец метода writeXML
Практический пример корпоративного приложения 475 169 170 // преобразование XML-документа с применением указанного потока 171 // ввода XSLT к алпись полученного документа в указанный объект PrintWriter 172 public void transform( Document document, 173 Inputstrearn xslStream, PrintWriter output ) 174 { 175 // создание объекта Transformer и применение XSLT-трансформации 176 try { 177 178 // создание объекта DOMSource для исходного XML-документа 179 Source xmlSource = new DOMSource( document ); 180 181 // создание исходного потока StreamSource для XSLT-документа 182 Source xslSource = 183 new StreamSource( xslStream ); 184 185 // создание потока вывода StreamReeult для результата трансформации 186 Result result = new StreamResult{ output }; 187 188 // создание объекта Transformer для XSLT-трансформации 189 Transformer transformer = 190 transformerFactory.newTransformer( xslSource ); 191 192 // трансформация и доставка содержимого клиенту 193 transformer.transform( xmlSource, result ); 194 195 ) // конец блока try 196 197 // обработка исключения при трансформации XML-документа 198 catch ( TransformerException transformerException ) { 199 transformerException.printStackTrace(); 200 } 201 202 } // конец метода transform 203 204 // формирование элемента error содержащего сообщение об ошибке 205 public Node buildErrorMessage( Document document, 206 String message) 207 { 208 // создание элемента error 209 Element error = document.createElement( "error" }; 210 211 // создание элемента message 212 Element errorMessage - 213 document.createElement( "message" ); 214 215 // создание текста сообщения и добавление его в элемент message 216 errorMessage.appendChild ( 217 document.createTextNode( message ) ); 218 219 // добавление элемента message в элемент error 220 error.appendChild( errorMessage ) ; 221 222 return error; 223 }
Глава 224 225 // формирование списка объектов ClientModel для доставки 226 // соответствующего соквржиного клиентам каждого из типов 227 private List buildClientListО 228 { 229 // получение проверяющего на допустимость анализатора DocumentBuilder для клиентского XML-документа 230 DocumentBuilder builder = getDocumentBuildsr( true ); 231 232 // создание списка клиентов 233 List clientList = new ArrayListO; 234 235 // получение имени XML-документа, содержащего информацию 236 // о клиентах, извлеченную иа объекта ServletContext 237 String clientXML = getServletContext().getlnitParameter( 23B "CLIENT_LIST" ); 239 240 // чтение типов клиентов иа XML-документа и создание объектов ClientModel 241 try { 242 243 // открытие потока ввода InputStream для XML-документа 244 InputStream clientXMLStreain = 245 getServletContext().getResourceAsStream( 246 clientXML )• 2Л1 248 // синтаксический анализ XML-документа 249 Document clientsDocument = 250 builder .parse [ clientXMLStreain ); 251 252 // получение списка узлов NodeList элементов client 253 NodeList cliontElements = 254 clientsDocument.getEleraentsByTagName( "client" ); 255 256 // получение длины списка NodeList 257 int listLength = clientElements,getLength(); 258 259 // обработка списка NodeList элементов client 260 for { int i = 0; i < listLength; i++ ) { 261 262 // получение следующего элемента client 2 63 Elemsnt client = 264 ( Element ) ClientElements.item( i ); 265 266 // получение элемента agent из элемента client 267 Element agentElement = ( Element } 268 client.getElementsByTagName( 269 "userAgent" >.item< 0 ); 270 271 // получение дочернего текстового узла элемента agent 272 Text agentText = 2 73 ( Text ) agentElement.getFirstChild(); 274 2 75 // получение значения текстового узла agent 276 String agent = agent™ext.getModeValue{);
Практический пример корпоративного приложения 477 278 // получение элемента contentType 279 Element typeElement = { Element ) 280 client.getElementsByTagName( 281 "contentType" }.item{ 0 ); 282 283 // получение дочернего текстового узла элемента contentType 284 Text typeText = 285 ( Text ) typeElement.getFirstChild() ; 286 287 // получение значения текстового узла contsntType 288 String type = typeText.getNodeValue(); 289 290 // получение элемента XSLPath 291 Element pathElement = ( Element ) 292 client.getElementsByTagName( 293 "XSLPath" ).item( 0 ); 294 295 // получение дочернего текстового узла элемента XSLPath 296 Text pathText = 297 ( Text ) pathElement.getFirstChildt); 296 299 // получение значения текстового узла XSLPath 300 String path = pathText.getKodeValueO; 301 302 // добавление нового объекта ClientModel со свойствами 303 // userAgent, contentType и XSLPath для данного элемента client 304 clientList.add{ 305 пен ClientModel! agent, type, path ) ); 306 1 307 308 } // конец блока try 309 310 // обработка исключения SAXException при разборе XML-документа 311 catch { SAXException saxException ) { 312 saxException.printStackTrace(); 313 } 314 315 // перехват исключения ввода/вывода при чтении XML-документа 316 catch ( lOException ioException ) { 317 ioException.printStackTrace () ,- 31B } 319 320 // возврат созданного списка объектов ClientModel 321 return clientList,- 322 323 > // конец метода buildClientList 324 325 // получение объекта ClientModel для аадакиого НТГР-эаголовка User-Agent 326 private ClientModel getClientModel( String header ) 327 { 328 // получение итератора для списка clientList 329 Iterator iterator = clientList.iterator(); 330
478 Глава 10 331 // нахождение объекта ClientModel, свойство userAgent которого 332 // является подстрокой указанного HTTP-заголовка User-Agent 333 while ( iterator,hasNext() ) { 334 ClientModel client = ( ClientModel ) iterator.next{),- 335 336 // если данное свойство userAgent обгеиа 337 // ClientMode является подстрокой НТТР-ааголовка 338 // User-Agent, вернуть ссылку на объект ClientModel 339 if (■ header.indexOf( client.getUserAgent() ) > -1 } 340 return client; 341 } 342 343 // возврат объекта ClientModel no умолчанию, если другие объекты не подходят 344 return new ClientModel( 345 "DEFAULT CLIENT", "text/htnO.", "/XHTML/" ); 346 347 } // конец метода getClientModel 348 ) __ __ Рис. 10.1. Базовый класс XMLServlet для сервлетов а приложении Deitel Bookstore В строке 84 вызывается метод bnildClientList, который прочитывает файл конфигурации Clients.xml и создает список объектов ClientModel (рис. 10.4). Экземпляры класса XMLServlet используют эти объекты для того, чтобы определить, какую таблицу стилей XSL применять для клиента каждого из типов. Метод getDocumentBuilder (строки 88-107) создает объект Document Builder для синтаксического анализа и создания XML-документов. Аргумент типа boolean определяет, должен ли метод getDocumentBuilder создавать синтаксический анализатор XML, проверяющий документы на допустимость. Класс XMLServlet сохраняет объект DocumentBuilderFactory в переменной экземпляра (строка 29), что позволяет избежать необходимости создавать новый объект DocumentBuilderFactory каждый раз, когда нужно получить объект DocumcntBuilder. Перегруженный метод getDocumentBuilder (строки 110-113) вызывает метод getDo- cumentBuilder с аргументом false, чтобы создать синтаксический анализатор XML, не проверяющий документы на допустимость. Методы setXSLFileName и getXSLFileName (строки 116-125) представляют собой методы set и get для свойства XSLFileName класса XMLServlet. Свойство XMLFileNaine определяет имя XSLT-документа, который преобразует содержимое сервлета для определенного типа клиента, В приложении Deitel Bookstore для XSLT-трансформаций, предназначенных для различных типов клиентов, предусмотрены отдельные каталоги. Например, файл products, xsl в каталоге /XSLT/XHTML формирует содержимое в формате XHTML, тогда как версия файла products.xsl в каталоге /XSLT/WML формирует содержимое в формате WML. Свойство XSLFileName определяет только имя файла (например, pro ducts, xsl); соответствующий каталог для определенного типа клиента задается в каждом из объектов ClientModel. Метод writeXML (строки 130-168) определяет тип клиента, осуществляющего доступ к сервлету, и вызывает метод transform для выполнения XSLT-трансфор- мацик. В строках 138-139 осуществляется попытка получить объект ClientModel из объекта HttpSession. Вели объект HttpSession не содержит объект ClientModel, в строке 144 извлекается клиентский заголовок User-Agent, который уникально идентифицирует тип клиента. В строке 145 вызывается метод getClientModel для получения соответствующего объекта ClientModel для данного типа клиента.
Практический пример корпоративного приложения 479 В строке 146 объект Client Model помещается в объект HttpSession для последующего использования. В строке 150 из объекта ClientModel извлекается тип содержимого клиента и осуществляется настройка объекта HitpServletResponse. В строке 156 формируется полный относительный путь к таблице стилей XSL путем конкатенации свойства XSLPath объекта ClientModel со свойством XSLFile- Name сервлета. В строках 159-160 открывается поток ввода InputStream для XSLT-трансформации. В строке 163 вызывается метод transform для выполнения преобразования и отправки полученных результатов клиенту. Метод transform (строки 172-202) выполняет указанную XSLT-транеформа- цию для заданного XML-документа и записывает ее результат в указанный объект PrintWriter. В строке 179 создается объект DOMSource для XML-документа. В строках 182-183 создается объект входного потока StreamSource для XSL-доку- мента. В строке 159 создается объект выходного потока StreamResult для объекта PrintWriter, в который объект преобразования Transformer будет записывать результаты XSLT-трансформации. В строках 189-190 создается объект Transformer путем вызова метода newTransformer класса TransfonnerFactory. В строке 193 вызывается метод transform класса Transformer для выполнения XSLT-трансформации над заданным исходным объектом (Source) и записи результатов в заданный целевой объект (Result). Метод buildErrorMessage (строки 205—223) представляет собой утилитарный метод для формирования элемента XML (объект Element), содержащего сообщение об ошибке. В строке 209 с использованием предоставленного документа document создается объект error типа Element. В строках 212-213 создается объект errorMessage типа Element, который будет содержать фактическое сообщение об ошибке. В строках 189-190 создается узел Text, содержащий текст сообщения об ошибке, после чего этот узел Text добавляется в объект (элемент XML) error- Message типа Element. В строке 220 объект (элемент XML) errorMessage добавляется в объект (элемент XML) crrorElement, а в строке 223 возвращается законченный объект (элемент XML) error. Метод buildClientList (строки 227-323) формирует список (List) объектов ClientModel, прочитывая информацию о клиенте из XML-файла конфигурации (рис. 10.2). В строках 237-238 из параметра инициализации контекста сервлета ScrvIetContext извлекается имя файла конфигурации. Администратор развертывания должен указать значение этого параметра при развертывании приложения. В строках 244-246 открывается поток ввода InputStream для чтения данных из файла конфигурации. В строках 249-250 осуществляется синтаксический разбор XML-файла конфигурации, и в памяти формируется объект Document. В строках 253-254 осуществляется получение списка узлов NodcList XML-элементов client (объекты типа Element) из документа. Каждый XML-элемент client имеет атрибут (объект типа Attribute) name и три дочерних элемента — user Agent, contentType и XSLPath — каждый из которых соответствует свойству объекта ClientModel. В строках 260-306 формируются объекты ClientModel для каждого элемента client в файле конфигурации, и эти объекты ClientModel добавляются в список List. Метод getClientModel (строки 326-347) возвращает объект ClientModel, содержащий подстроку заголовка User-Agent, уникально идентифицирующую тип клиента. Например, подстрока Mozilla/4.0 (compatible; MSIE 5) заголовка User-Agent уникально идентифицирует клиента как браузер Microsoft Internet Explorer версии 5. В строках 333-341 свойство userAgent каждого из объектов ClientModel сравнивается с заданным заголовком User-Agent. Если свойство userAgent является подстрокой заголовка User-Agent, значит, объект ClientModel соответствует предполагаемому, и в строке 340 он возвращается вызвавшему процессу. В строках 344-345 формируется обобщенная модель клиента ClientModel в формате XHTML, используемая по умолчанию, если данный заголовок User-Agent не соответствует какому-либо другому типу клиента.
480 Глава 10 На рис. 10.2 представлен образец файла конфигурадии, который дает возможность приложению поддерживать несколько наиболее распространенных типов клиентов. На рис. 10.3 представлено определение DTD для этого файла конфигурации. 1 <?xml version = "1.0" encoding = "0TF-8"?> 2<!DOCTYPE clients SYSTEM 3 "http://www.deitel.com/advjhtpl/clients.dtd"> 4 5<!— Файл конфигурации для клиентов приложения Deitel Bookstore —> 6 7 <clients> в Э <!-- Клиент Microsoft Internet Explorer версии 5 —> 10 <client name = "Microsoft Internet Explorer 5"> 11 <userAgent>Mozilla/4.0 (compatible; MSIE 5</userAgent> 12 <contentType>text/html</contentType> 13 <XSLPath>/XSLT/XHTML/</XSLPath> 14 </client> 15 16 <!-- Клиент Microsoft Internet Explorer версии б --> 17 <client name = "Microsoft Internet Explorer 6"> 15 <userAgent?Mozilla/4.0 (compatible; MSIE 6</userAgent> 19 <contentType>text/html</contentType> 20 <XSLPath>/XSLT/XHTML/</xSLPath> 21 </elient> 22 23 <!— Клиент netscape версии 4. 7x —> 24 <client name = "Netscape 4.7"> 25 <userAgent>Mozilla/4.7</userAgent> 26 <contentType>text/html</contentType> 27 <XSLPath>/XSLT/XHTML/</XSLPath> 28 </client> 29 30 <*— Клиент Mozilla/Netscape версии б —> 31 <client name = "Mozilla/Netscape 6"> 32 <userAgent>Gecko</userAgent> 33 <contentType>text/html</contentType> 34 <XSLPath>/XSLT/XHTML/</xSLPath> 35 </client> 36 37 <!-- Клиент HML-браузер Phone.com --> 38 <client name = "Openwave SDK Browser"> 39 <userAgent>OP.Browaer/4</userAgent> 40 <contentType>text/vnd.wap.wml</contentType> 41 <XSLPath>/XSLT/WML/</XSLPath> 42 </client> 43 44 <!-- Клиент WML-браузер Nokia —> 45 <client name = "Nokia WAP Toolkit Browser"> 46 <userAgent>Nokia-WAP</userAgent> 4 7 <contentType>text/vnd.wap.wml</contentType> 48 <XSLPath>/XSLT/WML/</XSLPath> 49 </client> 50 51 <!-- Клиент браузер Pixo iMode --> 52 <client name = "Pixo Browser">
Практический пример корпоративного приложения 481 53 <userAgent>Pixo-Browser</iiserAgent> 54 <contentType>Lext/html</contentType> 55 <XSLPath>/XSLT/cHTML/</XSLPath> 56 </client> 57 58 </clients> Рис. 10.2. Файл конфигурации, позволяющий осуществлять поддержку клиентов различного типа 1 <!— clients.dtd —> 2 <!-- DTD для задания типов клиентов приложения Bookstore --> 3 4 <!ELEMENT clients ( client+ )> 5 6<!ELEMENT client { userAgent, contentType, xSLPath )> 7 <'ATTLIST client name CDATA #REQUIRED> 8 9 <1 ELEMENT userAgent ( #PCDATA )> 10 <!ELEMENT contentType ( #PCDATA )> 11 <!ELEMEMT XSLPath { #PCDATA )> Рис. 10.3. Определение DTD для документа clients.xml Класс ClientModel (рис. 10.4) представляет конкретный тип клиента. В строках 18-23 определен конструктор ClientModel. В строках 26-59 предоставляются методы set get для свойств user Agent, contentType и XSLPath. 1 // ClientModel.Java 2 // ClientModel - утилитарный класс для определения надлежащего 3 // типа содержимого Content-Type и пути к XSL-файлам для каждого 4 // из типов клиентов, поддерживаемых приложением Bookstore. 5 package com.deitel.advjhtpl.bookstore.model; 6 ill Набор базовых пакетов Java 8 import j ava.io.*; 9 10 public class ClientModel implements Serializable { 11 12 // Свойства объекта ClientModel 13 private String userAgent; 14 private String contentType; 15 private String XSLPath; 16 17 // Конструктор ClientModel для инициализации объектов данных 18 public ClientModel( String agent, String type, String path ) 19 i 20 setUserAgent( agent ); 21 setContentType( type ); 22 setXSLFath( path ); 23 } 24 25 // задание подстроки UserAgent 26 public void setUserAgent{ String agent ) 27' ( 28 userAgent = agent;
482 Глава 10 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 } // получение подстроки OserAgent public String getUserAgent() return userAgent; // задание типа содержимого ContentType public void setContentType( String type ) contentType = type; / получение типа, содержимого ContentType public String getContentType() return contentType; // задание пути к таблице стилей XSL public void setXSLPath( String path ) XSLPath = path; // получение пути к таблице стилей XSL public String getXSLPath() return XSLPath; Рис. 10.4. Класс Client Mode l для представления поддерживаемых топов клиентов 10.3. Сервлеты, реализующие магазинную тележку Для реализации Internet-магазинов традиционно используется несколько моделей электронной коммерции. К таким моделям относятся аукционные сайты, электронные торги, бартерные сделки и сделки с назначением вашей собственной цены. В нашем приложении используется известная модель с «магазинной тележкой», в которой-покупатель имеет возможность просматривать ассортимент имеющихся товаров и выбирать интересующие его товяры для покупки. Каждый выбранный товар помещается в виртуальную магазинную тележку. После того как покупатель закончит отбор товаров, активизируется процесс проверки информации о платежных средствах покупателя и реквизитов для доставки товара, после чего транзакция завершается. Обслуживание виртуальной магазинной тележки является составной частью бизнес-логики, реализованной в EJB-компонентах приложения. На рис. 10.5 показан поток клиентских запросов и ответов для нового покупателя, который заказывает несколько экземпляров книги в своем Web-браузере. Зайдя на сайт, покупатель попадает на основную страницу Lndcx.html. Затек он переходит по ссылке, чтобы просмотреть каталог товаров, который генерируется серв- летом GetAHProductsServlet. После этого покупатель выбирает нужный ему товар
Практический пример корпоративного приложения 483 из каталога, а сервлет GretAllProductsServlet отображает подробные сведения об этом товаре (в данном случае, к таким сведениям относятся рисунок обложки книги, авторы книги и ее цена). Покупатель добавляет товар в магазинную тележку с помощью сервлета AddToCartServlet, а сервлет ViewCartServlct отображает содержимое магазинной тележки. Покупатель изменяет количество приобретаемого вида товара с помощью сервлета UpdateCartServlct, и сервлет ViewCartServlet снова отображает покупателю содержимое магазинной тележки. Далее Покупатель переходит по ссылке, чтобы зарегистрироваться в качестве нового покупателя, и заполняет регистрационную форму на странице register.html. Далее покупатель отправляет форму сервлету RegisterScrvlet, Этот сервлет, в свою очередь, вызывает сервлет LoginScrvIet, через который пользователь осуществляет вход в Internet-магазин. Далее покупатель выбирает ссылку, вызывающую сервлет Check- outServlet. Сервлет CheckontServlet размещает заказ и переадресовывает покупателя к сервлету VicwOrderServlet, который отображает подробную информацию о заказе. О index.htm GetAilProductsServlet GetProductServlet О CpdateCartServlet О) ViewCartServlet Regis terServlet AddToCartServlet Л zregieter.html ■О -о LoginServlet Che ckoutSe rvle t ViewOrderServlet Рис. 10.5, Поток клиентских запросов и данные, возвращаемые приложением Deitel Bookstore для клиентов XHTML 10.3.1. Сервлет AddToCartServlet По мере просмотра покупателями содержимого Internet-магазина, они добавляют нужные им товары в свои магазинные тележки. Сервлет AddToCartServlet (рис. 10.6) обрабатывает каждый из запросов покупателя на добавление товара в магазинную тележку. Класс AddToCartServlet расширяет класс XMLServlet, в котором реализованы функциональные возможности, общие для всех сервлетов в приложении Deitel Bookstore, например, инициализация и XSLT-трансформации, В строке 41 из объекта HttpServletRequest извлекается ISBN-код книги, которую покупатель хотел бы приобрести. В строках 37-38 из объекта HttpSession сервлета извлекается ссылка на объект магазинной тележки (ShoppingCart). Возможно, что у покупателя еще нет магазинной тележки, и в этом случае ссылкой на
484 Глава 10 объект ShoppingCart будет null. При этом в строке 59 с помощью собственного интерфейса ShoppingCart создается новый объект ShoppingCart, а ссылка на него сохраняется в объекте сеанса HttpSession сервлета для дальнейшего использования (строка 62). EJB-компонент ShoppingCart представляет собой сеансовый компонент с состоянием и будет, следовательно, сохранять содержимое магазинной тележки покупателя в ходе сеанса просмотра. 1 // AddToCartServlet.Java 2 // Сервлет AddToCartServlet добавляет товар в магазинную 3 // тележку покупателя. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import Java.io.*; 8 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http.*; 12 import javax.naming.*; 13 import javax.rmi.*; 14 import javax.ejb.*; 15 16 // Пакеты сторонних поставщиков 17 import org.w3c.dom.*; IS 19 // Пакеты Deitel 20 iroport com.deitel.advjhtpl.bookstore.model.*; 21 import com.deitel.advjhtpl.bookstore.ejb.*; 22 import com.deitel.advjhtpl.bookstore.exceptions.*,- 23 24 public Class AddToCartServlet extends XMLServlet { 25 26 // обработка НТТР-запросоа post 27 public void doPost( HttpSeivletRequest request, 28 HttpServletResponse response ) 29 throws ServletException, lOException 30 { 31 Document document = getDocumentBuilder О . newDocument () ; 32 33 // получение объекта HttpSession для данного пользователя 34 HttpSession session = request,getSession(); 35 36 // получение объекта ShoppingCart для покупателя 37 ShoppingCart ShoppingCart = 38 ( ShoppingCart ) session.getAttribute( "cart" ); 39 40 // получение параметра ISBN из объекта запроса 41 String isbn = request.getParameter{ "ISBN" ); 42 43 // получение объекта ShoppingCart и добавление приобретаемого товара в тележку 44 try { 45 InitialContext context = new InitialContext(); 46 47 // создать облеки ShoppingCart, если у покупателя «""° нет тележкгс 48 if ( ShoppingCart = null ) {
Практический пример корпоративного приложения 485 49 Object object = context.lookup( 50 "java:comp/env/ejb/ShoppingCart" ); 51 52 // приведение ссыпки типа Object к типу ShoppingCartHome 53 ShoppingCartHome shoppingCarfcHome = 54 ( Shopping-Car tHome ) 55 PortableRemoteObj ect.narrow( 56 object, ShoppingCartHome.class ); 57 58 // создание объекта ShoppingCart с помощью интерфейса SftoppingCartHoine 59 ShoppingCart = ShoppingCartHome.create(); 60 61 // сохранение объекта ShoppingCart в данных сеанса 62 session.setAttribute( "cart", ShoppingCart ); 63 } 64 65 // добавление товара в магазинную тел«жку покупателя 66 ShoppingCart.addProduct( isbn ); 67 68 // переадресация покупателя к сервлету 69 // ViewCartServlet для просмотра содержимого тележки 70 response.sendRedirect( "ViewCart" ); 71 72 } II конец блока try 73 74 // обработка исключения при поиске EJB-компонента ShoppingCart 75 catch ( NamingException namingException ) { 76 namingException.printStacfcTrасе () ; 77 78 Stxing error = "The ShoppingCart EJB was not " +• 79 "found in the JNDI directory."; 80 81 // добавление в XML-документ сообщении об ошибке 82 document.appendChiId( buildErrorMessage( 83 document, error ) ); 84 85 writeXML( request, response, document ); 86 } 87 88 // обработка исключения при создании EJB-компонента ShoppingCart 89 catch ( CreateException CreateException ) { 90 CreateException.printstackTrace(); 91 92 String error = "ShoppingCart could not be created"; 93 94 // добавление в XML-документ сообщения об ошибке 95 document.appendChild( buildErrorMessage( 96 document, error ) ); 97 98 writeXML( request, response, document ) ; 99 } 100 101 // обработка исключения, если товар не найден 102 catch ( ProductHotFoundException productException ) {
486 Глава 10 103 productException.printStackTrace(); 104 105 // добавление в XML-документ сообщения об ошибке 106 document.appendChild( buildErrorMessage( 107 document, productExeeption.getMessage() ) ); 108 10Э writeXMM request, response, document ); 110 ] 111 112 \ 11 конец метода doGet 113 } „__ _ .^___ Рис. 10.6. Се pa лет AddToCartServiet д/m добавдения товаров в магазинную тележку Вели сервлет получает допустимую ссылку на объект ShoppingCart, в строке 60 вызывается бизнес-метод addProduct класса ShoppingCart с указанием ISBN-кода книги в качестве аргумента. В строке 70 вызывается метод sendRedirect класса HttpServletResponse для переадресации клиента к сервлегу ViewCartServlet, который отображает список книг, имеющихся в магазинной тележке. В приложении предусмотрено три контролируемых исключения, которые могут быть возбуждены из блока try в строках 44-72. В строках 75-86 перехватывается исключение NamingException, если EJB-компонент ShoppingCart не найден в каталоге JNDI, В строках 89-99 перехватывается исключение CreateException, если при создании магазинной тележки покупателя имела место ошибка. В строках 102-110 перехватывается исключение ProdoctNotFoxradException, представляющее собой специфичное для приложения исключение, которое будет подробно рассматриваться в главе 11. Исключение ProductNotFoundException указывает, что книга с заданным ISBN-кодом не может быть найдена в базе данных. Каждый из обработчиков исключений использует метод buildErrorMessage класса XMLServlet для создания сообщения об ошибке в виде XML-кода и отправки его клиенту. В каждом случае предоставляется сообщение, информирующее пользователя об ошибке, и выводится трасса стека для исключения, чтобы оказать содействие разработчику при отладке приложения. При вызове метода writeXML класса XMLServlet (строки 85, 95 и 109) содержимое отправляется клиенту. 10.3.2. Сервлет ViewCartServlet После того как покупатель добавил товар в магазинную тележку, сервлет ViewCartServlet (рис. 10.7) отображает содержимое тележки. Сервлет ViewCartServlet является подклассом класса XMLServlet. 1 // ViewCartServlet.Java 2 // Сервлет ViewCartServlet отображает содержимое 3 / / магазинной тележке покупателя. 4 package com,deitel.advjhtpl.bookstore,servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import Java.util.*; 9 import Java.text.*; 10 11 // пакеты расширений Java 12 import javax.aervlet.*; 13 import javax.servlet.http.*; 14 impost javax.rmi.*;
Практический пример корпоративного приложения 487 15 16 // Пакеты сторонних поставщиков 17 import org.w3c.dom.*; 18 19 // Пакеты Deitel 20 import com.deitel.advjhtpl.bookstore.model.*; 21 import com.deitel.advjhtpl.bookstore.ejb.*; 22 23 public class ViewCartServlet extends XMLServlet { 24 25 // обработка HTTP-запросов get 26 public void doGet( HttpServletRequest request, 27 HttpServletResponse response ) 28 throws ServletException, IOException 29 { 30 Document document = getDocumentBuilder(),newDocument(); 31 HttpSession session = request.getSession(J; 32 33 // получение из' данных сеанса объекта StioppingCart для покупателя 34 ShoppingCart shoppingCart = 35 ( ShoppingCart ) session.gfttAttribute( "cart" ); 36 37 // формирование XML-документа, описывакнцего содержимое магазинной тележки 38 if ( shoppingCart T= null ) { 39 40 // создание элемента cart в XML-документе 41 Element root = { Element ) document.appendChild( 42 document.createElement( "cart" } }; 43 44 // получение общей стоимости товаров в магазинной тележке 45 double total = shoppingCart.getTotal(); 46 47 // создание формата NumberFormat для местных денежных единиц 4 В WuiriberFormat priceFormatter- = 49 NumberFormat.getCurrencylnstance0; 50 51 // Форматирование стоимости товаров в тележке и 52 // добавление этого числа В качестве атрибута элемента cart 53 root.setAttribute{ "total", 54 priceFormatter.format( total ) ); 55 56 // получение содержимого магазинной тележки 57 Iterator orderProducts = 58 shoppingCart.getContents().iterator(); 59 60 // добавление элемента для из каждого товаров в 61 // тележке в XML-документ 62 while ( orderProdtscts . hasNext О ) { 63 OrderProductModel orderProductModel = £4 ( OrderProduetModel ) order-Products . next () ; 65 66 root.appendChild( 67 orderproductModel.getXMM document ) ); 68 } 69
488 Глава 10 70 J // конец блока if 71 72 else { 73 String error = "Your ShoppingCart is empty."; 74 75 // добавление в XML-документ сообщения об ошибке 76 document.appendChild( buildErrorMesaage( 77 document, error ) ) ; 78 } 79 80 // запись содержимого клиенту 81 writeXMLf request, response, document ); 82 83 } // конец метода doGet 84 ) __ Рис. 10.7. Сервлет ViewCartServlet для просмотра содержимого магазинной тележки Удаленная ссылка на магазинную тележку покупателя (объект ShoppingCart) хранится в объекте HttpSession. В строках 34-35 эта удаленная ссылка извлекается. После проверки, что удаленная ссылка на объект ShoppingCart не есть null, в строках 41-42 созданием элемента cart начинается построение XML-документа, который описывает магазинную тележку покупателя. В строке 45 вычисляется общая стоимость всех товаров в магазинной тележке путем вызова метода getTotal класса ShoppingCart. В строках 48-49 осуществляется форматирование данных об общей стоимости с использованием формата NumbcrFormat, после чего отформатированное значение total добавляется в XML-документ. В строках 62-68 осуществляется обработка коллекции объектов Order Pro duct- Model в магазинной тележке. Каждый объект OrderProductModel соответствует определенному виду товара, имеющемуся в магазинной тележке, и содержит количество этого товара. В строке 67 вызывается метод getXML класса OrderProductModel Для получения элемента XML, который описывает данный объект OrderProductModel и содержит, например, такие сведения, как ISBN-код книги и ее количество в тележке. В строках 66-67 это описание добавляется в XML-документ, сформированный сервлетом Если удаленная ссылка на объект ShoppingCart, хранящаяся в объекте HttpSession, есть nnll, в строках 72-78 генерируется сообщение об ошибке. В строке 81 осуществляется вызов метода writeXML для преобразования XML-содержимого с помощью таблицы стилей XSL, после чего содержимое отправляется клиенту. XSLT-грансформация (таблица стилей XSL), представленная на рис. 10.8, формирует XIITML-код для документа, сгенерированного сервлетом ViewCartServlet. В строках 6-8 задаются выходные параметры для трансформации. В строке 10 осу- щестзляется включение шаблонов из документа error.xsl для трансформации сообщений об ошибках. В строках 23—27 из внешнего XML-документа загружается заголовок, содержащий средства навигации. Обратите внимание, что для обращения к документам error.xsl и navigation.xml используются относительные URI. Напомним, что класс XMLServlet создает собственный вычислитель URIResolver, который дает возможность XSL-преобразователю вычислять эти относительные TJRI. Если не предоставить собственный вычислитель URIResolver, в каждой XSLT-трансформации пришлось бы указывать полный TJRI для этих документов, что ограничило бы переносимость приложения. Заголовок со средствами навигации содержит ссылки для просмотра каталога товаров, создания учетной записи (счета), просмотра содержимого магазинной тележки и т.д. В строках 42-43 применяется шаблон, который осуществляет заполнение таблицы сведениями о това-
Практический пример корпоративного приложения 489 ре, такими как название книги, автор, ISBN-код, цена и количество. Форма (элемент form) в строках 51-54 дает возможность покупателю подсчитывать стоимость сделанных в Internet-магазине покупок и размещать свой заказ. Элемент xsi:temp- late в строках 61-91 извлекает информацию по каждому из товаров из XML-документа, сформированного сервлетом ViewCartServlet, и создает строку таблицы. Элемент form в строках 75-80 дает возможность пользователю удалять товар из магазинной тележки. Таблица стилей XSL, представленная на рис. 10.9, формирует cHTML-код для документа, созданного сервлетом ViewCartServlet. В строках 28-29 осуществляется вывод данных об общей стоимости товаров в тележке. В строках 38-41 для элементов orderProduct применяется шаблон xsl:template, чтобы сформировать неупорядоченный список сведений о товаре. Форма form в строках 44-47 дает возможность покупателю подсчитывать стоимость сделанных в Internet-магазине покупок и размещать свой заказ. Форма form в строках 64—69 позволяет покупателю изменять количество для определенного товара в магазинной тележке. Форма form в строках 72-76 дает возможность покупателю удалять товар из магазинной тележки. 1 <?х»1 version = "1.0"?> 2 3 <xsl:stylesheet version = "1.0" 4 xmlns:xsl = "http://www.w3.org/1999/xSL/Transform"> 5 € <xal:output method = "xml" omit-xml-declaration = "no" 7 indent = "yes" doctype-system = "DTD/xhtmll-strict.dtd" 8 doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/> 9 10 <xsl:include href = "/XSLT/XHTML/error,xsl"/> 11 12 <xsl:template match = "cart"> 13 <htral xmlns = "http://www.w3.org/1999/xhtml" 14 xml:lang = "en" lang = "en"> 15 16 <heao> 17 <title>Your Online Shopping Cart</title> 18 <linx rel = "Stylesheet" href = "styles/default.css"/> 19 </head> 20 21 <body> 22 23 <xsl:for-each select = 24 "document( '/XSLT/XHTML/navigation.xml' )"> 25 26 <xsl:copy-of select = "."/> 2 7 </xs1:for-each> 28 29 <div class = "header">Your Shopping Cart:</div> 30 31 <table class = "cart"> 32 <tr> 33 <th>Title</th> 34 <th>Author(s)</th> 35 <th>ISBN</th> 36 <th>Price</th> 37 <th>Quantity</th> 38 </tr>
490 Глав 39 40 <xsl:apply-templates 41 select = "orderProduct"/> 42 43 </table> 44 45 <p> 46 Your total: 47 <xsl rvalue-:of select = "8total"/> 48 </p> 49 50 <p> 51 <form action = "Checkout" method = "post"?- 52 <input name = "Submit" type = "submit" 53 value = "Check Out"/> 54 </form> 55 </p> 56 57 </body> 58 </ntml> 59 </xsl:template> 60 61 <xsl:template match = "orderProduct"> 62 <tr> 63 <td> 64 <a href = "GetProduct?ISBN={product/ISBN}"> 65 <xsl: value-of select = "product/title"/x/a> 66 </td> 67 6B <tdxxsl: value-of select = "product/author"/X/td> 69 70 <td><xsl:value-©f select = "product/ISEN"/x/td> 71 72 <tdXxsl: value-of select = "product/price"/x/td> 73 74 <td> 75 <form action = "UpdateCart" method = "post"> 76 <input type = "text" size = "2" 77 name = " {product/ISBN}'' 78 value = "{quantity}"/> 79 <input type = "submit" value = "Update"/> 80 </form> 81 </td> 82 83 <td align = "center"> 84 <form action = "RemoveFromCart" method = "poSt"> 85 <input name = "ISBN" type = "hidden" 86 value = "{product/ISBU}"/> 87 <input type = "submit" value = "Rei»ove"/> 88 </form> 89 </td> 90 </tr> 91 </xsl:template> 92 </xsl:stylesheet> Рис. 10.8. Таблица стилей XSL (XHTML/viewCart.xsl), применяемая сервлетом ViewCartServlet для XHTML-браузеров (часть I)
Практический пример корпоративного приложения 491 Рис. 10.8. Таблица стилей XSL (XHTML/viewCart.xsl), применяемая сервлетом ViewCartServlet для XHTML-брауэеров (часть 2) I <?xml version = "U.0"?> 2 3 <xsl:stylesheet version = "1.0" 4 xmlns:xsl = "http://wwvr.w3.org/1999/XSL/Transform"> 5 6 <xsl:output method = "html" 7 omit-xml-declaration « "yes" 8 indent = "yes" 9 doctype-system = 10 "http://www. w3.org/KarJcUp/html-spec/html-spec_too.html" 11 doctype-public = "-//«3C//DTD HTML 2.0//Eft"/> 12 13 <xsl:include href = "/XSLT/eHTKL/error,xsl"/> 14 15 <xsl:template match = "cart"> 1G <html> 17 18 <head> 19 <title>Your Online Shopping Cart</title> 20 </head> 21 22 <body> 23 <div class = "haader"> 24 Your Shopping Cart: 25 </div> 26 21 <p> 28 Your total:
492 Глава 10 29 <xsl:value-of select = "9total"/> 30 </p> 31 32 <p>Title</p> 33 <p>buthor(s)</p> 34 <p>ISBM</p> 35 <p>Price</p> 36 <p>Quantity</p> 37 38 <ul> 39 <xsl:apply-templates 40 select = "orderProduct"/> 41 </ul> 42 43 <p> 44 <form method = "post" action = "Checkout"> 45 <input type = "submit" name = "Checkout" 46 value = "Checkout"/> 47 </form> 48 </p> 49 50 </body> 51 52 </html> 53 </xsl:template> 54 55 <xsl:template match = "orderProduct"> 56 <li> 57 <a href = "GetProduct?ISBN={pi:oduct/ISBN} "> 58 <xsl:value-of select = "product/title"/x/a> 59 <br/> 60 <pXxsl:value-of select = "product/author"/></p> Gl <pXxsl:value-of select = "product/ISBN"/X/p> 62 <pXxsl:value-of select = "product/price"/x/p> 63 <p> 64 <form method = "post" action = "UpdateCart"> 65 <input type = "text" size = "2" 66 name = "{product/ISBN}" 67 value = "{quantity}"/> 68 <input type = "submit" value = "Update"/> 69 </fonn> 70 </p> 71 <p> 72 <form method = "post" action = "RemoveFromCart"> 73 <input type = "hidden" name = "ISBN" 74 value = "{product/ISBN}"/> 75 <input type = "submit" value = "Remove"/> 76 </form> 77 </p> 78 <br/> 79 </Li> 80 </xsl:template> 81 </xsl:atylesheet> Рис. 10.9. Таблица стилей XSL (cHTML/viewCartxsl), применяемая сервлетом ViewCartServlet для браузеров i-modp (Изображение публикуется с разрешения компании Р1ко, Inc.) (часть 1)
Практический пример корпоративного приложения 493 Piko Internet MJtrobrwm**^ я^Ш^» "* *j PIXO Internet Mfcrobrowser 2.1 Рис. 10.9. Таблица сталей XSL (cHTML/viewCart.xs!), применяемая сервлетом ViewCartServlet для браузеров i-moce. (Изображение публикуется с разрешения компании Pixo, Inc.) (часть 2) XSLT-трансформация (таблица стилей XSL), представленная на рис. 10.10, формирует WML-код для сервлета VicwCartServlct. Элемент do в строках 21-23 дает пользователю возможность подсчитывать стоимость сделанных в Internet-магазине покупок и размещать свой заказ. В строках 25-49 осуществляется разметка списка товаров, содержащихся в магазинной тележке. Элемент card в строках 66-81 предоставляет интерфейс для изменения количества определенного товара в магазинной тележке. [Замечание. Чтобы загрузить WML-браузер Nokia WAP Toolkit, пожалуйста, посетите страницу www.nokia.com/corporate/wap/dowiiloads.html.] 1 2 3 4 5 б 7 8 9 10 11 12 13 14 15 16 17 <?xml version = "1.0"?> <xsl:stylesheet version = "1.0" xmlns:xsl = "http://wwh.w3.org/1999/XSL/Transform"> <xsl:output method = "xml" omit-xml-declaration = "no" doctype-sys tem = "bttp://www.wapforum.org/DTD/vrml_l.1.xml" doctype-public * "-//WAPFOROM//DTD WML l.l//EN"/> <xsl:include href = "/XSLT/WML/error.xsl"/> <xsi:template match = "cart"> <wml> <card title = "Shopping Cart"> <do type — "prev"> <prev/>
494 Глава 10 IB </do> 19 20 <do type = "accept" label = "Check Out"> 21 <go href = "ChecJcout" method = "post"/> 22 </do> 23 24 <pXem>Shopping Cart</emx/p> 25 26 <p> 27 Your total: $<xsl: value-of select = "@fcotal"/> 28 29 <table columns = "2"> 30 <tr> 31 <td>Tifcle</td> 32 <td>Price</td> 33 </tr> 34 35 <xsl:£or-each select = "orderProduct"> 36 <tr> 37 <td> 38 <a href = "«ISBlUproduct/ISBHJ'^ 39 <xsl:value-of select = "product/title"/> 40 </a> 41 </td> 42 <td> 43 $<xsl:value~o£ select = "product/price"/> 44 </td> 45 </tr> 46 </xsl:for-each> 47 48 </table> 49 </p> 50 51 </card> 52 53 <xsl:for-each select = "orderProduct" > 54 <card id = "ISBN{product/ISBN}"> 55 <do label = "OK" type = "prev"Xprev/x/do> 56 <do label = "Change Quant" type = "options"> 57 <go href = "#quant{product/ISBN)"/> 58 </do> 59 <pxxsl: value-of select = "product/title"/X/p> 60 <p>Quantity: <xsl:value-of select = "quantity"/X/p> 61 <pXxsl:value-of select = "product/author"/X/p> 62 <pXxsl:value-of select = "product/ISBN"/x/p> 63 </card> 64 65 <card id = "quant{product/lSBN}"> 66 <p>Enter new quantity 67 <input name = "quantity" emptyok = "false" 68 type = "text" format = "*n"/> 69 </p> 70 71 <do type = "accept" label = "Update Quentity"> 72 <go href =» "UpdateCart" method = "poSt"> 73 <postfield name = "{product/ISBN}"
Практический пример корпоративного приложения 495 1А 75 76 77 78 79 ВО 81 82 ВЗ 84 </до> </do> «Зо type </card> </xsl:for-each> </wml> </xsl:template> value = "$quantity"/> «3o type = "prev" label = ' "Cancel "Xprev/X/do> 85 </xsl:stylesheat> ^tf^t,- //.', "^ Рис. 10,10. Таблица стилей XSL (WML/viewCart.xsl), применяемая сервлетом ViewCartServlet для WML-браузеров (Использовано изображение Image© 2001 Nokia Mobile Phones) 10.3.3. Сервлет RemoveFromCartServlet После того как покупатель добавил товар в магазинную тележку, он может захотеть удалить этот товар из нее. Сервлет RemoveFromCartServlet (рис. 10.11) обрабатывает запросы на удаление товаров из магазинных тележек. В строке 34 из объекта HttpSession извлекается ISBN-код книги, которую покупатель хотел бы удалить из тележки. В строках 39-40 из объекта HttpSession извлекается удаленная ссылка на объект ShoppingCart. После проверки, что ссылка на ShoppingCart не есть null, в строке 44 вызывается метод rcmoveProduct объекта ShoppingCart, которому в качестве параметра передается ISBN-код книги. В строке 48 клиент переадресовывается к сервлету ViewCartServlet, который отображает результаты удаления товара из магазинной тележки. Метод removeProduct возбуждает исключение ProductNot- FoundExeeption, если товар с указанным ISBN-кодом не найден а магазинной тележке. В строках 53-61 это исключение перехватывается, и генерируется сообщение об ошибке, информирующее пользователя о возникшей проблеме.
Д96 Глава 10 1 // RemoveFroraCartServlet.Java 2 // Сервлет RemoveFromCartServlet удаляет товар иэ 3 // магазинной тележки покупателя. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java. io. * ,- В 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http.*; 12 13 // Пакеты сторонних поставщиков 14 import org.w3c.dom.*; 15 16 // Пакеты Deitel 17 import com.deitel.advjhtpl.bookstore.model.*; 18 import com.deitel.advjhtpl.bookstore.ejb.*; 19 import com.deitel.advjhtpl .bookstore.exceptions . *,- 20 21 public class RemoveFromCartServlet extends XMLServlet { 22 23 // обработка HTTP-запросов post 24 public void doPost{ HttpServletRequest request, 25 HttpServletResponse response ) 26 throws servletException, IOExeeption 27 ( 2B Document document = getDocumentBuilder().newDocument(); 29 30 // удаление товара иэ магазинной тележки 31 try { 32 33 // получение ISBN-кода удаляемой книги 34 String isbn = request.getParameter( "ISBN" >; 35 36 // получение объекта ShoppingCart покупателя иэ данных сеанса 37 HttpSessioD session = request.getSession() ; 38 39 ShoppingCart shoppingCart = 40 ( ShoppingCart ) session.getAttribute( "cart" ); 41 42 // если ShoppingCart ме есть null, удалить товар 43 if ( shoppingCart != null ) 44 shoppingCart.removeProduct( isbn ); 45 46 // переадресация покупателя к сервлету ViewCartServle 47 // для просмотра содержимого магазинной тележки 48 response.sendRedirect< "ViewCart" }; 49 50 } // конец блока try 51 52 // обработка исключения, если товар не найден в тележке 53 catch { ProductNotFoundExoepti<m productException ) { 54 productException.printStackTrace(); 55 56 // добавление в ХИОдокумек-г сообщения об ошибке
Практический пример корпоративного приложения 497 57 document.appendChild{ buildErrorMessage( 58 document, productException.getMessage0 ) )1 59 60 writeXML( request, response, document ); 61 } 62 63 ) // конец метода doGet 64 } Рис. 10.11. Сррвлрт RemoveFromCartServlet для удаления товароз из магазинной трлржки 10.3.4. Сервлет UpdateCartServlet Каждый товар в магазинной тележке имеется в определенном количестве. Сервлет UpdateCartServlet (рис. 10.12) дает возможность покупателям изменять количества товаров в своих тележках. В строках 37-38 из объекта HttpSession извлекается объект магазинной тележки пользователя ShoppingCart. В строке 44 из объекта HttpSession извлекаются имена параметров в виде списка-перечисления (Enumeration). Каждое имя параметра представляет собой ISBN-код книги в магазинной тележке покупателя. Значением каждого из параметров является желаемое количество товара в магазинной тележке. Для каждого имеющегося в тележке товара в строках 50—51 из параметра request извлекается новое количество new- Quantity этого товара. В строках 55-56 задаются количества каждого из товаров в магазинной тележке путем вызова метода setProductQuantity объекта ShoppingCart. После обновления количества всех товаров, в строке 61 осуществляется переадресация клиента к ссрслсту ViewCartServlet для отображения обновленного со держимого магазинной тележки. 1 // UpdateCartServlet.Java 2 // Сервлет UpdateCartServlet обновляет количества 3 // заданного товара в магазинной тележке покупателя. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.util.* ,- 9 10 // Пакеты расширений Java 11 import javax.servlet.*; 12 import javax.servlet.http.*; 13 14 // Пакеты сторонних поставщиков 15 import org/-w3c. dom. *; 16 17 // Пакеты Deitel 18 import com.deitel.advjhtpl.bookstore.model.*; 19 import com.deitel.advjhtpl.bookstore.ejb.*; 20 import com.deitel.advjhtpl.bookstore.exceptions.*; 21 22 publio class UpdateCartServlet extends XMLServlet { 23 24 // обработка НТТР-запросов post 25 public void doPost( HttpServletRequest request, 26 HttpServletResponse response ) 27 throws ServletException, IOException
498 Глава 10 28 { 29 Document document = getDocumentBuilder().newDocument{); 30 31 // изменение количества данного товара в магазинной тележке 32 try { 33 34 // получение объекта ShoppingCart для покупателя из данных сеанса 35 HttpSession session = request,getSession( false }; 36 37 ShoppingCart shoppingCart = { ShoppingCart ) 38 session.getAttribute( "cart" ); 39 40 // получение перечислимого списка имен параметров 41 Enumeration parameters = request.getParameterNames(); 42 43 // обновление количества книг для каждого значения параметра ISBN 44 while ( parameters. hasMoreElemctits() ) { 45 46 // получение ISBN-кеда книги, количество которой будет обновлено 47 String ISBN = ( String ) parameters.nextElement(); 48 49 // получение нового количества для книги 50 int. newuuaiitity = Integer .parselnt( 51 request.getParameter( ISBN ) ); 52 53 // установка количества для книги с заданным 54 // ISBN-кодом 55 shoppingCart.setProduetQuantity( ISBH, 56 newQuantity ); 57 } 58 59 // переадресация покупателя к сервлету ViewCartServlet 60 // для просмотра содержимого магазинной тележки 61 response.sendRedirect{ "ViewCart" ); 62 63 ) // конец блока try 64 65 // обработка исключения, если товар не найден а тележке 66 catch { ProductNotFoundException productException ) { 67 productException.printStackTrace(); 6B 69 document.appendChild( buildErrorMessage( 70 document, productException.getMessage{) ) ); 71 72 writeXML( request, response, document ); 73 } 74 75 } // коней метода doGet 76 } Рис. 10.12. Сервлет UpdateCartServlet для изменения количества товаров в магазинной тележке
Практический пример корпоративного приложения 499 Метод setProductQuautity возбуждает исключение ProdnctNotFoundExcep- tion, если товар с указанным ISBN-кодом не найден в магазинной тележке. В строках 66-73 это исключение обрабатывается, и с использованием метода buildError- Message генерируется XML-сообщение об ошибке. Метод writeXML отправляет сообщение об ошибке клиенту {строка 72). 10.3.5. Сервлет CheckoutServlet После того как покупатель закончил просмотр содержимого Internet-магазина и добавил товары в магазинную тележку, заказ покупателя окончательно обрабатывается с помощью сервлета CheckoutServlet (рис. 10.13). Чтобы иметь возможность пройти кассовый контроль, покупатель при посещении Internet-магазина должен зарегистрироваться. В строках 32—33 из объекта HttpSession извлекается идентификатор пользователя userlD. После этого в строках 39—40 из объекта сеанса session извлекается удаленная ссылка на объект магазинной тележки ShoppingCart пользователя. После проверки, что ни Shopping- Cart, ни userlD не есть null, в строке 51 вызывается метод checkout объекта ShoppingCart для окончательного размещения заказа. Метод checkout возвращает удаленную ссылку на экземпляр EJB-компонента Order, который представляет заказ покупателя. В строке 54 путем вызова метода getPrimaryKey извлекается идеитлфикагор заказа oruerlD. В строках 57—58 осуществляется переадресация клиента к сервлету ViewOrderServlet, который отображает подробные сведения о размещенном заказе. Если идентификатор userlD есть null, в строках 60-70 генерируется сообщение об ошибке, указывающее, что покупатель не зарегистрировался. В строках 75-83 перехватывается исключение Product Not Found Exception, которое метод checkout возбуждает, если магазинная тележка пуста (т.е. не содержит товаров). 1 // CheckoutServlet.Java 2 // Сервлет CheckoutServlet дает возможность покупателю пройти 3 // контроль и оформить покупку товаров, содержащихся в магаэ. тележке. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax,servlet.http.*; 12 13 // Пакеты сторонних поставщиков li import org.w3cdom.*; 15 16 // Пакеты Deitel 17 import com.deitel.advjhtpl.bookstore.model.*; 18 import com.deitel.advjhtpl.bookstore.ejb.*; 19 import com.deitel.advjhtpl.bookstore.exceptions.*; 20 21 public class CheckoutServlet extends XMLServlet { 22 23 // обработка HTTP-запросов post 24 public void doPost( HttpServletRequest request, 25 HttpServletResponse response ) 26 throws ServletException, lOException 27 (
500 Глава 10 28 Document document = getDocumentBuilder().newDocument(); 29 HttpSeesion session = request,getSessian(J ; 30 31 // получение идентификатора покупателя userlD из данных сеанса 32 String userlD = ( String ) 33 session.getAttrihute( "userlD" ); 34 35 // получение объекта ShoppingCart и подсчет стоимости покупок 36 try { 37 38 // получение объекта ShoppingCart из данных сеанса 39 ShoppingCart shoppingCart = { ShoppingCart ) 40 session.getAttribute( "cart" ); 41 42 // проверка, имеет ли покупатель магазинную тележку 43 if ( shoppingCart = null ) 44 throw new ProductNotFoundException{ "Your " + 45 "ShoppingCart is empty." J; 46 47 // проверка, что идентификатор user/ID не ест*, null и не пуст 48 if ( !( userlD = null I I userlD.equals( "" ) ) ) { 49 50 // вызов метода checkout для размещения заказа 51 Order order = shoppingCart.checkout( userlD ); 52 53 11 получение идентификатора заказа, orderID для покупателя 54 Integer orderlD = ( Integer ) order.getPrimaryKey0; 55 56 // переход к сервлеву ViewOrder для отображения данных заказа 57 response.sendRedirect( "ViewOrder?orderID=" + 58 orderID J ; 59 } 60 else { 61 // если userlD есть null, указать, что 62 // покупатель не был допущен в магазин 63 String error = "you are not logged in."; 64 65 // добавление в XML-документ сообщения об ошибке 66 document.appendChiId( buildErrorMessage( document, 67 error ) ) ; 68 69 writeXML( request, response, document }; 70 1 71 72 } // конец блока try 73 74 // обработка исключения, если товар не найден в тележке 75 catch { ProductNotFoundException productException ) { 76 productExceptiorv.printStackTraceO ; 77 73 // добавление в XML-документ сообщения об ошибке 79 document.appendChiId( buildErrorMessage( SO document, prodUctException.getMessage 0 ) )»' 81 82 writeXMH request, response, document );
Практический пример корпоративного приложения S01 83 } 84 85 ) // конец метода doGet 86 > щзтшЕшшвшшт 1Й?! ] *Ыр:ЦЫН>Л №X/t£otACTrrA»viOdet"V[SeT][>Z7 3. '■j^B'fflft?] OrderlO: 27 Order Date: Aug 18, 200111:21:00 AM Title :«^iS?;5wftipiaifafm Author ISBN Quanlity Price rS^:^l.:.^: - ^ЖгДЩ?; '"Г:ЖШШШ?|12>ё PIXO /fltwnet Mlcrebrowsei 2.1 Рис. 10.13. Сервлет CheckoutServlet для размещения заказов (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) 10.4. Сервлеты, обслуживающие каталог товаров Сервлеты GretAllProductsServlet и ProductSearchServIet обслуживают каталог товаров, имеющихся в Internet-магазине. Эти сервлеты извлекают список товаров и представляют его покупателю. Сервлет GetProductServlet отображает подробные сведения о заданном товаре.
502 Глава 10 10.4.1. Сервлет GetAIIProductsServlet Сервлет GetAIIProductsServlet (рис. 10.14) предоставляет список товаров, имеющихся в нашем Iiiternet-магазиые. В строках 35-44 извлекается удаленная ссылка на EJB-компонент Product, который представляет один из товаров в Internet-магазине. Метод findAllProdiicts интерфейса ProductHome возвращает коллекцию EJB-компонентов Product, каждый из которых представляет один товар (строки 47-48). В строках 58-70 обрабатывается коллекция товаров для формирования XML-документа, который содержит информацию о каждом из товаров. В строках 64—65 осуществляется получение объекта ProductModel для каждого товара, а в строках 68-69 извлекается XML-описание каждого из товаров. 1 // GetAIIProductsServlet.Java 2 // Сервлет GetAIIProductsServlet извлекает список всех товаров, 3 // ямеящихся в магазине, и отображает этот список клиенту. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.util.*; 9 10 // Пакеты расширений Java 11 import j avax,servlet.*; 12 import javax.servlet.http.*; 13 import j avax. rroi.*; 14 import j avax.naming.*; 15 import javax.ejb.*; 16 17 // Пакеты сторонних поставщиков 18 import org.w3c.dom.*; 19 20 // Пакеты Deitel 21 import com.deitel.advjhtpl.bookstore.model.*; 22 import com.deitel.advjhtpl.bookstore.ejb.*; 23 24 public class GetAIIProductsServlet extends XMLServlet { 25 26 // обработка HTTP-запросов get 27 public void doGet( HttpServletRequest request, 28 HttpServletResponse response ) 29 throws ServletException, IOException 30 { 31 Document document = getDocumentBuilder{).newDocument(); 32 33 // формирование каталога товаров 34 try { 35 InitialContext context = new InitialContext(); 36 37 // поиск EJB-компонента Product 38 Object object = 39 context.lookup{ "java:comp/env/ejb/Product" ); 40 41 // получение интерфейса ProductHome для нахождения всех товаров 42 ProductHome productHome = ( ProductHome ) 43 PortableRemoteObject.narrow( object.
Практический пример корпоративного приложения 503 44 ProductHome.class ); 45 46 // получение итератора для списка товаров 47 Iterator products = 48 productHome.findAllProducts{).iterator(}; 49 50 // создание корня XML-документа 51 Element rootElement = 52 document.createElement( "catalog" ); 53 54 // добавление элемента catalog в XML-документ 55 document,appendChild( rootElement ); 56 57 // добавление каждого из товаров в XML-документ 58 while ( products .hasNextO ) { 59 Product product = { Product ) 60 PortableRemoteObjeet.narrow( products.next(), 61 Product.class ); 62 63 // получение объекта ProductModel для текущего товара 64 ProductModel productModel = 65 produo t.ge tProdu etModel(); 66 67 // добавление в документ XML-элемента для товара 68 rootElement.appendChiId( 69 productModel.getXML( document ) ) ,■ 70 } 71 72 } // конец блока try 73 74 // обработка исключения при поиске EJB-компонента Product 75 catch ( NamingException namingException ) { 76 namingException.printStackTraceО; 77 78 String error = "The Product EJB was not found in " + 79 "the JNDI directory."; 80 81 // добавление в XML-документ сообщения об ошибке 82 document.appendChildf buildErrorMessage( 83 document, error ) ); 84 } 85 86 // обработка исключения, если товар не может быть найден 87 catch ( FinderException finderException ) { 88 finderException.printStackTrace(); 89 90 String error = "No Products found in the etore."; 91 92 // добавление в XML-документ сообщения об ошибке 93 document.appendChild{ buildErrorMessage( 94 document, error ) ); 95 } 96 97 // гарантированная запись содержимого клиенту 98 finally { 99 writeXML( request, response, document );
504 Глава 10 100 } 101 102 } // конец метода doGefc 103 } =._ шшж DeiteI ^Assocraits Inc. . Saar^l Title t**ijo,ttJii'?wra"- Aurtior Price Del*. tiotaK' 3&31 S»*j" .< pSfS ДОЩвм'гы Celfrl Dstel a Mela i 1 Perl Шуд HM-am: Tbe Carp^cit- Tr?ri ry; caris '«] MfSlBOatEjl teita $ Оёяг| .". MM, DMA wm> Zt&L aw* d -=Ёйй PIXO IIM9W2.1 M (SBmS Рис. 10.14. Сервлет GetAllProductsServlet для просмотра каталога товаров {Изображения публикуются с разрешения компаний Pjxo, Inc и © 20СГ Nokia Mobile Phones)
Практический пример корпоративного приложения 505 Метод lookup (строка 39) возбуждает исключение NamingException, если EJB-компонент Product не может быть найден в каталоге JNDI. Соответствующий блок catch (строки 75—84) формирует XML-сообщение об ошибке, указывающее, что поиск EJB-компонента Product сервисом JNDI окончился неудачей. Метод findAHProducts (строка 48) возбуждает исключение FinderException, если никаких компонентов Product не было найдено. В строках 87—95 это исключение перехватывается, и формируется XML-сообщение об ошибке, указывающее, что в базе данных не было найдено соответствующих товаров. В блоке finally (строки 98-100) содержимое отправляется клиенту с помощью метода writeXML. 10.4.2. С ер влет GetProductServlet Сервлет GetAllProductsServlet представляет список всех товаров (объектов Product), имеющихся в магазине. Чтобы получить более подробные сведения для данного товара, покупатель вызывает сервлег GetProductServlet (рис. 10.15). Этот сервлет использует бизнес-логику приложения для извлечения подробной информации о данном товаре и отображения ее покупателю. 1 // GetProductServlet.Java 2 // Сервлет GetProductServlet извлекает сведения о товаре и 3 // отображает ик покупателю. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http.*; 12 import javax.naming.*; 13 import javax.ejb.*; 14 import j avax.rmi.*; 15 16 // Пакеты сторонних поставщиков 17 import org.w3c.dom.*; 18 19 // Пакеты Deitel 20 import com.deitel.advjhtpl.bookstore.model.*; 21 import com.deitel.advjhtpl.bookstore.ejb.*; 22 23 public class GetProductServlet extends XMLServlet { 24 25 public void doGet( HttpServletRequest request, 26 HttpServletResponse response ) 27 throws ServletException, IOException 28 { 29 Document document = getDocumentBuilder().newDocument(); 30 31 // получение ISBN-кода иэ объекта запроса 32 String isbn = request.getParameter( "ISBN" }; 33 34 // генерирование XML-документа, содержащего подробные сведения о товаре 35 try ( 36 InitialContext context = new initialContextO;
506 Глава 10 37 38 // поиск EJB-компонента Product 39 Object object = 4 0 context. lookup ( "Java: comp/env/e jb/Product" ) ; 41 42 // получение интерфейса ProductHome для поиска товара 43 ProductHome productHome - { ProductHome ) 44 PortableRemoteObject.narrow( 45 object, ProductHome,class ); 46 47 // нахождение книги с заданным ISBN-кодом 48 Product product = 49 productHome,findByPrimaryKey( isbn J; 50 51 // создание корневого элемента XML-документа 52 Kode rootNode = 53 document.createElement( "bookstore" ); 54 55 // добавление корневого элемента в XML-документ 56 document.appendChiId( rootNode ); 57 58 // получение сведений о товаре в виде объекта ProductModel 59 ProductModel productModel = 60 product.getProductKodel(); 61 62 // построение XML-документа, содержащего сведения о говере 63 rootNode.appendChiId( 64 productModel.getXML( document ) ); 65 66 } // конец блока try 67 68 // обработка исключения при поиске ЕЛВ-компонента Product 69 catch ( NamingException namingException ) { 70 namingException.printStackTrace(); 71 72 String error = "The Product EJB was not found in " + 73 "the JNDI directory,"; 74 75 document.appendChild( buildErrorMessage( 76 document, error ) ); 77 } 78 79 // обработка исключения, если EJB-компонент Product не найден 80 catch ( FinderException finderException ) { 81 finderException.printstackTrace(); 82 83 String error = "The Product with ISBN " + isbn + 84 " was not found in our store."; 85 86 document.appendChiId( buildErrorMessage( 87 document, error ) ) ; 88 } 89 90 // гарантированная запись содержимого клиенту 91 finally { 92 writeXML( request, response, document ) ,-
Практический пример корпоративного приложения 507 93 } 94 95 } // конец метода doGet 96 } ЬШШШШаЯ&ЛШШВЯШЕ' I^KkEdt: :*п f(Mrt«' Toots Hefc "31 DeiteL &AsvDti*its lute. ■ XML How to Program bvDeitelHDeitolHNietM-fcnG St^dhu ffi$H Зжга^^МЯи?*™ igtf№£ cRI Paajjlg £*™t$GSSU 1S8W: 0X3294173 рдотДОР RVittBKll ffiLKJHtrgi*_ PIXO Internet Mlcn>browser2,1 mwser2,l Я I i«t 01 ЫМч 173 ' JOSS I TftdiruC- . flack 4«$ '•«*, ■l / f 5+7-fJ i ■ fsfifssi Рис. 10.15. Сервлет GetProductServlet для просмотра информации о товаре (Изображения публикуются с разрешения компаний Pixo, inc. и © 2001 Nokia Mobile Phones)
508 Глава 10 EJB-компонент Product предоставляет подробную информацию по заданному товару в виде объекта Product Mod el. В строках 39-45 извлекается ссылка на интерфейс ProductHome. В строках 48-49 осуществляется вызов метода findByPri- maryKey интерфейса ProductHome для получения EJB-компонента Product а-заданным ISBN-кодом (ISBN). В строках 59-64 осуществляется получение сведений о товаре в виде объекта ProductModel, и используется метод getXML для генерирования XML-содержимого для клиента. В строках 69-77 перехватывается исключение NamingException, если поиск (посредством метода lookup) интерфейса ProductHome окончился неудачей. В строках 80-88 перехватывается исключение FinderException, которое генерируется при обращении к методу findByPrimaryKey в строке 49, если книга с заданным ISBN-кодом не найдена в базе данных. В каждом блоке catch создается XML-сообщение об ошибке, информирующее пользователя о возникновении проблемы. Блок finally в строках 91-93 оОесиечивает отправку клиенту содержимого независимо от любых возбуждаемых исключений. 10.4.3. Сервлет ProductSearchServlet Сервлет ProductSearchServlet (рис. 10.16) осуществляет поиск в базе данных книг, названия которых содержат определенное ключевое слово. Метод findBy- Title собственного интерфейса EJB-компонента Product принимает строковый аргумент и ищет книгу, в названии которой присутствует указанное ключевое слово. Поскольку при таком поиске может быть найдено множество книг, отвечающих условию, метод fmdByTitle возвращает коллекцию (Collection) удаленных ссылок на EJB-компоненты Product. В строках 34 -35 из объекта запроса извлекается параметр search String, а в начале и в конце искомой строки searchString помещается универсальный символ замещения %. Этот символ используется базой данных для нахождения всех названий книг, которые содержат заданную строку searchString. В строках 50-51 создается объект итератора Iterator для обработки списка книг, возвращенного в результате поиска. В строках 58-69 в XML-документ добавляются XML-код для каждой найденной книги. 1// ProductSearchServlet.Java 2 // Сервлет ProductSearchServlet дает возможность покупатели 3 // осуществлять поиск определенного товара в Internet-магазине. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.util,*; 9 10 // Пакет» расширений Java 11 import javax.aervlet.*; 12 import javax.servlet.http.*; 13 import javax.naming.*; 14 import javax.ejb.*; 15 import javax.rmi.PartableRemoteObject; 16 17 // Пакеты сторонанх поставщиков 18 import org.w3c.dom.*; 19 20 // Пакеты Deitel 21 import com.deitel.advjhtpl.bookstore.model.*;
Практический пример корпоративного приложения 509 22 import com.deitel.advjhtpl.bookstore.ejb.*; 23 24 public class ProductSearchServlet extends XMLServlet { 25 26 // обработка HTTP-запросов get 27 public void doGetf HttpServletRequest request, 28 HttpServletRespor.se response } 29 throws ServletException, IOException 30 { 31 Document document = getDocumentBuilder().newDocument(); 32 33 // получение строки поиска searchstring из объекта запроса 34 String searchstring = "%" + 35 request. getParameter( "searchstring" ) + "%",- 36 37 // нахождение товара с использованием EJB-компонента Product 38 try { 39 InitialContext context = new InitialContext{); 40 41 // поиск EJB-коипонента Product 42 Object object = 43 context.lookup( "java:comp/env/ejb/Produet" ); 44 45 ProductHome productHome = ( ProductHome ) 46 PortableRemoteObject.narrow( 47 object, ProductHome.class ); 48 49 // нахождение товаров, соответствующих строке поиска searchstring 50 Iterator products = productHome.findByTitle( 51 searchstring ).iterator(); 52 53 // создание элемента catalog документа 54 Node rootNode = document.appendChiId( 55 document.createElement( "catalog" ) ); 56 57 // формирование списка товаров, отвечающих условию поиска 58 while ( products.hasNext() ) { 59 Product product = ( Product ) 60 PortableRemoteObject.narrow( products.next(), 61 Product.class ); 62 63 ProductModel productHodel = product.getProductModel(); 64 65 // добавление в документ XML-элемента для 66 // текущего товара 67 rootNode.appendChild( 68 productModel.getXML{ document ) ); 69 } 70 71 } // конец блока try 72 73 // обработка исключения при поиске EJB-компонента Product 74 catch ( NamingException namingException ) { 75 namingException,printStackTraoe(); 76
510 Глава 10 77 78 79 80 31 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 } String error = "The Product EJB was not found in " + "the ЛГО1 directory."; document.appendChild( buildErrorMessage( document, error ) ); } // обработка исключения, если EJB-компонент Product не найден catch ( FinderException finderException ) { finderException.printStackTrace(); String error = "Mo Products match your search,"; document.appendChild( buildErrorMessage( document, error ) ); } // гарантированная запись содержимого клиенту finally t writeXML( request, response, document ); ] } // конец метода doGet уЬ store - Microsoft Internet Explore | J^^fyk-: -Vfe» Fivontei „ТасЬд^Нф- l^-^fit^:<^'^a:an0''K'^Mt^^^t^Mch7sMchsl'ir^3»vJ ^ML^inJJil Г**#^&.ШММ1&р M*°*M&¥&\ 8~д я«a*. rs- DeiteI 4Associates Inc. Product cjtjlog sLrejte Ac count Login Products Title '" * *. ', '3a^Hofr:ta'lS-Qqrarri Доуд 21, Tha СпгтиЫе 1эуд гтгаШпл Carse ЩШ'&>(гр№г^з Trying Cwrss 'Лжз ■■■■■"■"■■ ■ uj Author DeiteI, DprtsJ & Deital a oeltel DeStelSOefel ^^и^иЬйАи^мЛ^иЦкЦ^ь Search | Ж "*-m$mmsffl&zm 4 Рис. 10.16, Сервлет ProductSearchServiet для поиска в каталоге товаров {Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) (часть 1)
Практический пример корпоративного приложения 511 Рис. 10.16. Сервлет ProductSearchServlet для поиска в каталоге товаров (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) (часть 2) 10.5. Сервлеты для обслуживания покупателей Internet-магазины обычно предоставляют покупателям возможность регистрироваться и указывать сведения о себе (например, имя, адрес e-mail и адрес для доставки, товара), которые будут храниться и использоваться дли различных целей. Прежде всего, предоставленная пользователем при регистрации информация нужна магазину для составления счета и доставки товаров. Регистрация имеет то преимущество, что пользователю нужно ввести необходимую информации только один раз. Впоследствии пользователь может повторно посетить магазин и зарегистрироваться с указанием имени пользователя и пароля, чтобы извлечь ранее введенную информацию о реквизитах счета и доставки товара. Регистрация покупателей также дает возможность Internet-магазинам предоставлять покупателям содержимое в соответствии с их личными предпочтениями, а также отображать информацию о предыдущих покупках. Некоторые Internet-магазины также могут использовать введенную при регистрации информацию для направленной рекламы в зависимости от личных пристрастий потребителей или же в зависимости от возраста потребителей. Регистрацией покупателей в приложении Deitel Bookstore управляет сервлет RegistrationServlet (раздел 10.5.1). После того как покупатель зарегистрировался, сервлет LoginServlet разрешает покупателю вход па сайт, для чего следует указать имя пользователя и пароль. Сервлет ViewOrderHistoryServlet дает возможность покупателю просмотреть информацию о своих предыдущих заказах. Сервлет GetLostPasswordServlet предоставляет покупателям подсказку, чтобы они могли вспомнить забытые пароли.
512 Глава 10 10.5.1. Сервлет RegisterServlet Сервлет RegisterServlet (рис. 10.17) обрабатывает регистрационные формы, отправленные новыми покупателями. Сервлет создает экземпляр класса Customer- Model (строка 34) и использует значения параметров, указанные клиентом, чтобы ввести в модель сведения о покупателях (строки 38-125). После того как объект CustomerModel заполнен данными, в строке 133 осуществляется поиск EJB-компонента Customer, который представляет информацию о покупателе в базе данных. В строках 141-142 осуществляется создание в базе данных новой регистрационной записи для покупателя путем вызова метода create EJB-компонента Customer с передачей ему в качестве аргумента вновь созданного объекта CustomerModel. После регистрации покупателя, в строках 147-157 покупатель переадресовывается к сервлету LoginServlet для входа в магазин. 1 // RegisterServlet.java 2 // Сервлет RegisterServlet обрабатывает регистрационную 3 // форму, чтобы зарегистрировать нового покупателя. 4 package com.deitel.adv jhtpl.bookstore.servlets; 5 6 // Набор базовых пзхето* Java 7 import java.io.*; 8 import java.util.*; 9 10 // Пакеты расширений Java 11 import javax.eervlet.*; 12 import javax.servlet.http.*; 13 import j avax.ejb.*; 14 impor t j avax.naming.*; 15 import javax.rmi.*; 16 17 // Пакеты сторонних поставщиков 18 import org.w3c.dom.*; 19 20 // Пакеты Deitel 21 import com.deitel.advjhtpl.bookstore.model.*; 22 import com.deitel.advjhtpl.bookstore.ejb.*; 23 24 public class RegisterServlet extends XHLServlet { 25 2 6 // обработка НТТР-эапросов post 27 public void doPost{ HttpServletRequest request, 28 HttpServletResponse response ) 29 throws ServletException, IOException 30 t 31 Document document = getDocumentBuilder{).newDocument(); 32 33 // создание объекта CustomerModel для хранения регистрационных данных 34 CustomerModel customerModel = new CustomerModel{); 35 36 // задание свойств объекта CustomerModel с 37 // использованием значений, переданных через объект запроса 38 customerModel.setUserlD( request.getParameter( 39 "userlD" ) ); 40 41 customerModel.setPassword( request.getParameter(
Практический пример корпоративного приложения S13 42 "password" ) ); 43 44 customerModel.setPasswordHint( request.getParameter( 45 "passwoz-dHint" ) ) ; 46 47 customerModel.setFixstName( request.getParameter( 4 В "firstName" ) ); 49 50 customerModel.setLaetName( request.getParameter( 51 "laBtName" > ); 52 53 // задание информации о кредитной карте 54 customerModel.setCreditCardName( request.getParameter( 55 "creditCardName" ) ); 56 57 customerModel.setCreditCardNumber( request.getParameter{ 58 "creditCardNwnber" ) ); 59 60 customerModel.setCreditCardExpirationDate( 61 request.getParameter( "creditCardExpirationDate" ) ); 62 63 // создание об'ьехта AddressModel для адреса доставки счета 64 AddressModel billingAddress = new AddressModel(); 65 66 billingAddress.setFirstName( request.getParameter( 67 "billingAddressFirstNaate" } }; 68 69 billingAddress.setLastHamet request.getParameter( 70 "billingAddressLastName" ) ); 71 72 billingAddress.SetStreetAddressLinel( 73 request.getParameter( "billingAddressStreetl" ) ); 74 75 billingAddress.setStreetAddressLine2( 76 request.getParameter( "billingAddressStreet2" ) ); 77 78 billingAddress.setCityf request.getParameter( 79 "billingAddressCity" ) ); 80 81 billingAddress.setState( request.getParameter( 82 "billingAddressState" ) ); 83 84 billingAddress.setZipCode( request.getParameter( 85 "billingAddressZipCode" > ); 86 B7 billingAddress.setCountry( request.getParameter( 88 "billingAddressCountry" ) ); 89 90 billingAddress.setPhoneNumber( request,getParameter( 91 "billingAddressPhoneNumber" ) ); 92 93 customerModel.setBillingAddrass( billingAddress ); 94 95 // создание объекта AddressModel для адреса доставки товара 96 AddressModel shippingAddress = new AddressModel(); 97 98 shippingAddress.setFirstHame( request.getParameter(
514 Глава 10 99 "shippingAddressFirstHame" ) ); 100 101 shipping/Address . setLastName ( request. getParameter ( 102 "shippingAddressLastNaroe" ) ); 103 104 shippingAddress.setStreetAddressLinel( 105 request.getParameter( "shippingAddressStreetl" ) ); 106 107 shippingAddress.setStreetAddressLine2( 108 request.getParameter{ "shippingAddressStreet2M ) ); 109 110 shippingAddress.setCity{ request.getParameter( 111 "shippingAddressCity" ) 1 ; 112 113 shippingAddress,setState( request.getParameter( 114 "shippingAddressState" ) ) ; 115 116 shippingAddress,setZipCode( request.getParameter( 117 "shippingAddiessZipCode" ) ) ; 118 119 ShippingAddress.setCountry( request.getParameter( 120 "shippingAddressCountry" ) ) ; 121 122 shippingAddress.setPhoneNumber( request.getParameter( 123 "shippingAddressPhoneNumber" ) ) ; 124 125 customerModel.setShippin.gAddre.ss( shippingAddress ); 126 127 // поиск EJB-компонента Customer и создание нового покупателя 128 try { 129 InitialContext context = new InitialContext(); 130 131 // поиск ЕОВ-хомпонента Customer 132 Object object = 133 context.lookup( "java:comp/env/ejb/Customer" ) ,- 134 135 CustoaterHome customerHome = ( CustomerHome ) 136 POrtableRemoteObject.narrow( object, 137 CustomerHome.class ); 138 139 // создание нового покупателя на основе о&ьёхта Custamerltodel, 140 // содержащего регистрационную информаяию о покупателе 141 Customer customer = 142 customarHome.create( customerHodel ); 143 144 customerModel = customer.getCustomerModel{); 145 // получение объекта RequestDispatcher для сервлета входа 146 RequestDispatcher dispatcher = 147 getServletContextO.getRequestDispateher( "/Login" ); 148 149 // задание идентификатора userlD и народа для сервлета входа 150 request.setAttribute( "userID", 151 customerModel.getUserID(} ); 152 request.setAttribute( "password", 153 custoroerModel.getPassniordО ); 154 155 // переадресания пользователя к сералету LoginServlet
Практический пример корпоративного приложения 515 156 dispatcher.forward( request, response ); 157 158 } УУ конец блока try 159 160 // обработка исключения при поиске EJB-компонента Customer 161 catch С NamingException namingException ) { 162 namingException.printstackTrace () ,- 163 164 165 String error = "The Customer EJB was not " + 166 "found in the JNDI directory."; 167 168 document.appendchildf buildErrorMessage( 169 document, error ) ); 170 171 writeXML( request, response, document ) ; 172 } 173 174 // обработка исключения при создании нового покупателя 175 catch ( CreateException CreateException ) { 176 CreateException.printstackTrace() ; 177 178 String error = "The Customer could not be created"; 179 ISO document.appendChiId( buildErrorMessage( 181 document, error ) ) ; 182 183 writeXML( request, response, document ); 184 > 185 186 } // конец метода doFost 187 } __^ Рис. 10.17. Сервлет ReglsterServlet для регистрации новых покупателей В строках 162-172 перехватывается исключение NamingExeeption, которое указывает, что интерфейс CustomerHome не может быть найден в каталоге JNDI. Поскольку сервлет RegisterServlet создает новый объект Customer, в строках 175-184 перехватывается исключение CreateException в случае, если EJB-компо- нент Customer не может быть создан. Каждый блок catch формирует XML-сообщение об ошибке с помощью метода buildErrorMessage и вызывает метод writeXML для отображения сообщения об ошибке пользователю. 10.5.2. Сервлет LoginServlet Чтобы войти в Internet-магазин, зарегистрированный покупатель должен указать правильные идентификатор и пароль. Яти предо ста пленные покупателем данные клиент отправляет сервлету LoginServlet. Сервлет LoginServlet (рис. 10.18) проверяет идентификатор пользователя userlD и пароль password, сравнивая их со значениями userlD и password, хранящимися в базе данных. Сервлет LoginServlet использует EJB-компонент Customer для проверки идентификатора и пароля, введенных покупателем. В строках 39-44 извлекается ссылка на интерфейс CustomerHome. В строках 47-48 вызывается метод fmdByLogin интерфейса CustomerHome, который возвращает удаленную ссылку на покупатели (EJB-компонект Customer) с идентификатором userlD и паролем password, пре-
516 Глава 10 доставленными пользователем. После того как EJB-компонент Customer найден, в строках 59-69 формируется XML-документ, который сообщает, что покупатель допущен в магазин. 1// LoginServlet.Java 2 // Сервлет LoginServlet предоставляет покупателю возможность входа на сайт. 3 package com.deitel.advjhtpl.bookstore.servlets; 4 5 // Набор базовых пакетов Java 6 import 3ave.io.*; 7 8 // Пакеты расширений Jave 9 import javax.servlet.*; 10 import javax.servlet.http.*; 11 import j avax.naming.*; 12 import javax.ejb.*; 13 import j avax.rmi.*; 14 15 // Пакеты сторонних поставщиков 16 import org.w3c.dom.*; 17 18 // Пакеты Deitel 19 import com.deitel.advjhtpl.bookstore.model.*; 20 import com.deitel.advjhtpl.bookstore.ejb.*; 21 22 public Class LoginServlet extends XMLServlet { 23 24 // обработка HTTP-запросов post 25 public void doPost( HttpServletRequest request, 26 HttpServletResponse response ) 27 throws ServletExeeption, IOException 28 { 29 Document, document = getDocumentBuilder().newDocunent{}; 30 31 String userlD = request.gatParameter( "userlD" ); 32 String password = request.getParameter( "password" 1; 33 34 // использование EJB-компонента Customer для аутентификации пользователя 35 try { 36 InitialContext context = new InitialContext(); 37 38 // поиск EJB-компонента Customer 39 Object object = 40 context.lookup( "java:comp/env/ejb/Customer" ); 41 42 CustomerHome customerRome = ( CvistomerHome > 43 PortableRemoteObjact.narrow( object, 44 CustomerHome,class ); 45 46 // нахождение покупателя с заданным идентификатором и пароле» 47 Customer customer = 48 customerHome.findByLogin( userlD, password ); 49 50 // получение объекта CustomerModel для локупателя
Практический пример корпоративного приложения 517 51 CustomerModel customerModel = 52 customer .getCustomerModel () ,- 53 54 // задание идентификатора userID для сеанса дамного покупателя 55 requeat.getSession().setAttribute( "userlD", 56 customerModel.getUserlDO ); 57 58 // совдакиз XML-элемента login 59 Element login = document.createElement( "login" ); 60 document.appendchild( login ); 61 62 // добавление в XML-документ имени покупателя 63 Element firstHame = 64 document.createElement{ "firstName" ); 65 66 firstName.appendChild( document.createTextNode( 67 customerModel.getFirstNameO ) ); 68 69 login.appendChiId( firstName ) ; 70 71 ) // конец блока try 72 73 // обработка исключения при поиске EJB-коыпОнента Customer 74 catch ( NamingException namingException ) { 75 namingException.printstackTrace(); 76 77 String error = "The Customer EJB was not found in " + 78 "the JNDI directory."; 79 80 document.app*mdChiId( buildBx-rorMeseage ( 81 document, error ) ); 82 } 83 84 // обработка исключении, если ЕоВ-жОипонент Customer н* найден 85 catch { FinderEacception finderException ) ( 86 finderException. prin tStacJcIrace () ; 87 88 String error = "The userlD and password entered " + 89 "were not found."; 90 91 document.appendChiId( buildErrorMessage( 92 document, error ) ); 93 } 94 95 // гарантированная запись содержимого клиенту 96 finally { 97 writeXKM request, response, document }; 98 } 99 100 } // конец метода doPost 101 ) ^ Рис. 10.18. Сервлет LoginServlet для аутентификации зарегистрированных покупателей (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) (часть 1)
518 Глава 10 rojoft Internet Explorer M> 'iSWfil L«*iBJ.al Isbmg.m&.V: ЖШШШ& ______ .___ _ _..__ _______ :^№*tl}@ titte; //localho-i- iSOOOJbPoksta-e/Hegiaef n DeiteI &Амос1ат« Inc. ss Product Cdtaluy Create Accuui.t Log in _.hn^piiTg Cart tlrder History Welcome to the Deitel Bookstore, Julie Click hsre to browse аж producs crdLbpre Id view YctJL_Qr.tfer h^tof у [ВРШЮ .1 .^ "S3 ilil :Wf^»i»SI>iiPI_i4, j P** 1,. «?$ *.)*■}& trll**:: 7< s _ Рис. 10.18. Сервлет LoginServlet для аутентификации зарегистрированных покупателей (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) (часть 2) В строках 74-82 перехватывается исключение NamingException, если EJB-компонент Customer кв может быть найден в каталоге JNDI. Если EJB-комповент Customer, значения userlD и password для которого совпадают со значениями, введенными пользователем, не найден, в строках 85-93 перехватывается исключение FinderException. Каждый блок catch формирует XML-сообщение об ошибке, отображаемое клиенту. В строках 96-98 содержимое отправляется клиенту.
Практический пример корпоративного приложения 519 10.5.3. Сервлет ViewOrderHistoryServlet Зарегистрированные покупатели могут просматривать информацию о своих предыдущих заказах. Сервлет ViewOrderHistoryServlet (рис. 10.19) дает возможность покупателям просмотреть сведения о ранее размещенных ими заказах с указанием их дат, общей суммы заказов и сведений, были ли заказы доставлены. 1 // ViewOrderHistoryServlet.Java 2 // Сервлет ViewOrderHistoryServlet представляет покупателю 3 // список ранее размещенных ни заказов, 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.util.*; 9 10 // Пакет» расширений Java 11 import javax-servlet.*; 12 import javax.servlet.http.*; 13 import j avax.naming.*; 14 impo-rt javax. rmi. * ; 15 import javax.ejb.*; 16 17 // naicexu сторонних поставщиков IB import org.w3c.dom.*; 19 20 // Пакеты Deitel 21 import com.deitel.advjhtpl.bookstore.model.*; 22 import com,deitel,advjhtpl.bookstore.ejb.*; 23 import com.deitel.advjhtpl.bookstore.exceptions.*; 24 25 public class ViewOrderHistoryServlet extends XMLServlet { 26 27 // обработка HTTP-запросов get 28 public void doGet( HttpServletRequest request, 29 HttpServletResponse response ) 30 throws ServletException, IOException 31 { 32 Document document = getDocumentBuilder().newDoCument(); 33 34 HttpSession session = request.getSession(); 35 String userlD = ( String ) 36 session.getAttribute( "userlD" ); 37 38 // формирование архива заказов с помощью EJB-компонента Customer 39 try 1 40 InitialContext context = new InitialContext () ; 41 42 // поиск EJB-компонента Customer 43 Object object = 44 context.lookup( "Java:comp/env/ejb/Customer" ); 45 46 CustomerHome customerHome = ( CustomerHome ) 47 PortableRemoteObject.narrow( 46 object, CustomerHome.class };
520 49 50 // поиск покупателя с заданным идентификатором userID 51 Customer customer = 52 customerHome.findByOserlD( userID ); 53 54 // создание элемента orderHistory 55 Element rootNode = ( Element ) document.appendChiId( 56 document.createElement( "orderHistory" ) ); 57 58 // получение списка предыдущих заказов покупателя 59 Iterator orderHistory = €0 customer.getOrderHistory().iterator(); 61 62 // просмотр архива заказов и добавление в 63 // XML-документ элементов для каждого заказа 64 while ( orderHistory.hasNext() ) { 65 OrderModel orderModel = 66 ( OrderModel ) orderHistory next(); 67 68 rootNode.appendChiId( 69 orderModel.getXML( document ) ); 70 1 71 } // конец блока try 72 73 // обработка исключения, если у покупателя нет архива заказов 74 catch ( NoOrderHistoryException historyException ) { 75 historyException.printStackTrace(); 76 77 document.appendChiId( buildErrorMessage( document, 78 historyException.getMessage() ) ) ; 79 } 80 81 // обработка исключения при поиске EJB-хомпонента Customer 82 catch ( NamingException namingException ) { 83 namingException. printStacVTrace (> ; 84 85 String error = "The Customer EJB was not found in " + 86 "the JND1 directory."; 87 88 document.appendChiId{ buildErrorMessage( 89 document, error } ); 90 } 91 92 // обработка исключения, если покупатель не найден 93 catch ( FinderException finderException ) { 94 finderException.printStaeJcTrace() ; 95 96 String error = "The Customer with \iserID " + vtserlD + 97 " was not found."; 98 99 document.appendChiId( buildErrorMessage( 100 document, error ) ); 101 } 102 103 // гарантированная запись содержимого клиенту 104 finally {
Практический пример корпоративного приложения 521 105 writeXML( request, response, document ); 106 } 107 10B J // конец меиода doGet 109 } Ж £\&1УВ-1РК1 bttp^AKefasCSOGQ/bocfcttve/vvttOtterrtstov &At&OciATf* Inc. ~ш Order ID Order Dale Total Shipped ^ 'a HP ^'J- тагаг-.л-* !^&W&№*gjg уЬ. .ЕЛ *Щг£$ф^ + ^*;г 'PIXO Attentat MicnbrousarZl Рис. 10.19. Сервлет ViewOrderHtstoryServlet для просмотра ранее сделанных покупателем заказов (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones)
522 Глава 10 Метод getOrdevHistory EJB-компонента Customer возвращает коллекцию ранее сделанных покупателем заказов (объектов Order). В строках 51-52 извлекается EJB-компонент Customer для покупателя, который посещает Internet-магазин. В строках 59—60 извлекается объект итератора Iterator для обработки архива предыдущих заказов данного покупателя. В строках 64—70 осуществляется поиск в архиве заказов, и формируется XML-документ для отображения его клиенту. Если покупатель до этого момента не размещал каких-либо заказов в Internet-магазине, метод get Order History возбуждает исключение NoOrder- HistoryException. В строках 74-79 это исключение перехватывается и формируется сообщение об ошибке для отображения его покупателю. Если интерфейс CustomerHome не может быть найден, либо покупатель не может быть обнаружен в базе данных, выдается, соответственно, исключение NamLngException или Finder Exception. В строках 82-101 каждое из этих исключений перехватывается, и формируется сообщение об ошибке с использованием метода DuildErrorMessage. В строке 105 содержимое отправляется клиенту с помощью метода writeXML. 10.5.4. Сервлет ViewOrderServlet Сервлет ViewOrderServlet (рис. 10.20) отображает информацию о заказе. Сервлет CheckoutServlet переадресовывает клиентов к сервлету ViewOrderServlet, когда покупатель делает заказ. Сервлет ViewOrderHistoryServIet переадресовывает клиентов к сервлету ViewOrderServlet, чтобы отобразить информацию об уже сделанном заказе. 1 // ViewOrderServlet.Java 2 // Сервлет ViewOrderServlet отображает содержимое заказа 3 // покупателю. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import j ava.io.*; в 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http,*; 12 import javax.naming.*; 13 import javax.ejb.*; 14 import javax.rmi.*; 15 16 // Пакеты сторонних поставщиков 17 import org.w3c.dom.*; 18 19 // Пакеты Deitel 20 import com.deitel.advjhtpl.bookstore.model.*; 21 import com.deitel.advjhtpl.bookstore.ejb.*; 22 23 public class ViewOrderServlet extends XMLServlet { 24 25 // обработка HTTP-запросов get 26 public void doGet( HttpServletRequest request, 27 HttpServletResponse response } 28 throws ServletException, IOExeeption 29 { 30 Document document = getDocumentBuilder().newDocument(); 31 Integer orderlD = null;
Практический пример корпоративного приложения 523 32 33 // поиск ЕЛВ-компонентa Order и получение сведений о 34 // заказе с заданным идентификатором orderlD 35 try { 36 ZnitialContext context = new InitialContext(); 3T 38 // поиск EJB-компонента Order 39 Object object = 40 context.lookup( "java:comp/env/ejb/Order" ); 41 42 OrderHome orderHome = ( OrderHorcte ) 43 PortableReitioteObject. narrow { 44 object, OrderHome.class ) ; 45 46 // получение идентификатора orderlD из объекта запроса 47 orderlD = new Integer( 48 request.getParamater( "orderlD" ) ) ; 49 50 // поиск заказа с заданным идентификатором orderlD 51 Order order = orderHome.findByPrimaryKey( orderlD ); 52 53 // получение подробных сведений о заказе в гиде объекта OrderModel 54 OrderModel orderModel = order.getOrderModel(); 55 56 // добавление сведений о заказе в XML-документ 57 document.appendChild( 58 orderModel.getXML( document ) ); 59 60 } // конец блока try 61 62 // обработка исключения при лоиске ЕЛВ-конпонента Order 63 catch ( NamingException namingException ) { 64 namingException.printStackTrace(); 65 66 String error = "The Order EJB was not found in " + 67 "the JNDI directory."; 68 69 document.appendChild( buildErrorMessa^e( 70 document, error ) ) ,- 71 } 72 7 3 // обработка исключения, если EJB-компонент Order не найден 74 catch ( FinderException finderException ) { 75 finderException.printStackTrace(}; 76 77 String error = "An Order with orderlD " + orderlD + 78 " was not found."; 79 80 document.appendChild( buildErrorMessage( 81 document, error ) ); 82 } 83 84 // гарантированная запись содержимого клиенту 85 finally ( 86 writeXKM request, response, document );
524 Глава 10 В7 } ВВ 89 } // конец метода doGet 90 } ]ЛЙ^З,)Д ^B-;)facJiM;300№iMliifaT;fffa«Onl»?aji5rIL'-i7 DeiteI &A»socIates Inc. srID: 27 Order Date: Aug IS, 2001 11:21:00 AM Title ftuthar •л*чк> ISBN Quantity Price •>*■■ -iraa ^«^ "'*■:.•** l^te;.,^1**ui». "^ г ЭД9.95 Total: 1209.95 «fib."* ■ ;SE3§h| If'" .ift. ЩггЩ0ЩЖ^1Ш: a ^щ^гтт PIXO Internet Mtovbmw$er2.t :*Ш1 €mP <*rrst.?rl~ >**.*■>*, Рис. 10.20. Сервлет ViewOrderServlet для просмотра информации о заказе (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 No<ia Mobile Phones) В строках 39-44 осуществляется получение ссылки на интерфейс OrderHome. В строках 47-48 из объекта request извлекается параметр orderlD. В строке 51 вызывается метод findByPrimaryKey для получения удаленной ссылки на заказ (объ-
Практический пример корпоративного приложения 525 ект Order) с заданным идентификатором orderlD. В строках 54-58 извлекается модель OrderModel для заказа Order, и в XML-документ добавляется ее XML-пред- ставленне. 8 строках 63-71 перехватывается исключение NamingException, которое возбуждается из метода lookup, если EJB-компонент Order не может быть найден в каталоге JNDI. В строках 74-82 перехватывается исключение FindcrEx^eption, которое возбуждается методом findByPrimaryKey, если заказ (объект Order) с заданным идентификатором orderlD не найден. В строке 86 XML-документ отправляется клиенту с помощью метода writeXML. 10.5.5. Сервлет GetPasswordHintServlet Зарегистрированные покупатели порой забывают свои пароли. Сервлет GetPasswordHintServlet (рис, 10.21) предоставляет подсказки, помогающие покупателям вспомнить свои пароли. Покупатель указывают текст подсказки для пароля в процессе регистрации. 1 // GetPasswordHintServlet.Java 2 // Сервлет GetPasswordHintServlet дает возможность 3 // покупателю восстановить забытый пароль. 4 package com.deitel.advjhtpl.bookstore.servlets; 5 6 // Набор базовых пакетов Java 7 import java.io.*; В 9 // Пакеты расширений Java 10 import javax.servlet.*; 11 import javax.servlet.http.*; 12 import javax.naming.*; 13 import javax.ejb.*; 14 import javax.rmi.*; 15 16 // Пакеты сторонних поставщиков 17 import org,w3c.dom.*; IB 19 // Пакеты Deitel 20 import com.deitel.advjhtpl bookstore.model.* ; 21 import com,deitel.advjhtpl.bookstore.ejb.*; 22 23 public class GetPasswordHintServlet extends XMLServlet ( 24 25 // обработка HTTP-запросов get 26 public void doGet( HttpServletRequest request, 27 HttpServletResponse response ) 28 throws ServletException, lOException 29 { 30 Document document = getDocumentBuilder()-newDocument(); 31 String userlD = request.getParameter( "userlD" ); 32 33 // получение подсказки пароля из EJB-компонента Customer 34 try ( 35 InitialContext context = new InitialContext(); 36 37 // поиск EJB-компонента Customer 38 Object object =
526 Глава 10 39 context.lookup( "java:comp/env/ejb/Customer" ); 40 41 CustomerHome customerHome = { CustomerHome } 42 portableRemoteObject.narrow( object, 43 CustomerHome.class ); 44 45 // нахождение покупателя с заданным идентификатором userID 46 Customer customer = 47 customerHome,findByUserlD{ userlD ); 48 4 9 // создание элемента passwordHint в XML-документе 50 Element hintElement = 51 document.createElement( "passwordHint" ); 52 53 // добавление текста подсказки в XML-элемент 54 hintElement.appendChild( document.createTextNode{ 55 customer.getPasswordHint() ) ); 56 57 // добавление элемента passwordHint в XML-документ 58 document.appendChild( hintElement ); 59 60 ) // конец блока try 61 62 // обработка исключения при поиске EJB-компонента Customer 63 catch ( NamingException namingException ) { 64 namingException.printStaCkTraceО; 65 66 String error = "The Customer EJB was not found in " + 67 "the ЛЮ1 directory."; 68 69 document.appendChild( buildErrorMessage( 70 document, error ) ); 71 } 72 73 // обработка исключения, если EJB-компонеят Customer не найден 74 catch { FinderException finderException ) { 75 finderException.printStaclfcTraceО; 76 77 String error = "No customer was found with userlD " + 78 userID + "."; 79 80 document.appendchild( buildErrorMessage( Bl document, error } ); 82 } 83 B4 // гарантированная запись содержимого клиенту 85 finally i 86 writeXMH request, response, document ),- 87 ) 88 89 } // конец метода doGet 90 } ^^___ ___ Рис. 10.21. Сервлет GetPasswoidHintServlet для отображения текста подсказки пароля (Изображения публикуются с разрешения компаний Pixo, Inc. и © 2001 Nokia Mobile Phones) (часть 1)
Практический пример корпоративного приложения 527 3 Hera's У<шг Password Hint ■ Murcstft [ntemet Екр&зют ' i££ ;Nt-,f»ew/£l*e*e* . Jm* tfcfe-- -»« DeiteI &А««ос1аге* Inc. ;atalpq create Ad bhoppsirj Cdrt Urrlpt Password hint: my hint Etn*rtAddress: j fe" Л" ЗДов'Н' I ■rEJ-SjwP' ■' ^J ^ЯВ;|. .МЙГШШ» .-din Рис. 10.21. Сервлет GetPasswordHintServlet для отображения текста подсказки пароля (Изображения публикуются с разрешения компаний Pixo, Inc и © 2001 Nokia Mobile Phones) (часть 2) Подсказка хранится вместе с другой регистрационной информацией в EJB-kom- поненте Customer. В строках 38-47 осуществляется поиск интерфейса Customer- Home, и извлекается удаленная ссылка на EJB-компонент Customer. Метод get- PasswordHint (строка 55) EJB-компонента Customer возвращает подсказку, введенную пользователем при регистрации. В строке 58 подсказка добавляется в XML-документ.
528 Глава 10 В этой главе мы рассмотрели логику управления и логику внешнего представления данных для приложения Deitel Bookstore. Логика управления обеспечивает интерфейс HTTP с объектами бизнес-логики, рассматриваемыми в главах 10 и 11. Сервлеты Java представляют собой устойчивую и гибкую реализацию логики управления. Логика внешнего представления данных посредством XSLT позволяет приложению Deite\ Bookstore поддерживать множество различных типов клиентов, не внося при этом изменений в реализации логики управления. В главах 11 и 12 будет рассмотрена бизнес-логика приложения Deitel Bookstore, реализованная с помощью технологии Enterprise JavaBeans. Упражнения для самоконтроля 10.1. Какая составная часть архитектуры MVC (модель—вид—контроллер) в приложении Deifel Bookstore реализована на сервлетах? Какую составную часть архитектуры MVC реализуют XSLT-трансформации? 10.2. Напятите фрагменты кода для поиска EJB-коьшонеита ShoppingCart в каталоге JNDI и создания нового экземпляра с помощью интерфейса ShoppingCartHome. Позаботьтесь о перехвате любых исключений, возбуждаемых при поиске KJB-комттонента или создании нового экземпляра. 10.3. Каким образом сервлет ViewOrderServlet (рис. 10.20) находит заказ (EJB-компонент Order), информацию о котором покупателю разрешено видеть? 10.4. Какие общие функциональные возможности класс XMLServlet (рис. 10.1) предоставляет для сервлетоа а приложении Deitel Bookstore? Опишите предназначения основных методов класса XMLServlet. 10.5. Каким образом класс XMLServlet определяет имя таблицы сталей XSL, которую следует использовать при трансформации содержимого, сгенерированного сервлетом? Какие преимущества обеспечивает такая стратегия? 10.6. Каким образом класс XMLServlet определяет, какую таблицу стилей XSL следует использовать при трансформации содержимого, сгенерированного сервлетом для определенного типа клиента? Какие преимущества обеспечивает такая стратегия? Ответы на упражнения для самоконтроля 10.1. Сервлеты реализуют в архитектуре MVC логику управления (контроллер), поскольку они обрабатывают все пользовательские запросы и данные, введенные пользователем. XSLT-трансформации реализуют в архитектуре MVC логику представления данных (вид), нескольку они определяют внешний вид данных в приложении. 10.2. Следующий фрагмент кода ищет EJB-компонепт ShoppingCart в каталоге JNDI и создает новый экземпляр с помощью интерфейса ShoppingCartHome try ( InitialContext context = new InitialContext; Object object = context.lookup { "3*4a:caKp/env/ejb/ShoppingCaTt" ): ShoppingCartHome ShoppingCartHome = { ShoppingCartHome } PortableRemoteObject.narrow ( object, SliOppingCartHoina. class ); ShoppingCart ShoppingCart = ShoppingCartHome.create(); i catch ( NamingException namingExceptiori ){ namingException .printStackTrace f) ,■ } catch ( CreateExceptian createException ) } createException.printStackTraceО; }
Практический пример корпоративного приложения 529 10.3. Сервлет ViewOrderServlet использует метод find By Primary Key интерфейса Order- Home для нахождения EJB-компонепта Order с идентификатором orderlD, передаваемым в качестве параметра объекту HttpServletRequest. 10.4. Класс XMLServlet предоставляет общий метод mit для инициализации объектов DocumentBuilderFactory, TransformerFactory и свойств, используемых каждым из сервлетов. Класс XMLServlet предоставляет метод buildErrorMessage, который создает элемент XML, содержащий описание сообщения об ошибке. Класс XMLServlet также предоставляет метод writeXML, который использует метод transform для преобразования XML-содержимого, сгенерированного каждым из сервлетов, с использованием таблицы стилей XSL, характерной для конкретного клиента. 10.5. Класс XMLServlet имеет свойство XSLFileName, которое определяет имя XSL-файла, используемого при трансформации содержимого, генерируемого сервлетом. Метод init класса XMLServlet устанавливает для свойства FileName значение, заданное для параметра инициализации XSL_FILE сервлета. Определение имени файла из параметра инициализации дает возможность разработчику задавать имя файла при развертывании приложения. Имя файла может быть впоследствии изменено без необходимости повторной компиляции сервлета. 10.6. Класс XMLServlet использует объект ClientModel для того, чтобы определить, в каком каталоге содержится тайлица стилей XSL, используемая для трансформации XML-содержимого, сгенерированного сервлетом. Класс XMLServlet создает список объектов ClientModel из XML-файла конфигурации при первой инициализации сервлета. В каждом объекте ClientModel определен заголовок User-Agent, который уникально идентифицирует клиента, заголовок Content-Type для отправки данных клиенту и каталог, в котором следует искать таблицу стилей XSL для формирования содержимого, специфичного для конкретного клиента. Это позволяет разработчику добавлять поддержку новых типов клиентов без внесения изменений в код сервлета. Разработчик просто предоставляет набор таблиц стилей XSL (XSLT-трансформаций) для нового типа клиента и включает информацию о новом типе клиента в файл clients.xml.
11 Практический пример корпоративного приложения. Бизнес-логика: часть 1 Цели • Познакомиться с моделью данных на базе EJB, используемой в учебном приложении Deitel Bookstore. • Уяснить бизнес-логику, применяемую в учебном приложении Deite] Bookstore. • Получить представление о передаче объектов через RMI-IIOP и возникающих при этом проблемах с производительностью. • Понять, какие преимущества дает применение EJB-комионентов с персистентнастью, управляемой контейнером, для хранения информации в базе данных. • Познакомиться с применением классов первичного ключа для представления составных первичных ключей. Управляй своим делом, иначе дело станет, управлять тобой, Бенджамин Франклин Общее, дело — это ничье дело, а ничье дело — это мое дело. Клара Бартон
532 Глава 11 В этой главе будет рассмотрена реализованная на базе EJB-компонентов бизнес-логика модели магазинной тележки, используемой в приложениях электронной коммерции, а также компоненты-сущности EJB, которые предоставляют объектно-ориентированный интерфейс для хранения каталога товаров, имеющихся в Internet-магазине. Изучив эту главу, вы поймете, как использовать EJB-компо- ненты в приложениях электронной коммерции, а также получите представление о некоторых других вопросах, связанных с EJB, таких как нестандартные (создаваемые разработчиком) классы первичного ключа и отношения «многие ко многим». 11.2. Архитектура компонентов EJB EJB-компоненты реализуют в учебном приложении Deitel Bookstore бизнес-логику. Логика управления, реализованная на сервлетах, взаимодействует с бизнес-логикой, реализованной на EJB-компонентах, для обработки запросов пользователей и извлечения информации из базы данных. Например, сервлет GetPro-
Практический пример корпоративного приложения. Бизнес-логика: часть 1 533 duetServlet обрабатывает запросы, поступившие от покупателя (EJB-компонент Customer) для просмотра сведений о товаре (EJB-компонент Product). Сервлет GetProductServlet использует каталог JNDI для нахождения собственного интерфейса EJB-компонента Product. Сервлет GetProductServlet вызывает метод findByPrimaryKcy интерфейса ProductHome для извлечения удаленной ссылки на книгу (EJB-компонент Product) с запрашиваемым ISBN-кодом. Сервлет GetProductServlet должен затем воспользоваться методами удаленного интерфейса Product для извлечения информации о товаре. При каждом вызове метода удаленного интерфейса Product создается сетевой трафик, поскольку взаимодействие с EJB-компонентом осуществляется через RMI-ПОР. Если для извлечения информации о названии книги {свойство title), авторе (свойство author), цене (свойство price) и т.д. осуществляются отдельные вызовы мегомов, затраты на сетевые взаимодействия могут серьезно ограничить производительность и масштабируемость приложения. Применение компонентов-сущностей EJB в учебном приложении Deitel Bookstore позволяет уменьшить издержки, связанные с возникновением заторов в сети, за счет использования моделей для передачи данных EJB-компонентами. Модель представляет собой сериализуемый (Serializable) класс, который содержит все данные для определенного EJB-компонента. Эти классы называются моделями, поскольку каждый класс модели реализует ту составную часть архитектуры MVC (модель—вид—контроллер), которая относится к модели. Каждая EJB-сущ- ностъ предоставляет get-метод, который возвращает представление своей модели. Например, EJB-компонент Product имеет метод getProductModel, который возвращает объект ProductModel, содержащий ISBN-код (ISBN), автора (author), цену (price) и другие свойства товара (EJB-компонент Product). Многие EJB-сущ- ности также предоставляют методы создания (create), которые принимают модели в качестве параметров. Эти методы создают новые экземпляры EJB-компонента и устанавливают данные в EJB-компонентах, используя значения свойств, предоставляемые моделью. I——, Совет по повышению эффективности 11,1 "^Р^ Объединение EJB-компонентов в класс модели и возврат экземпляров этого класса модели бизнес-методами EJB-компонента может повысить эффективность работы EJB-компонента за счет сокращения сетевого трафика, сопровождающего множественные вызовы методов через RMI-IIOP. На рис. 11.1 показан образец взаимодействия между сервлетом GetProductServlet и EJB-компонентом Product. Чтобы получить информацию об указанном товаре, сервлет GetProductServlet вызывает метод getProductModel EJB-компо- нента Product. Метод getProductModel возвращает объект ProductModel, содержащий данные для указанного товара, и выполняет сериализацию объекта Рго- Контейнер сервлетов Контейнер Ш GetProductServlet getProduc tMode1 Produc tHode1 Product EJB Рис. 11.1. Взаимодействие между сервлетом GetProductServlet и EJB-компонентом Product
534 Глава 11 duct Mode] через RMI-IIOP. Сервлет GetProductServlet извлекает значения свойств объекта ProductModel для формирования выходного результата, отображаемого пользователю. 11.3. Реализация магазинной тележки Сеансовый EJB-компонент с состоянием ShoppingCart реализует бизнес-логику для обслуживания магазинной тележки каждого из покупателей. EJB-компонент ShoppingCart состоит из удаленного интерфейса, реализации EJB-компонепта и собственного интерфейса. Магазинная тележка ShoppiugCart реализуется в виде сеансового EJB-компонента с состоянием, чтобы каждый его экземпляр сохранял свое состояние в течение сеанса посещения Internet-магазина покупателем. Точно так же, как посетители супермаркетов используют магазинные тележки для отбора товаров, так и посетители нашего Internet-магазина используют EJB-компонен- ты ShoppingCart для отбора товаров при просмотре содержимого магазина. 11.3.1. Удаленный интерфейс ShoppingCart Удаленный интерфейс ShoppingCart (рис. 11.2) определяет методы бизнес-логики, доступные для EJB-компонента ShoppingCart. Каждый метод удаленного интерфейса должен объявлять, что он возбуждает исключение RemoteException. Каждый метод также должен объявлять любые специфичные для приложения исключения, которые могут быть возбуждены из реализации. Метод getContents (строка 20) возвращает коллекцию (Collection) товаров (Product), содержащихся в магазинной тележке (ShoppingCart). Метод addProduct (строки 23-24) принимает в виде строкового аргумента ISBN-код книги, добавляемой в магазинную тележку. Метод addProduct возбуждает исключение ProductNotFoundException, которое является специфичным для приложения исключением, указывающим, что книга (объект Product) с данным ISBN-кодом (ISBN) отсутствует в базе данных и. следовательно, не может быть добавлена в магазинную тележку (ShoppingCart). 1 // ShoppingCart.Java 2 // ShoppingCart - удаленный интерфейс для сеансового 3 // EJB-комлонента с состоянием ShoppingCart. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 7 import j ava.. rmi. RemoteException; 8 import Java,util.Collection; 9 10 // Пакеты расширений Java 11 import javax.ojb.EJBObject; 12 13 // Пакеты Deitel 14 import com.deitel.advjhtpl.bookstore .model. *; 15 import com.deitel.advjhtpl.bookstore.exceptions,*; 16 17 public interface ShoppingCart extends EJBObject { 18 19 // получение содержимого магазинной тележки 20 public Collection getContents() throws RemoteException; 21 22 // добавление в тележку книги с заданным ISBN-кодом 23 public void addProduct( String isbn )
Практический пример корпоративного приложения. Бизнес-логика: часть 1 535 24 throws RemoteException, ProductNotFoundException; 25 26 // удаление из одлежки книги с заданным ISBN-кодом 27 public void removeProduct( String isbn ) 28 throws RemoteExceptiort, ProductNotFoundException; 29 30 // изменение количества книг с заданным ISBN-кодом. 31 // имеющихся в тележке, на указанное число 32 public void setProductQuantity( String isbn, int quantity ) 33 throws RemoteException, ProductNotFoundException, 34 IllegalArgumentException; 35 36 // подсчет стоимости товаров в тележке (создание нового заказа) 37 public Order checkout( string userlD ) 38 throws RemoteException, ProductNotFoundException; 39 40 // получение общей стоимости товаров в магазинной тележке 41 public double getTotal() throws RemoteException; 42 ) Рис. 11.2. Удаленный интерфейс ShoppingCart для добавления, удаления и обновления товаров, контроля и подсчета общей стоимости заказа Метод removeProduct (строки 27-28) удаляет книгу (Product) с заданным ISBN-кодом (ISBN) из магазинной тележки (ShoppingCart). Если книга с данным ISBN-кодом не найдена в магазинной тележке, метод removeProduct возбуждает исключение ProductNotFoundException. Метод setProductQuantity (строки 32-34) обновляет количество книг с заданным ISBN-кодом в магазинной тележке. Например, если в магазинной тележке покупателя имелся один экземпляр книги Технологии программирования на Java 2. Книга 3, для того, чтобы приобрести пять экземпляров книги, следует вызвать метод setProductQuantity с указанием в качестве аргументов ISBN-кода книги Технологии программирования на Java 2. Книга 3 и целого числа 5. Если книга с данным ISBN-кодом в магазинной тележке отсутствует, метод setProductQuantity возбуждает специфичное для приложения исключение ProductNotFoundException, Метод checkout (строки 37-38) помещает книги (компоненты Product) в соответствии со сделанным покупателем заказом (компонент Order) в магазинную тележку (компонент ShoppingCart). Метод checkout принимает в качестве строкового аргумента идентификатор userlD покупателя, сделавшего заказ. Заказы могут размещать любые зарегистрированные покупатели. Метод getTotal (строка 41) возвращает общую стоимость товаров, имеющихся в магазинной тележке покупателя. 11.3.2. Реализация ShoppingCartEJB удаленного интерфейса ShoppingCart Реализация ShoppingCartEJB (рис. 11.3) удаленного интерфейса ShoppingCart содержит коллекцию объектов OrderProductMode! (строка 24). Объект OrderPro- ductModel (рис. 11.25) представляет элемент в магазинной тележке ShoppingCart. Каждый объект OrderProdnctModel содержит товар (компонент Product) и его количество в магазинной тележке. Метод ejbCreate (строки 27-30) инициализирует коллекцию (строка 29). Метод getContents (строки 33-36) возвращает содержимое магазинной тележки как коллекцию объектов OrderProductMode 1.
1 // shoppingc3.rtEJB.3ava 2 // Сеансовый EJB-компонент с состоянием ShoppingCart 3 // представляет магазинную тележку покупателя. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор баэовкх пакетов Java 7 import java.util.*; 8 import java.rmi.RemoteException; 9 import Java.text.DateFormat; 10 11 // Пакеты расширений Java 12 import javax.ejb.*; 13 import j avax.naming.*; 14 import javax.rmi.PortableRemoteObject; 15 16 // Пакеты Deitel 17 import com.deitel.advjhtpl.bookstore.model.*; 18 import com.deitel.advjhtpl.bookstore.exceptions.*; 19 20 public class ShoppingCartEJB implements SessionBean { 21 private SessionContext sessionContext; 22 23 // товары в магазинной тележке и их количество 24 private Collection orderProductModels ,- 25 26 // создание нового компонента ShoppingCart 27 public void ejbCreate{) 28 { 29 orderProductModels = new ArrayListO; 30 } 31 32 // получение содержимого магазинной тележки 33 public Collection getContentsO 34 { 35 return orderProductModels; 36 J 37 38 // добавление в тележку книги с заданным ISBN-кодом 39 public void addProductf String isbn ) 40 throws ProductNotFoundExeeption, EJBException 41 { 42 // проверка, имеется ли в магазинной тележке 43 // книга с заданным ISBN-кодом 44 Iterator iterator = orderProductModels.iterator(); 45 46 while ( iterator .tiasUext() ) { 47 OrderProductModel orderProductModel = 48 ( OrderProductModel ) iterator.next(); 49 50 ProductModel productModel = 51 orderProductModel.getProductModel(); 52 53 // если книга уже имеется в тележке, инкрементировать ее количество 54 if ( productModel.getISBN().equals( isbn ) ) { 55 56 orderProductModel.setQuantity(
Практический пример корпоративного приложения. Б из и ее-логика: часть 1 537 57 orderProductModel.getQuantity() + 1 ); 58 59 return; 60 } 61 62 } // конец блока while 63 64 // если книга отсутствует в тележке, найти книгу с заданным 65 // ISBN-кодом и добавить объект OrderProductModel в компонент ShoppingCart 66 try i 67 InitialContext context = new InitialContext(); 68 69 Object object = context.lookup( 70 "java:comp/env/ejb/Product" ); 71 72 FroductHome productHome = ( ProductKome ) 73 PortableRemoteObjeet.narrow( object, 74 ProductHome.class ); 75 76 // нахождение книги с заданным ISBN-кодом 77 Product product = productHome.findByPrimaryKey( isbn ); 78 79 // получение объекта ProductModel 80 ProductModel productModel ■ product.getProductModel(}; 81 82 // создание объекта OrderProductModel для объекта 83 // ProductModel и задание количества товара дли него 84 OrderProductModel orderProductModel = 85 new OrderProductModel(); 86 87 orderProductModel.setProductModel( productModel ); 88 orderProductModel.setQuaatity( 1 ); 89 90 // добавление объекта OrderProductModel в компонент ShoppingCart 91 orderProductModels.add( orderProductModel ); 92 93 } // Конец блока try 94 95 // обработка исключения при поиске записи дли товара 96 catch ( FinderException finderException ) { 97 finderException.printStackTrace(); 98 99 throw new ProductNotFoundException( "The Product " + 100 "with ISBN " + isbn + " was not found." >; 101 } 102 103 // обработка исключении при вызове методов EJB-комяонента Product 104 catch ( Exception exception ) { 105 throw new EJBExcoption( exception ); 106 } 107 108 } // конец метода addProduct 109 110 // удаление книги с заданным tSBN-кодом из тележки
538 Глава 11 111 public void removeProduct( String isbn ) 112 throws ProductNotFoundException 113 { 114 Iterator iterator = orderProductModels.iterator(); 115 116 while { iterator.hasNext() ) { 117 118 // получение следующего объекта OrderProduct в компоненте Shopp i ngCa r t 119 OrderProductModel OrderProductModel = 120 ( OrderProductModel ) iterator.next(); 121 122 ProductModel productModel = 123 OrderProductModel.getProductModel() ; 124 125 // удаление книги с заданным ISBN-кодом из тележки 126 if ( productModel.getlSBN ().equals( isbn ) ) { 127 orderProductModels.remove( OrderProductModel ); 128 129 return; 130 } 131 132 } // конец блока while 133 134 // возбуждение исключения, если книга не найдена в тележке 155 throw ivew ProductHotFoundException ( "The Product " + 136 "with ISBN " + isbn + " was not found in your " + 137 "ShoppingCart." ); 138 139 } // коней метода removeProduct 140 141 // задание количества данного товара в тележке 142 public void setProductQuantity( String isbn, 143 int productQuantity ) throws ProductNotFoundException 144 { 145 // возбуждение исключения IllegalArgumentException, если указано недопустимое количество 14 6 if ( productQuantity < 0 ) 147 throw new IllegalArgumentException( 148 "Quantity cannot be less than zero." ); 14 9 150 // удаление товара, если значение productQuantity меньше 1 151 if ( productQuantity = 0 ) ( 152 removeProduct( isbn ); 153 return; 154 } 155 156 Iterator iterator = orderProductModels.iterator(); 157 158 while ( iterator.hasNextO } f 159 160 // получение следуянцего объекта OrdarProduct в компоненте ShoppingCart 161 OrderProductModel OrderProductModel = 162 ( OrderProductModel ) iterator.next() ,- 163 164 ProductModel productModel =
Практический пример корпоративного приложения. Бизнес-логика: часть 1 539 165 orderProductModel.getProductModel{); 166 167 // установка количества для книги с заданным ISBN-кодом 168 if ( productModel.getlSBN{).equals{ isbn ) ) { 169 orderProductModel.setQuantity( productQuantity ); 170 return; 171 } 172 17 3 } // конец блока while 174 175 // возбуждение исключения, если книга не найдена в тележке 176 throw new ProductNotFoundException( "The Product " + 177 "with ISBN " + isbn + " was not found in your " + ПВ "ShoppingCart. " ) ; 179 180 } // конец метода setProductQuantity 131 182 // подсчет стоимости (создание нового заказа) 183 public Order checkout( String userlO ) 184 throws ProductNotFoundException, EJBException 185 { 186 // возбуждение исключения, если тележка пуста 187 if ( orderProductModels.isEmpty() ) 188 throw new ProductNotFoundException( "There were " + 189 ''no Products found in your ShoppingCart." ); 190 191 // создание объекта OrderModel, содержащего сведения о заказе 192 OrderModel orderModel = new OrderModel(); 193 194 // задание в качестве даты заказа сегодняшней даты 195 orderModel.setOrderDate( new Date() ); 196 197 // задание списка объектов OrderProduct в заказе OrderModel 198 orderModel.setOrderProductModels( orderProductModels ); 199 200 // задание значения false для флага доставки shipped 201 orderModel.setShipped( false ); 202 203 // использование интерфейса OrderHome для создания нового заказа 204 try { 205 InitialContext context = new InitialContext{); 206 207 // поиск EJB-хомпонента Order 208 Object object = context.lookup( 209 "Java:comp/env/ejb/Order" ); 210 211 OrderHome orderHome = ( OrderHome ) 212 PortableRemoteObject.narrow( object, 213 OrderHome.class ); 214 215 // создание нового компонента Order о использованием 216 // объекта OrderModel и идентификатора покупателя userlD 217 Order order = orderHoae.create( orderModel, user-ID ); 218 219 // освобождение тележки для дальнейших покупок 220 orderProductModels = new ArrayList(); 221
540 Глава 11 222 /7 возврат созданного EJB-компонента Order 223 return order; 224 225 } // конец блока try 226 227 // обработка исключения при поиске EJB-компонента Order 228 catch ( Exception exception ) { 229 throw new EJBException( exception ); 230 J 231 232 } // конец метода checkout 233 234 // получение общей стоимости товаров в магазинной тележке 235 public double getTotalO 236 { 237 double total = 0.0; 23S Iterator iterator = orderProductModels . iterator О <" 239 240 // подсчет обшей стоимости заказа 241 while { iterator.hasNextO ) { 242 243 // получение следукщег'о объекта OrderProduct в компоненте ShoppingCart 244 OrderProductModel orderProductHodel = 245 ( OrderProductHodel ) iterator.next(); 246 247 ProductModel productModel = 248 OrderProductModel.getProductModel(); 249 250 // добавление стоимости данного товара к общей сумме 251 total += ( productModel.getPrice(} * 252 OrderProductModel.getQuantity0 ); 253 } 254 255 return total,- 256 257 } // конец метода getTotal 258 259 // установка контекста SessionContext 260 public void setSessionContext( SessionContext context ) 261 { 262 SessionContext = context; 263 } 264 265 // активация экземпляра EJB-компонента ShoppingCart 266 public void ejbActivateO {} 267 268 // пассивация экземпляра EJB-компонента ShoppingCart 269 public void ejbPassivate() {} 270 271 // удаление экземпляра EJB-компонента ShoppingCart 272 public void ejbRamove() {} 273 } Рис. 11.3. Реализация ShoppingCartEJB удаленного интерфейса ShoppingCart
Практический пример корпоративного приложения. Бизнес-логика: часть 1 541 Метод addProduct (строки 39-108) добавляет товар (книгу) в магазинную тележку. В строках 46-62 определяется, имеется ли уже данная книга в тележке. Если да, в строках 56-57 инкрементируется значение параметра quantity соответствующего объекта OrderProductModel. В противном случае метод fiudBy- PrimaryKey интерфейса ProductHome ищет книгу (компонент Product) с заданным значением ISBN (строка 77). В строках 83-85 создается объект OrderProductModel для хранения книги в магазинной тележке. В строке 87 в объект OrderProductModel добавляется объект ProductModel, а в строке 88 для параметра quantity объекта OrderProductModel устанавливается значения 1. В строке 91 объект OrderProductModel добавляется в коллекцию, что завершает процесс добавления товара в магазинную тележку. Если метод fiudBy Primary Key интерфейса ProductHome не находит книгу с указанным первичным ключом, в строках 96-101 перехватывается исключение finder Except ion. В строках 99-100 возбуждается исключение Product NotFound- Exception, указывающее, что книга с данным ISBN-кодом не может быть найдена. Метод removeProduct (строки 111-139) сравнивает ISBN-код (свойство ISBN) каждой книги (объект Product) в коллекции объектов OrderProductModel компонента ShoppingCart с ISBN-кодом удаляемой книги. Если книга с заданным ISBN-кодом найдена, в строке 127 соответствующий объект OrderProductModel удаляется из коллекции. Если книга (объект Product) в магазинной тележке не найдена, в строках 135-137 возбуждается исключение ProductNotFoundException. Метод setProductQuantity (строки 142—180) устанавливает свойство quantity для объекта OrderProductModel, т.е. количество экземпляров даипой книги в магазинной тележке. Если аргумент productQuantity меньше О, в строках 147-148 возбуждается исключение IUegalArgumentException. Если значение productQuantity равно О, в строке 152 данная книга (объект Product) удаляется из магазинной тележки (ShoppingCart). В строках 158-173 сравнивается ISBN-код каждой из книг в коллекции OrderProductModel с заданным ISBN-кодом. В строке 169 обновляется количество quantity для соответствующей книги путем вызова метода setQuantity интерфейса OrderProductModel. Если книга с заданным ISBN-кодом не найдена в магазинной тележке, в строках 176-178 возбуждается исключение ProductNotFoundException. Метод checkout (строки 183-232) осуществляет размещение заказа (компонент Order) на товары, имеющиеся в магазинной тележке. Каждый заказ (компонент Order) должен иметь соответствующего покупателя (компонент Customer), поэтому метод checkout принимает в качестве аргумента идентификатор покупателя userlD. В строках 192-201 создается объект OrderModel для представления подробной информации о заказе. Каждый заказ (компонент Order) имеет дату оформления заказа orderDate, флаг доставки shipped и коллекцию объектов OrderProductModel. В строке 195 устанавливается дата для объекта OrderModel. В строке 198 вызывается метод setOrderProductModels класса OrderModel для добавления в компонент Order списка объектов OrderProductModel. В строке 201 для флага shipped устанавливается значение false, которое указывает, что заказ не был доставлен со склада. Компонент-сущность EJB Order будет рассмотрен в разделе 11.5. В строке 217 вызывается метод create интерфейса OrderHome для создания нового заказа. Метод create принимает в качестве аргумента объект OrderModel, содержащий информацию о создаваемом заказе, и строку, содержащую идентификатор покупателя userlD. В строке 220 осуществляется освобождение магазинной тележки ShoppingCart путем присвоения нового списка типа Array List коллекции ссылок orderProductModels. В строке 223 возвращается удаленная ссылка на вновь созданный компонент Order. В строках 228-230 перехватываются любые возникшие исключения. Метод getTotal (строки 235-257) просматривает коллекцию объектов OrderProductModel и вычисляет общую стоимость содержимого магазинной тележки.
542 Глава 11 11.3.3. Собственный интерфейс ShoppingCartHome Интерфейс ShoppingCartHome (рис. 11,4) определяет единственный метод create (строки 15-16), который создает новый экземпляр EJB-компонента ShoppingCart. Контейнер EJB предоставляет реализацию для метода create. На рис. 11.5 и 11.6 представлены параметры дли развертывания сеансового EJB-компоненга с состоянием ShoppingCart. В дополнение к приведенным здесь установкам, следует установить для типа транзакции Transaction Type значение Required для всех бизнес-методов. 1 // ShoppingCartHome.Java 2 // ShoppingCartHome - собственный интерфейс для сеансового 3 // EJB-компонента с состоянием ShoppingCart. 4 package сою.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 7 import java.rmi.RemoteException; 8 9 // Пакеты расширений Java 10 import javax.ejb. *,- 11 12 public interface ShoppingCartHome extends EJBHome { 13 14 // создание нового EJB-компонента ShoppingCart 15 public ShoppingCart create{) 16 throws RemoteException, CreateException; 17 ) , ___ ^__^__ Рис. 11.4. Интерфейс ShoppingCartHome для создания экземпляров EJB-компонента ShoppingCart Основные параметры развертывания для EJB-компонента ShoppingCart Тип Bean Type Класс Enterprise Bean Class Собственный интерфейс Home Interface Удаленный интерфейс Remote Interface Stateful Session com.deitel.advjhtpl.bookstore.ejb.ShoppirlgCartEJE com.deitel.advjhtpl.bookstore.ejb.ShoppingCartHome com.deitel.advjhtpl.bookstore.ejb.ShoppingCart Рис. 11.5. Основные параметры развертывания для EJB-компонента ShoppingCart Ссылки на EJB-компоненты для компонента ShoppingCart Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте ejb/Product Entity com.deitel.advjhtpl.bockstore.ejb.ProductHome com.deitel.adviatpl.bookstore.ejb.Product Product ' ejb/Order Entity com.deitel.advjhtpl.bookstore.ejb.OrderHome
Практический пример корпоративного приложения. Бизнес-логика: часть 1 543 Ссылки на EJB-компокенты для компонента ShoppingCart Удзленный интерфейс Remote Имя JNDI Name com.deitel.advjhtpl.bookstore.ejb.Order Order Рис. 11,6. Ссылки на EJB-компоненты для компонента ShoppingCart 11.4. Реализация EJB-компонента Product EJB-сущность Product использует контейнерное управление перси стентностью для представления товара в Internet-магазине Deitel Bookstore. Контейнер EJB реализует методы, которые осуществляют выборку, вставку, обновление и удаление данные из базы данных. Администратор развертывания должен предоставить информацию о том, как создавать таблицы в базе данных, а также задать на этапе развертывания SQL-запросы, используемые для методов создания (create), удаления (remove) и поиска (finder). 11.4.1. Удаленный интерфейс Product Удаленный интерфейс Product (рис. 11.7) объявляет метод getProductModel (строки 17-18), который возвращает объект ProductModel, содержащий информацию о товаре. 1 // Product.Java 2 // Product - удаленный интерфейс для EJB-сущности Product. 3 pacfcage com.deitel,advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 import j ava.rmi.RemoteException; 7 8 // Пакеты расширений Java 9 import javax.ejb.*; 10 11 // Пакеты Deitel 12 import com.deitel.advjhtpl.bookstore.model.*; 13 14 public interface Product extends EJBObject 1 15 16 // получение сведений о товаре в виде объекта ProductModel 17 public ProductModel getProductModel() 18 throws RemoteException; 19 ) Рис. 11.7. Удаленный интерфрйг Product для внесения изменений в экземпляры EJB-компонента Product, содержащие сведения о товаре 11.4.2. Реализация ProductEJB удаленного интерфейса Product Реализация ProductEJB удаленного интерфейса Product (рис. 11.8) использует контейнерное управление персистентностью. Контейнер EJB управляет синхронизацией с содержимым базы данных открытых (public) элементов данных, объявленных в строках 18-24. Метод getProductModel (строки 27—43) создает объект ProductModel, который содержит информацию о товаре, В строке 30 создается эк-
544 Глава 11 земпляр класса ProductModel, а в строках 33-39 вызываются set-методы для инициализации элементов данных объекта ProductModel. 1 // ProductsJB.java 2 // EJB-сущность Product представляет книгу и 3 // содержит такую информации, как ISBN-код, издатель, автор, 4 // название, цена, количество страниц и рисунок обложки. 5 package com.deitel.advjhtpl.bookstore.ejb; б 111 Пакеты расширений Java 8 import javax.ejb.*; 9 10 // Пакеты Deitel 11 import com.deitel.advjhtpl.bookstore.model.*; 12 import com,deitel.advjhtpl.bookstore.*; 13 14 public class ProduetEJB implements EntityBean { 15 private EntityContext entityContext; 16 17 // поля, управляемые контейнером 18 public String ISBN; 19 public String publisher; 20 public String author; 21 public String title; 22 public double price; 23 public int pages; 24 public String image; 25 26 // получение сведений о книге в виде объекта ProductModel 27 public ProductModel getProduetModel() 28 { 29 // создание нового объекта ProductModel 30 ProductModel productModel = new ProductModel(); 31 32 // инициализация объекта ProductModel данными иэ компонента Product 33 productModel.setISBN( ISBN ); 34 productModel.setPublisher( publisher ); 35 productModel.setAuthorf author ); 36 productModel.setTitle( title ); 37 productModel.setPrice( price ) ; 38 productModel.setPages( pages ); 39 productModel.setImage( image ); 40 41 return productModel; 42 43 } // конец метода getProduetModel 44 45 // задание сведений о книге с использованием объекта ProductModel 46 private void setProductModel( ProductModel productModel ) 47 { 48 // заполнение элементов данных компонента Product 49 // данными из предоставленного объекта ProductModel 50 ISBH = productModel.getlSBN(); 51 publisher = productModel.getPublisher(); 52 author = productModel.getAuthor(); 53 title = productModel.getTitie(J; 54 price - productModel .getPrice 0-"
Практический пример корпоративного приложения. Бизнес-логика: часть 1 545 55 pages = productModel.getFages{) ,- 56 image = productModel.getImage(); 57 58 } // конец метола setProductModel 59 60 // создание экземпляра EJB-компонента Product с использованием заданного объекта ProductModel 61 public String ejbCreate{ ProductModel productModel ) 62 < 63 setProductModel( productModel ); 64 return null; 65 } 66 67 // выполнение необходимых после создания объекта действий 68 public void ejbPostCreatet ProductModel productmodel } (} 69 70 // задание контексша EntityContext 71 public void setEntityContext( EntityContext context ) 72 { 73 entityContext = context; 74 } 75 76 // сброс контекста EntityContext 77 public void unsetEntityContextf) 78 { 79 entityContext = null; SO ) 81 82 // активация экземпляра EJB-компонента Product 83 public void ejbActivate(> 84 { 85 ISBN = ( String ) entityContext.getPrimaryKey(); 86 } 87 88 // пассивация экземпляра EJB-компонента Product 89 public void ejbPassivate() 90 { 91 ISBN = null; 92 } 93 94 // удаление экземпляра EJB-компонента Product 95 public void ejbRemoveO {> 96 97 // сохранение данных EJB-компонента Product в базе данных 98 public void eJbStoreO () 99 100 // загрузка данных EJB-компонента Product из базы данных 101 public void eJbLoadO {) 102 1 ____^____ Рис. 11.8. Реализация ProductEJB удаленного интерфейса Product Метод setProductModel (строки 46-58) устанавливает подробную информацию о товаре с использованием значений из заданной модели ProductModel. В строке 50 для элемента данных ISBN устанавливается значение ISBN-кода, содержащегося в аргументе ProductModel. В строках 51-56 устанавливаются значения других элементов данных ProductEJB. Метод ejbCreate (строки 61-65) принимает аргу-
546 Глава 11 мент Product Model. Метод ejbCreate вызывает метод setProdnctModel с предоставленным объектом ProductModel для инициализации экземпляра EJB-компонента Product (строка 63). 11.4.3. Собственный интерфейс ProductHome Интерфейс ProductHome (рис. 11.9) создает новые экземпляры EJB-компонента ProductEJB и объявляет методы поиска для нахождения имеющихся товаров (компонентов Product). Метод create (строки 18-19) соответствует методу ejbCreate, используемому в листинге, представленном на рис. 11,8, и обеспечивает интерфейс для создания экземпляра EJB-компонента ProductEJB. Метод findBy- PrimaryKey (строки 22-23) принимает в качестве строкового аргумента ISBN-код для определенной книги в базе данных. Метод findAUProducts (строки 26-27) возвращает коллекцию всех книг, содержащихся в базе данных. Метод findByTitle (строки 30-31) осуществляет поиск книг, названия которых содержат заданную строку searchString, и возвращает коллекцию удаленных ссылок на товары (компоненты Product). Контейнер EJB реализует каждый из методов поиска с использованием SQL-запросов, которые администратор развертывания должен предоставить на этапе развертывания. 1 // ProductHome.Java 2 // ProductHome - собственный интерфейс для ЕJB-сущности Product. 3 package com.deitel.advjhtpl.bookstore,еЗЬ; 4 5 // Набор базовых пакетов Java 6 import. Java.rmi.RemofceException; 7 import java.util,Collection; 8 9 // Пакеты расширений Java 10 import javax.ejb.*; 11 12 // Пакеты Deitel 13 import com.deitel.advjhtpl.bookstore.model.*; 14 IS public interface ProductHome extends EJBHome ( 16 17 // создание EJB-компонента Product с использованием заданного объекта ProductModel 18 public Product create( ProductModel productModel ) 19 throws RemoteException, CreateException; 20 21 // нахождение книги с заданным ISBN-кодом 22 public Product findByPrimaryKey( String isbn ) 23 throws RemoteException, FinderException; 24 25 // нахождение всех книг 26 public Collection findAUProducts () 27 throws RemoteException, FinderException; 2» 29 // нахождение книг с заданным названием 30 public Collection findByTitle( String title ) 31 throws BemoteException, FinderException; 32) Рис. 11.9. Интерфейс ProductHome для нахождения и создания экземпляров EJB-компонента Product
Практический пример корпоративного приложения. Бизнес-логика: часть 1 547 11.4.4. Класс Product Model Класс ProductModel (рис. 11.10) реализует интерфейс типа Serializable, чтобы можно было осуществлять сериализацию экземпляров через RMI-IIOP. Класс ProductModcl имеет частный (private) элемент данных (строки 18-25) и методы set и get (строки 28-109) для каждого свойства EJB-компонента Product. 1 // ProductModel.java 2 // Класс ProductModel представляет книгу в Internet-магааине 3 // Deitel Bookstore и содержит ISBN-код, автора, название и рисунок обложки книги. 4 package com.deitel.advjhtpl.bookstore.model; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import Java.util.*; 9 import Java.text.*; 10 11 // Пакеты сторонних поставщиков 12 Import org.w3c.dom.*; 13 14 public class ProductModel implements Serializable, 15 XMLGanerator { 16 17 // свойства объекта ProductModel 18 private String ISBN; 19 private String publisher; 20 private String author; 21 private String title; 22 private double price; 2 3 private int pages; 24 private String image; 25 private int quantity; 26 27 // задание ISBN-кода 28 public void setISBN{ String productlSBN ) 29 30 31 32 33 // получение ISBN-кода 34 public String getlSBN() 35 36 37 38 39 // задание издателя 40 public void setPublisher{ String productPublisher ) 41 42 43 44 45 // яолучвние издателя 46 public String getPublisher() 47 { 48 return publisher; ISBN = productlSBN; return ISBN; publisher = productPublisher:;
548 } // задание автора public void setAuthor( string productAuthor ) author = productAuthor; // получение автора public String getAuthor() return author; // Задание названия книги public void setTitle( String produCtTitle ) title = productTitle; // получение названия книги public String getTitle() return title; // задание цены public void setPrice( double productPrice ) price = productPrice; // получение цены public double getPrice() return price; // задание количества страниц public void setPages{ int pageCount ) pages = pageCount; // получение количества страниц public int getPagesf) return pages; // задание DRL файла рисунка обложки public void setImage( String productlmage ) image = productlmage;
Практический пример корпоративного приложения. Бизнес-логика: часть 1 549 105 // получение UKL файла рисунка обложки 106 public String getImage(} 107 { 108 return image; 109 } 110 111 // получение XKL-предетавлекия товара (книги) 112 public Element getXML{ Document document ) 113 { 114 // создание элемента product 115 Element product = document.createElement{ "product" J; 116 117 // создание элемента ISBN 118 Element temp = document.createElement( "ISBN" ); 119 temp.appendChild( 120 document.createTextNode( getISBN() ) ); 121 product.appendChild( temp ); 122 123 // создание элемента publisher 124 temp = document.createElement( "publisher" ); 125 temp.appendChild( 126 document.createTextNode( getPublisher() ) ); 127 product.appendChild( temp ); 128 129 // создание элемента author 130 temp = document.createElement( "author" ); 131 temp.appendChild( 132 document.createTextNode( getAuthorO ) }; 133 product.appendChiId{ temp ); 134 135 // создание элемента title 136 temp = document.createElement( "title" ); 137 temp.appendCh iId( 138 document.createTextNode^ getTitleO > } : 139 product.appendChild( temp ) ; 140 141 HumberFonnat priceFormatter = 142 NumberFormat.getCurrencylnstance( Locale.US ); 143 144 // создание элемента price 145 temp = document.createElement( "price" ); 146 temp.appendChild( document.createTextNode( 147 priceFormatter.format( getPrice() ) ) }; 148 product.appendChild( temp ); 149 150 // создание элемента pages 151 temp = document.createElement( "pages" }; 152 temp.appendChi1d( documen t.crea teTextMode{ 153 String.valueOf( getPages() ) ) ); 154 product.appendChild( temp ); 155 156 // создание элемента image 157 temp = document.createElement( "image" j; 158 temp.appendChi1d( 159 document.createTextNode( getImage О ) ); 160 product.appendChild( temp );
550 Глава 11 161 162 163 164 165 } return product; } // конец метода getXML Рис. 11.10. Класс Product Model для поиска данных в EJB- компоненте Product Класс Product Model также реализует интерфейс X ML Generator (рис. 11.11), который определяет единственный метод getXML. Метод getXML класса Product- Model (строки 112-164) генерирует элемент (объект Element) XML для данных, содержащихся в объекте ProductModel. Этот метод использует аргумент Document для создания элементов XML для каждого объекта (элемента) данных класса Рго- ductModel, однако метод getXML при этом не модифицирует документ (объект Document). В строке 162 возвращается вновь созданный элемент Product. 1 // XMLGenerator.Java 2 // XMLGenerator - интерфейс для классов, которые способны создавать 3 // элементы XML. ХМЬ-элемент, возвращаемый методом getXML, 4 // должен содержать элементы для каждого открытого свойства. 5 package com.deitel.advjhtpl.bookstore.model; 6 7 // Пакеты сторонних поставщиков 8 import org.w3c.dom.*; 9 10 public interface XMLGenerator i 11 12 // формирование XML-элемекта для заданного объекта 13 public Element getXML( Document document ); 14 ) —^_ - . Рис. 11.11. Интерфейс XMLGenerator для генерирования элементов XML для открытых свойств На рис. 11.12 и 11,13 представлены параметры развертывания для компонента-сущности EJB Product. В дополнение к этим параметрам, для всех бизнес-методов следует установить значение Required для типа транзакции Transaction Type. Основные параметры развертывания для EJB-компонента Product | 1ИГ1 Bean Type Класс Enterprise Bean Class Собственный интерфейс Home Interface Удаленный интерфейс Remote Interface Entity 1 com.deitel.advjhtpl.bookstore.ejb.ProductEJB | com.deifcel.advjhtpl.bookstore.ejb.ProductHome 1 com.deitel.advjhtpl.bookstore.ejb.Product Рис. 11.12. Основные параметры развертывания для EJB-компонента Product Параметры управления данными и развертыванием для EJB-компонента Product Управление персистентностью Persistent Management Container-Managed Persistence Класс первичного ключа Primary Key Class java.lang.String
Практический пример корпоративного приложения. Бизнес-логика: часть 1 551 Параметры управления данными и развертыванием для EJB-компонента Product Имя поля первичного ключз Primary Key Fietd Name Имя базы данных JNDI Database JNDI Name SQL-оператор SQL Statement метода findByTitle SQL-оператор SQL Statement метода findAIIProducts SQL-оператор SQL Statement метода ejbStore SQL-оператор SQL Statement метода ejbCreate SQL-оператор SQL Statement метода ejbRemove SQL-оператор SQL Statement метода findByPrimaryKey SQL-оператор SQL Statement метода ejb Load SQL-оператор создания таблицы SQL Statement Table Create SQL-оператор удаления таблицы SQL Statement Table Delete ISBN jdbc/Books tor* SELECT ISBN FROM Product WHERE title LIKE ?1 SELECT ISBN FROM Product WHERE 1=1 UPDATE Product SET author=?, image=?, pages=?, price=?, publisher=?, title=?, WHERE ISBN=? INSERT INTO Product (ISBN, author, imag*, pages, price, publisher, title) VALUES (?, ?, ?, ?, ?, ?, ?) DELETE FROM Product WHERE ISBN=? SELECT ISBN FROM Product WHERE ISBN=? SELECT author, image, pages, price, publisher, title FROM Product WHERE ISBN=? CREATE TABLE Product (ISBN VARCHAR (255), author VARCHAR (255), image VARCHAR (255) , pages INTEGER NOT HULL, price DOUBLE PRECISION NOT NULL, publisher VARCHAR (255), title VARCHAR (255), CONSTRAINT pk_Product PRIMARY KEY (ISBN)) DROP TABLE Product Рис. 11.13. Параметры управления данными и развертыванием для EJ В -компонента Product 11.5. Реализация EJ В-компонента Order EJB-сущность Order представляет заказ, сделанный посетителем Internet-магазина Deitel Bookstore. Каждый заказ Order содержит выбранные покупателем товары Product с указанием их количеств, а также идентификатор customerlD покупателя, разместившего заказ. 11.5.1. Удаленный интерфейс Order Удаленный интерфейс EJB-компонента Order (рис. 11.14) определяет бизнес-методы, доступные для этого EJB-компонента. Метод getOrderModel (строка 17) возвращает объект OrderModel, содержащий информацию о заказе. Метод setShipped (строки 20—21} делает отметку, что заказ был доставлен со склада. Метод is Shipped (строка 24) возвращает булево значение, указывающее, был ли заказ доставлен. 11.5.2. Реализация OrderEJB удаленного интерфейса Order Реализация OrderEJB (рис. 11.15) удаленного интерфейса Order объявляет открытые методы для управления персистентностью на стороне контейнера (строки 26-29). Метод getOrderModel (строки 32-91) создает экземпляр класса Order-
552 Глава 11 Model, который содержит информацию о заказе. В строках 42-44 объект OrderMo- del заполняется значениями элементов данных EJB-компонента Order. 1 // Order.java 2 // Order - удаленный интерфейс для EJB-супзяоети Order. 3 package com.deitel.advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 import Java.rmi.RemoteException; 7 8/7 Пакета расширений Java 9 import javax.ejb.*; 10 11 // Пакеты Deitel 12 import com.deitel.advjhtpl.bookstore.model.*; 13 14 public interface Order extends EJBObject { 15 16 // получение сведений о заказе в виде объекта OrderModel 17 public OrderModel getOxderModel() throws RemoteException; 18 19 // установка флага доставки shipped 20 public void setshipped( boolean flag ) 21 throws RemoteException; 22 23 // получение флага доставки shipped 24 public boolean isShippedf) throws RemoteException; 25} Рис. 11.14. Удаленный интерфейс Order для изменения информации, содержащейся в экземплярах EJ В-компонента Order 1 // OrderEJB.Java 2 // EJB-сущность Order представляет заказ и 3 // содержит идентификатор заказа, дату заказа, обшув 4 // стойкость заказа и информации, был ли заказ доставлен. 5 package com.deltel.advjhtpl.bookstore.ejb; € 7 // Набор базовых пакетов Java 8 import java.util.*; 9 import Java.text.DateFormat; 10 import Java.rmi.RemoteException; 11 \2 /I Пакеты расширений Java 13 import javax.ejb.*; 14 import javax.naming.*; 15 import javax.rmi.PortableRemoteObject; 16 17 // Пакеты Deitel 18 import com.deitel.advjhtpl.bookstore.model.*, 19 20 public class OrderEJB implements EntityBean { 21 private EntityContext entityContext; 22 private InitialContext initialContext; 23 private DateFormat dateForroat; 24
Практический пример корпоративного приложения. Бизнес-логика: часть 1 553 25 // поля, управляемые контейнером 26 public Integer orderID; 27 public Integer customerlD; 28 public String orderDate,* 29 public boolean shipped; 30 31 // получение сведений о заказе в виде объекта OrderModel 32 public OrderModel getOrderModel() throws EJBException 33 { 34 // создание нового объекта OrderModel 35 OrderModel orderHodel = new OrderModelО; 36 37 // поиск EJB-хомпонента OrderProduct для извлечения 38 // списка товаров, содержащихся в заказе 39 try ( 40 41 // заполнение элементов данных OrderModel данными из компонента Order 42 orderModel.setOrderID( orderlD ); 43 orderModel.setOrderDate( dateFormat.parse( orderDate ) ); 44 orderModel.setShipped( shipped ); 45 46 initialContext = new InitialContext(}; 47 48 Object object = initialContext.lookup ( 49 "Java:comp/env/ejb/OrderProduct" ); 50 51 OrderProductHome OrderProductHome = 52 ( OrderProductHome ) PortableRemoteObject.narrow( 53 object, OrderProductHome.class ); 54 55 // получение записей таблицы OrderProduct для заказа 56 Collection orderProducts = 57 OrderProductHome.findByOrderlD( orderlD ); 5B 59 Iterator iterator = orderProducts.iterator(); 60 61 // объекты OrderProductModel, помещаемые в объект OrderModel 62 Collection orderProductModels = new ArrayListO; 63 64 // получение объекта OrderProductModel для каждого товара в заказе 65 while { iterator.hasNext() ) [ 66 OrderProduct OrderProduct = ( OrderProduct ) 67 PortableRemoteObject.narrow) iterator.next(), 68 OrderProduct.class ); 69 70 // получение объекта OrderProductModel для записи таблицы OrderProduct 71 OrderProductModel orderProductModel = 72 OrderProduct.getOrderProductModel(}; 73 74 // добавление объекта OrderProductModel в 75 // список объектов в компоненте Order 76 orderProduotModels.add( orderProductModel ); 77 }
554 Глава 11 78 79 // добавление коллекции объектов OrderFrodtictModel в объект OrderModel 80 orderModel .setOrderProductModele( orderProductModels ) 81 82 } // конец блока try 83 84 // обработка исключения при работе с EJB-компонентом OrderProduct 85 catch ( Exception exception ) ( 86 throw new EJBException( exception \; 87 ) 88 89 return orderModel; 90 91 } // конец метода getOrderModel 92 93 // установка флага доставки shipped 94 public void setshipped( boolean flag ) 95 ( 96 shipped = flag; 97 ) 98 99 // получение флага доставки shipped 100 public boolean isShippedf) 101 I 102 return shipped; 103 J 104 105 // создание нового EJB-компонента Order с использованием заданного объекта OrderModel и userXD 10 6 public Integer ejbCreete( OrderModel order, String userlD ) 107 throws CreateException 108 { 109 // извлечение уникального значения для первичного ключа этого 110 // заказа с использованием EJB-компонента SequenceFactory 111 try ( 112 initialContext = new InitialContext(); 113 114 Object object = initialContext.lookup( 115 "javaicomp/env/ejb/SequenceFactory" ); 116 117 SequenceFactoryHome SequenceFactoryHome = 118 ( SequenceFactoryHome ) 119 PortableRemoteObjeCt,narrow( 120 object, SeguenceFactoryHome.class >; 121 122 // нахождение последовательности для таблицы CustomerOrder 123 SequenceFactory SequenceFactory = 124 seguenceFactoryHome.findByPrimaryKey( 125 "CustomerOrders" >; 126 127 // получение следящего уникального идентификатора orderlD 128 orderlD = SequenceFactory.getNextID(); 129 130 // получение даты, стоимости, флага доставки и списка объ-
Практический пример корпоративного приложения. Бизнес-логика: часть 1 555 131 // ектов OrdarProduct иэ предоставленного объекта OrderModel 132 orderDate = dateFormat.formatf order, getOrderDate () ); 133 shipped = order.getShipped(); 134 135 // получение объектов OrderProductModel, содержащих объекты OrderModel 136 Collection orderProductModels = 137 order.getOrderProductModels{); 138 139 // создание EJB-компонентов OrderProduct для каждого 140 // товара в заказе для отслеживания их количеств 141 object = initialContext.lookup( 142 "javarcomp/env/ejb/OrderProduct" ); 143 144 OrderProductHome orderProductHome = 145 ( OrderProductHome } PortableRemoteObject.narrow( 146 object, OrderProductHome.class ); 147 148 Iterator iterator = orderProductModels.iterator(); 149 150 // создание EJB-компонента OrderProduct, содержащего 151 // ISBN-код, количество и номер для этого заказа 152 while { iterator.hasNext() ) { 153 154 OrderProductModel OrderProductModel = 155 ( OrderProductModel ) iterator.next(); 156 157 // задание идентификатора orderlD для записи таблицы OrderProduct 158 OrderProductModel.setOrderlD{ orderlD ); 159 160 // создание экземпляра EJB-компонента OrderProduct 161 orderProductHome.create( OrderProductModel ); 162 } 163 164 // получение идентификатора customerlD покупателя, сделавшего заказ 165 object = initialContext.lookup( 166 "Java:comp/env/ejb/Customer" ); 167 168 CustomerHome customerHome = 169 ( CustomerHome ) PortableRemoteObject.narrow( 170 object, CustomerHome. class ) ,* 171 172 // использование предоставленного идентификатора userlD для нахождения покупателя 173 Customer customer = 174 customerHome.findByUserlD( userID ); 175 176 customerlD = ( Integer ) customer.getPrimaryKey(); 177 178 } // конец блока try 179 180 // обработка исключения при поиске ЕЛВ-компонентов 181 catch [ Exception exception ) { 182 throw new CreateException{ exception.getMessagef) );
556 Глава 11 163 } 184 185 return null; 186 187 } // конец метода ejbCreate 188 189 // выполнение необходимых после создания объекта действий 190 public void ejbPostCreate( OrdexModel order, String id ) {} 191 192 // задание контекста EntityContext 193 public void setEntityContext( EntityContext context > 194 { 195 entityContext = context; 196 dateFoxmat = DateFormat.getDateTimelnstance( 197 DateFoxmat.FULL, DateFoxmat.SHORT, Locale.OS ); 198 } 199 200 // сброс контекста EntityContext 201 public void. unsetEntityContext() 202 { 203 entityContext = null; 204 > 205 206 // активация экземпляра БJB-компонента Order 207 public void ejbActivate() 208 { 209 orderlD = ( Integer ) entityContext.getPrimaxyKey () ,- 210 } 211 212 // пассивация экземпляра EJB-компонента Order 213 public void ejbPassivate() 214 { 215 orderlD = null; 216 } 217 218 // удаление экземпляра EJB-жомпонента Order 219 public void ejbRemoveO {} 220 221 // сохранение данных EJB-компонента Order в базе данных 222 public void ejbStore() {} 223 224 // загрузка данных EJB-коипонента Order из базы данных 225 public void ejbLoad<) {} 226 } Рис, 11.15. Реализация OrderEJB удаленного интерфейса Order Помимо атрибутов ordcrDatc, orderlD и флага shipped, EJB-компонент Order содержит список объектов OrderProductModel. Отношение между заказом (компонент Order) и входящими в него товарами (компоненты Product) и их количеством представлено в базе данных в виде отношения «многие ко многим» (т.е. заказ может включать множество товаров, и один и тот же товар может фигурировать в нескольких заказов). В EJB-компоненте OrderProduct это отношение реализуется путем установки соответствия между идентификатором заказа orderlD и ISBN-кодам и ISBN книг, входящих в заказ. Для каждого заказа Order в таблице OrderProduct имеются записи, содержащие ISBN-код и количество для каждой из
Практический пример корпоративного приложения. Бизнес-логика: часть 1 557 книг в заказе. Например, если покупатель заказывает один экземпляр книги Как программировать на Java и два экземпляра книги Технологии программирования на Java 2, в таблице OrderProduct будут иметься две записи. В каждой записи идентификатор orderlD будет одним и тем же, но в одной из них поле ISBN будет содержать ISBN-код книги Как программировать на Java, а поле quantity — значение 1, тогда как в другой записи поле ISBN будет содержать ISBN-код для книги Технологии программирования на Java 2, а поле quantity — значение 2. В строках 56-57 вызывается метод find By Order ID для получения записей таблицы OrderProduct для заказа. Метод findByOrderlD возвращает коллекцию удаленных ссылок на объекты OrderProduct. В строках 65-77 осуществляется обход коллекции удаленных ссылок на объекты OrderProduct с использованием итератора (интерфейс Iterator), а в строках 71-72 извлекается объект OrderProduct- Model для каждой записи в таблице OrderProduct. В строке 76 каждый из объектов OrderProductModel добавляется в коллекцию. В строке 80 коллекция объектов OrderProductModel добавляется в объект OrderModel, а в строке 89 возвращается вновь созданный объект OrderModel. Метод set Shipped (строки 94—97) принимает булевый (boolean) аргумент и обновляет флаг shipped EJ В-компонента Order. Приложение, отслеживающее поступившее заказы, может использовать метод setShipped для обновления статуса заказа при доставке заказа со склада. Метод isShipped (строки 100—103) возвращает текущее значение элемента данных shipped, указывающее, был ли заказ доставлен со склада. Метод ejbCreate (строки 106-187) создает EJB-компонент Order с использованием данных из указанного объекта OrderModel и значения userlD. Каждый заказ (компонент Order) имеет соответствующий идентификатор orderlD, который служит в качестве первичного ключа в таблице базы данных EJB-компонента Order. Метод getNextlD интерфейса SequenceFactory генерирует уникальный идентификатор заказа orderlD {строка 128). В строках 132-133 осуществляется заполнение EJB-компонента Order данными из объекта OrderModel. Объект OrderModel также предоставляет товары Product и их количества в заказе Order в виде коллекции объектов OrderProductModel. В строках 152-162 осуществляется обработка коллекции объектов OrderProductModel, и создаются записи OrderProduct для каждого из них с использованием метода create интерфейса OrderProduct Home (строка 161). Каждый заказ имеет соответствующего покупателя, сделавшего этот заказ. Это соответствие представляет собой отношением «один ко многим», поскольку один покупатель может сделать несколько заказов, но заказ может ассоциироваться лишь с одним покупателем. В строках 173-174 извлекается EJB-компонент Customer для данного идентификатора userlD. Метод getPrimaryRey EJB-компонента Customer извлекает идентификатор покупателя customer ID, В строке 176 задается идентификатор покупателя customerlD для заказа. 11.5.3. Собственный интерфейс OrderHome Интерфейс OrderHome (рис. 11.16) создает экземпляры EJB-компонента Order и находит имеющиеся заказы. Метод create (строки 18-19) соответствует методу ejbCreate из листинга, представленного на рис. 11.15, и создает новые компоненты Order с использованием модели OrderModel и идентификатора userlD. Метод findByPrimaryKey (строки 22-23) находит имеющиеся заказы по их идентификаторам orderlD. Метод findByCustomcrlD (строки 26-27) извлекает коллекцию EJB-компонентов Order для данного покупателя Customer.
558 Глава 11 1 // OrderHome.java 2 // OrderHome - собственный интерфейс компонента-сущности EJB Order. Ъpackage com.de itel.advjhtpl.books tore.ej Ь; 4 5 // Набор базовых пакетов Java 6 import j ava.util.*; 7 import java.rmi.ВетоteException; 8 9 // Пакеты расширений Java 10 import javax.ejb.*; 11 12 // Пакеты Deitel 13 import coa.deitel.advjbtpl.bookstore.model,*; 14 15 public interface OrderHome extends EJBHome { 16 17 // создание компонента Order с использованием заданного объекта OrderModel и идентификатора userlD 18 public Order create( OrderModel orderModel, String userlD ) 19 throws RemoteSxception, CreateException; 20 21 // поиск заказа по его идентификатору orderlD 22 public Order findByPrimaryKey{ Integer orderID ) 23 throws RemoteException, FinderException; 24 25 // поиск заказов для покупателя с заданным идентификатором CuatomerlD 26 public Collection findByCustomerlD( Integer customerID ) 27 throws RemoteException, FinderException; 28 I Рис. 11.16. Интерфейс OrderHome для поиска и создания экземпляров EJB-компонента Order 11.5.4. Класс OrderModel Класс OrderModel (рис. 11.17) инкапсулирует информацию из EJB-компонента Order в сериализуемый (Serializable) объект, пригодный для доставки через RMI-H0P. Класс OrderModel имеет частные (private) элементы данных (строки 19-22) с соответствующими методами set и get (строки 31-102) для каждого элемента данных EJB-компонента Order. Класс OrderModel также содержит коллекцию объектов OrderProductModel для отслеживания товаров в заказе. Класс OrderModel реализует интерфейс XMLGenerator и метод getXML, способствующий формированию XML-представления заказа (строки 105-166). 1// OrderModel. java 2 // Класс OrderModel представляет заказ и содержит 3 // идентификатор заказа, дату, общую стоимость заказа, 4 // а также булево значение, указывайте, был пи заказ доставлен. 5 package com.deitel,advjhtpl.bookstore.model; 6 7 // Набор базовых пакетов Java 8 import java.io.*; Э import java.util.*; 10 import java.text.*;
Практический пример корпоративного приложения. Бизнес-логика: часть 1 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 // Пакеты сторонних поставщиков import org.w3c.dora.*; public class OrderModel implemen'ts Serializable, XKLGenerator { // свойства объекта OrderModel private Integer orderlD; private Date orderDate; private boolean shipped,- private Collection orderProductModels; // формирование пустого объекта OrderModel public OrderModel() orderProductModels = new ArrayListO. // задание идентификатора заказа public void setOrderlD( Integer id ) orderlD = id; // получение идентификатора заказа public Integer getOrderID() return orderlD; // задание даты заказа public void setOrderDate( Date date } orderDate = date; // получение даты заказа public Date get0rderDate<) return orderDate; // получение общей стоимости заказа public double getTotalCostO { double total =0.0; Iterator iterator = orderProductModels.iterator(); // подсчет общей стоимости заказа while f iterator.hasNextO ) { // получение следующего товара в магазинной тележке OrderProductModel OrderProductModel = ( OrderProductModel ) iterator.next();
560 67 6 В ProductModel productModel = 69 orderProductModel.getProductModel(); 70 71 // добавление стоимости данного товара к общей стоимости 72 total += С productModel.getSriceО * 73 orderProductModel.getQuantity() ); 74 } 75 76 return total; 77 } 78 79 // установка флага доставки 80 public void setshippedt boolean orderShipped ) 81 { 82 shipped = orderShipped; 83 } 84 85 // получение флага доставки 86 public boolean getShipped(> 87 { 88 return shipped; 89 } 90 91 // задание списка объектов OrderProductModel 92 public void setOrderProductModels( Collection models ) 93 { 94 orderProductModels = models; 95 } 96 97 // получение списка объектов OrderProductModel 98 public Collection getOrderProductModels() 99 { 100 return Collections.unmodifiableCollection( 101 orderProductModels ) ,- 102 } 103 104 // получение XML-представления заказа 105 public Element getXML( Document document. ) 106 { 107 // создание элемента order 108 Element order = document.createElement( "order" ); 109 110 // создание элемента orderID 111 Element temp = document.createElement( "orderID" ); 112 temp.appendchild( document.createTextNode( 113 String.valueOf( getOrderID() ) ) ); 114 order.appendChild{ temp ); 115 116 // получение формата OateFormat для записи даюшх в XML-документ 117 DateFormat formatter = DateFormat.getDateTimeInstance( 118 DateFormat.DEFAULT, DateFormat.MEDIOM, Locale.US J; 119 120 // создание элемента orderDate 121 temp = document.createElement( "orderDate" }; 122 temp,appendChild( document.createTextNode(
Практический пример корпоративного приложения. Бизнес-логика: часть 1 561 123 formatter.format( getOrderDate() ) ) ); 124 order.appendChild{ temp ); 125 126 NumbexFormat costFormatter = 127 NumberFormat.getCurrencytnstancet Locale.US ); 128 129 // создание элемента totalCost 130 temp = document.createElement{ "totalCost" ); 131 temp.appendChild( document.createTextNode( 132 costFormatter.format{ getTotalCost() ) ) ); 133 order.appendChild( temp ); 134 135 // создание элемента shipped 136 temp = document.createElement( "shipped" ); 137 138 if ( getShippedO ) 139 temp.appendChild( 140 document.createTextNode( "yes" ) ); 141 else 142 temp.appendChild( 143 document.createTextNode[ "no" ) }; 144 145 order . appendChild ( temp ) ,- 146 147 // создание элемента oirderProducts 148 Element orderProducts = 149 document.createElement( "orderProducts" ); 150 151 Iterator iterator = getOrdetProductModels().iterator(); 152 153 // добавление элемента orderProduct для каждого объекта OrderProduct 154 while ( iterator.hasNextО ) { 155 OrderProductModel orderProductModel = 156 ( OrderProductModel } iterator.next(); 157 158 orderProducts.appendChild( 159 orderProductModel.getXML( document ) ); 160 } 161 162 order.appendChild( orderProducts ); 163 164 return order; 165 166 ) // конец метода getXML 167 1 Рис. 11.17. Класс OrderModel для серизлиэации данных EJВ-компонента Order В таблицах на рис. 11.18, 11.19 и 11.20 представлены параметры развертывания для EJB-сущности Order. В дополнение к приведенным здесь параметрам следует установить для типа транзакции Transaction Type значение Required для всех бизнес-методов.
Глава 11 'вертывания для EJB-компонента Order :s ■me ■» Entity com,deitel.advjhtpl,bookstore,ejb. com.deitel.advjhtpl.bookstore.ejb. com,deitel.advjhtpl.bookstore.ejb. OrderEJB OrderHome Order ы оэзвеотыеания для EJB-компонента Order ыми и развертыванием для EJB-компонента Order '■ jry dse : ..■t wit ГОЛИЦЫ .merit ^|Я таблицы cement Container-Managed Persistence Java.lang,Integer orderID j dbc/Book з tore SELECT orderlD FROM CustomerOrders WHERE CustomerID=?l UPDATE CustomerOrders SET customerID=?, orderData=?, orderID, shipped»?, Where orderID=? INSERT INTO CustomerOrders (customerID, orderData, orderlD, shipped) VALUES (?, ?, ?, ?) DELETE FROM Сиз tore rOrdars WHERE ordexXD=? SELECT orderlD FROM CustomerOrders WHERE orderID=? SELECT customerlD, orderData, orderlD, shipped FROM CustomerOrders WHERE orderID=? CREATE TABLE CustomerOrders (customerlD INTEGER, orderData VARCHAR (255), orderlD INTEGER, shipped BOOLEAN NOT NULL, CONSTRAINT pk_CustomerOrders PRIMARY KEY (orderlD) } DROP TABLE CustomerOrders u, управления данными и развертыванием для EJB-компонента Order ->мпоненты для компонента Order jded Name .нтерфейс Home герфейс Remote ejb/Product Entity com,deitel■advjhtpl.bookstore.ejb.ProductHome com.deitel■advjhtpl.bookstore.ejb.Product
Практический пример корпоративного приложения. Бизнес-логика: часть 1 563 Ссылки на EJB-компоненты для компонента Order Имя JNDI Name Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name Product ej b/SequenceFactory Entity com.deitel.advjhtpl.bookstore,ejb.Sequence- FactoryHome com.deitel.advjhtpl.bookstore.ejb.Sequence- Factory SequenceFactory ejb/Customer Entity com,deitel.advjhtpl.bookstore.ejb.CuatomerHome com.deitel.advjhtpl.bookstore.ejb.Customer Customer ejb/OrderFroduct Entity com.deitel.advjhtpl.bookstore.ejb.OrderProduct- Home com.deitel.advjhtpl.bookstore.ejb.OrderProduct OrderProduct Рис. 11.20. Ссылки на EJB-компоненты для компонента Order 11.6. Реализация EJB-компонента OrderProduct Компонент-сущность BJB OrderProduct представляет отношение «многие ко многим» между заказами и товарами. Каждый EJB-компонент OrderProduct имеет атрибуты orderlD, ISBN и quantity и представляет один элемент заказа. 11.6,1. Удаленный интерфейс OrderProduct Удаленный интерфейс OrderProduct {рис. 11.21) определяет бизнес-методы для EJB-компонента OrderProduct. EJB-компонент OrderProduct устанавливает соответствие между ISBN-кодом товара и идентификатором заказа, а также количеством заказываемого товара. Метод getOrderProductModel (строки 18—19) возвращает объект OrderProductModel, содержащий информацию из записи таблицы OrderProduct. 1 // OrderProduct.Java 2 // OrderProduct - удаленный интерфейс для EJB-сущности 3 // OrderProduct. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 7 import Java.rmi.RemoteException; a 9 // Пакеты расширений Java
564 Глава 11 10 import javax.ejb.*; 11 12 // Пакеты Deitel 13 import com.deltel.advjhtpl.bookstore.model.*; 14 15 public interface OrderProduct extends EJBObjeet { 16 17 // получение сведений о заказе в виде объекта OrderProductModel 18 public OrderProductModel getOrderProductModel0 19 throws RemoteException; 20 } Рис. 11.21. Удаленный интерфейс OrderProduct для изменения информации, содержащейся в экземплярах EJ В-компонента OrderProduct 11.6.2. Реализация OrderProductEJB удаленного интерфейса OrderProduct Реализация OrderProductEJB удаленного интерфейса OrderProduct (рис. 11.22) объявляет поля ISBN, orderlD и quantity (строки 22-24), управляемые контейнером. Метод getOrderProductModel (строки 27-69) возвращает информацию из записи таблицы OrderProduct в виде объекта OrderProductModel. Метод setOrder- ProductModel (строки 75-77) задает данные для записи таблицы OrderProduct с использованием данных, извлеченных из параметра OrderProductModel. 1 // OrderProductEJB.Java 2 // EJB-сущностъ OrderProductEJB устанавливает 3 // соответствие между товаром и заказом м содержит 4 // информацию о количестве данного товара в захаае. 5 package com.deitel.advjhtpl.bookstore.ejb; 6 7 // Набор базовых пакетов Java 8 import java.rmi.RemoteException; 9 10 // Пакеты расширений Java 11 import javax.ejb.*; 12 import j avax.naming.*; 13 import javax.rmi.PortableRemoteObject; 14 15 // Пакеты Deitel 16 import com.deitel.advjhtpl.bookstore.model.*; 17 18 public class OrderProductEJB implements EntityBean { 19 private EntityContext entityContext; 20 21 // поля, управляемые контейнером 22 public String ISBN; 23 public Integer orderlD; 24 public int quantity; 25 26 // получение сведений о заказе в виде объекта OrderProductModel 27 public OrderProductModel getOrderProductModel() 28 throws EJBException 29 { 30 OrderProductModel model = new OrderProductModel();
Практический пример корпоративного приложения. Бизнес-логика: часть 1 565 31 32 // получение объекта ProduetModel для товара в указанном заказе 33 try { 34 Context initialContext = пей InitialContext(); 35 36 // поиск EJB-компокекта Product 37 Object object = initialContext.lookup( 38 "java:comp/env/ejb/Product" ); 39 40 // получение интерфейса ProductHome 41 ProductHome productHome = ( ProductHome > 42 PortableRemoteObject.narrow( object, 43 ProductHome.class ); 44 45 // нахождение книги по ее ISBN-коду 4 6 Product product = 47 productHome.findByPrimaryKey( ISBN ); 48 49 // получение объекта ProduetModel 50 ProduetModel productModel = 51 product.getProductModel(); 52 53 if установка объекта ProductModel s объекте OrderProductModel 54 model.setProductModel( productModel ); 55 56 ) // конец блока try 57 58 // обработка исключения при поиске EJB-компонента Product 59 catch ( Exception exception } { 60 throw лен EJBSxception( exception ); 61 } 62 63 // запись идентификатора заказа и количества товаров в объект OrderProductModel 64 model.setOrderlO( orderlD ); 65 model.setQuantity( quantity ); 66 67 return model; 68 69 } // конец метода getOrderProductModel 70 71 // задание сведений о заказе с использованием объекта OrderProductModel 72 private void setOrderProductModel( OrderProductModel model ) 73 ( 74 ISBN = model.getProductModel().getISBH(); 75 orderlD = model.getOrderlD(); 76 quantity = model.getQuantity(); 77 } 78 7 9 // создание объекта OrderProduct для данного объекта OrderProductModel 80 public OrderProductPK ejbCreate[ OrderProductModel model ) 81 { 82 setOrderProductModel( model ) ; 83 return null; 84 )
566 Глава 11 85 86 (( выполнение необходимых после создания обчехта действий 87 public void ejbPostCreate( OrderProductModel model ) {) 88 89 // задание контекста Entity/Context 90 public void setEntityContext( EntityContext context ) 91 { 92 eivtityContext = context; 93 } 94 95 // сброс контекста EntityContext 96 public void unsetEntityContextO 97 { 98 entityCoiitext = mill; 99 } 100 101 // активация экземпляра EJB-компонента OrderProduct 102 public void ejbActivate{) 103 { 104 OrderProductPK primaxyKey = 105 ( OrderProductPK ) entityContext.getPrimaryKey(); 106 107 ISBN = primaryKey.getlSBNO ; 108 orderlD = primaryKey,getOrderlD{); 109 } 110 111 // пассивация экземпляра EJB-компонента OrderProduct 112 public void ejbPassivate{) 113 { 114 ISBN = null; 115 orderlD = null; 116 l 117 118 // удаление экземпляра EJB-компонента OrderProduct 119 public void ejbReraove() {} 120 121 // сохранение данных EJB-компонента OrderProduct в базе данных 122 public void ejbStoreO il 123 124 // загрузка данных EJB-компонента OrderProduct из базы данных 125 public void ejbLoad() [} 126 } Рис. 11.22. Реализация OrderProduttEJB удаленного интерфейса OrderProduct Контейнер EJB вызывает метод ejbCreate (строки 80-84) для создания новых экземпляров EJB-компонента OrderProduct. Атрибуты ISBN, orderlD и quantity для записи таблицы OrderProduct предоставляются в параметре OrderProduct- Modcl. В строке 82 иы-зываетея метод setOrderProductModd для завершения создания записи OrderProduct. 11.6.3. Собственный интерфейс OrderProductHome Интерфейс OrderProductHome (рис. 11.23) предоставляет методы для создания новых экземпляров ЕЛВ-компонента OrderProduct и нахождения имеющихся записей в таблице OrderProduct. Метод create (строки 19-20) соответствует методу
Практический пример корпоративного приложения. Бизнес-логика: часть 1 567 ejb Create реализации OrderProductEJB интерфейса OrderProduct (рис. 11.22). Метод findByOrderlD (строки 23-24) обнаруживает все записи таблицы Order- Product с заданным идентификатором orderlD и возвращает коллекцию удаленных ссылок на объекты OrderProduct. Метод fin dBy Primary Key (строки 27-28) находит запись OrderProduct с использованием класса первичного ключа Order- ProductPK. 1 // OrderProductHome.java 2 // OrderProductHome - собственный интерфейс для EJB- 3 // сущности OrderProduct. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 7 import Java.util.Collection; 8 import java.rmi.RemoteException; 9 10 // Пакеты расширений Java 11 import javax.ejb.*; 12 13 // Пакеты Deitel 14 import com.deitel.advjhtpl.bookstore.model.*; 15 16 public interface OrderProductHome extends EJBHome { 17 18 // создание объекта OrderProduct с использованием заданного объекта OrderProductHodel 19 public OrderProduct create( OrderProductModel model ) 20 throws RemoteException, CreateException; 21 22 // нахождение объекта OrderProduct с заданным идентификатором orderlD 23 public Collection findByOrderlD{ Integer orderlD ) 24 throws RemoteException, FinderException; 25 26 // нахождение объекта OrderProduct для заданного первичного ключа 27 public OrderProduct findByPrimaryKey( OrderProductPK key ) 28 throws RemoteException, FinderException; 29 ) Рис. 11.23. Интерфейс OrderProductHome для нахождения и создания экземпляров EJB-компонента OrderProduct 11.6.4. Класс первичного ключа Order ProductPK Класс OrderProductPK (рис. 11.24) является классом первичного ключа для EJB-компонента OrderProduct. Поля (атрибуты) orderlD и ISBN необходимы для уникальной идентификации определенного экземпляра EJB-компонента Order- Product. Для компонента-сущности EJB, который имеет составной первичный ключ (т.е. первичный ключ, состоящий из нескольких полей), требуется нестандартный класс первичного ключа, определяемый разработчиком. Нестандартный класс первичного ключа должен содержать открытые (public) элементы данных для кансдого поля в составном первичном ключе. Класс первичного ключа OrderProductPK имеет два открытых элемента данных (строки 12-13): ISBN и orderlD, которые соответствуют двум полям первичного ключа EJB-компонента .OrderProductEJB (рис. 11.22). Нестандартный класс первичного ключа также дол-
568 Глава 11 жен переопределять методы hashCode и equals класса Object. Переопределенные реализации методов hashCode (строки 38-41) и equals (строки 49-57) дают возможность контейнеру EJB и клиентам EJB-компонента OrderProduct определять, являются ли два экземпляра EJB-компонента OrderProduct эквивалентными, путем сравнения их экземпляров класса первичного ключа. 1 2 3 4 5 6 7 В 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // OrderProduc tPK.java // OrderProductPK - класс первичного ключа для EJB- // сущности OrderProduct. package com.deitel.advjhtpl.bookstore.ejb; // Набор базовых пакетов Java import java.io.*; public class OrderProductPK irnplements Serializable { // поля в составе первичного клича public String ISBN; public Integer orderlD; // конструктор беэ аргументов public OrderProductPK() {} // построение объекта OrderProductPK с ISBN-кодом и идентификатором orderlD public OrderProductPK( String isbn. Integer id ) ISBN = isbn; orderlD = id; // получение ISBN-кода public String getISBN{) return ISBN; // получение идентификатора orderlD public Integer getOrderID() return orderlD; // вычисление хэш-кода для данного объекта public int hashCode() return getISBN().hashCode() * getOrderlD().intValue(>; // нестандартная реализация метода equals класса Object public boolean equals( Object object ) { // проверка, что объект является экземпляром класса OrderProductPK if ( object instanceof OrderProductPK ) { OrderProductPK otherKey =
Практический пример корпоративного приложения. Бизнес-логика: часть 1 569 49 ( OrderProductFK ) object; 50 51 /У сравнение ISBN-кодов и идентификаторов orderID 52 return ( getlSBN().equals( otherKey.getlSBN() ) 53 && getOrderlDO .equals ( otherKey.getOrderID() ) ); 54 } 55 56 return false; 57 } 58 } Рис. 11.24. Класс первичного ключа OrderProducPK для EJ В -компонента OrderProduct 11.6.5. Класс OrderProductModel Класс модели OrderProductModel (рис. 11.25) представляет запись в таблице OrderProduct. В строке 18 объявляется ссылка на объект ProductModeJ для товара (компонент Product), ассоциированного с записью таблицы OrderProduct. Атрибут quantity (строка 19) представляет количество товара в заказе. Заказ идентифицируется по его номеру orderlD (строка 20). Метод getXML (строки 59-76) генерирует код элемента XML, представляющего объект OrderProductModel. 1 // OrderProductModel.Java 2 // Класс OrderProductModel представляет товар и его 3 // количество в заказе или в магазинной тележке. 4 package com.deitel.advjhtpl.bookstore.model; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 Import java.util.*; 9 import java.text.*; 10 11 // Пакеты сторонних поставщиков 12 import org.w3c.dom.*; 13 14 public class OrderProductModel implements Serializable, 15 XMLGenerator ( 16 17 // свойства объекта OrderProductModel 18 private ProductModel productModel; 19 private int quantity; 20 private Integer orderID,- 21 22 // задание объекта ProductModel 23 public void setProduetModel( ProductModel model ) 24 { 25 productModel = model; 26 } 27 28 // получение обгьекта ProductModel 29 public ProductModel getProductModel() 30 { 31 return productModel; 32 } 33
570 Глава 11 34 35 36 37 36 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 ) // задание количества public void setQuantity( int productQuantity ) quantity = productQuantity; // получение количества public int getQuantity() return quantity; // задание идентификатора orderlD public void setOrderlD( Integer id ) orderID = id; // получение идентификатора orderID public Integer getOrderlDO return orderXD; // получение XML-представления объекта OrderProduct public Element getXML( Document document ) < // создание элемента orderProduct Element OrderProduct = document.createElenient< "orderProduct" ); // добавление элемента иэ объекта ProductModel orderProduct.appendChild ( getProductModel(),getXML( document ) ); // создание элемента quantity Element temp = document.createElement( "quantity" ) temp.appendChild( document.createTextNode( String.valueOf ( getQuantity() ) ) ); orderProduct.appendChild( temp ); return orderProduct; Рис. 11.25. Класс OrderProductModel для сериализации данных EJB-компонента OrderProduct В таблицах на рис. 11.26, 11.27 и 11.28 представлены параметры развертывания для EJB-сущеости OrderProduct. В дополнение к этим установкам следует задать для типа транзакции Transaction Type значение Required для всех бизнес-методов. Заметим, что поскольку EJB-сущность OrderProduct использует нестандартный (создаваемый разработчиком) класс первичного ключа, значение для имени поля первичного ключа Primary Key Field Каше (рис, 11,27) должно быть оставлено пустым.
Практический пример корпоративного приложения. Бизнес-логика: часть 1 571 ^ . ч - Основные параметры развертывания для EJB-компонента OrderProduct Тип Bean Type Класс Enterprise Bean Собственный интерфейс Home Interface Удаленный интерфейс Remote Interface Entity com.deitel.advjhtpl.bookstore.ejb.OrderProductEJB com.deitel.advjhtpl.bookstore.ejb.OrderProductHame com.deitel.advjhtpl.bookstore.ejb.OrderProduct Рис. 11.26. Основные параметры развертызания для EJB-компонента OrderProduct | Параметры управления данными и развертыванием для EJB-компонента OrderProduct Управление п е рс и стентн ость ю | Persistent Management Класс первичного ключа Primary Key Class Имя поля первичного ключа Primary Key Field Name Имя базы данных JNDI Database JNDI Name SQL-оператор SQL Statement метода findByCustomerlD SQL-оператор SQL Statement метода ejbStore SQL-оператор SQL Statement метода ejbCreate SQI -оператор SQL Statement метода ejb Remove SQL-оператор SQL Statement метода findByPrimaryKey SQL-оператор SQL Statement метода ejbLoad SQL-оператор создания таблицы Table Create SQL Statement SQL-оператор удаления таблицы Table Delete 5QL Statement Container-Managed Persistence com.deitel.advjhtpl.bookstore.ejb.OrderProductPK Нет jdbc/Bookstore SELECT ISBN, orderlD, FROM OrderProduct WHERE orderiD=?l UPDATE OrderProduct SET quantity=? WHERE ISBN=? AND orderID=? INSERT INTO OrderProduct (ISBN, orderlD, quantity) VALUES {?, ?, ?) DELETE FROM OrderProduct WHERE ISBN=? AND orderID='> SELECT ISBN, orderlD FROM OrderProduct WHERE ISBN=? AND orderID=? SELECT quantity FROM OrderProduct WHERE ISBN=? AND orderiD=? CREATE TABLE OrderProduct (ISBN VARCHAR (2 55), orderlD INTEGER, quantity INTEGER NOT NULL, CONSTRAINT pk_OrderProduct PRIMARY KEY (ISBN, orderlD) ) DROP TABLE OrderProduct Рис. 11.27. Параметры управления данными и развертыванием для EJB-компонента OrderProduct
572 Глава 11 Ссылки на EJВ-компоненты для компонента OrderProduct Кодовое имя Coded Name Тип Туре Собственный интерфейс Ноте Удаленный интеофейс Remote Имя JNDI Name ejb/Product Entity coin. deitel. advjhtpl. bookstore. e jb. ProductHome com.deitel.advjhtpl.bookstore.ejb.Product Product Рис, 11.28. Ссылки на EJ В-компоненты для компонента OrderProduct В этой главе была рассмотрена бизнес-логика управления магазинной тележкой (компонент ShoppingCart) покупателя и модель данных для описания товаров и заказов в нашем Internet-магазине. Мы также обсудили, каким образом можно использовать сериализуемые (Serializable) объекты для снижения объема сетевого трафика при коммуникационном взаимодействии с EJB-компонентами. В следующей главе будут рассмотрены EJB-компоненты для обслуживания покупателей в нашем Internet-магазине.
19 Практический пример корпоративного приложения. Бизнес-логика: часть 2 Цели • Познакомиться с моделью данных для обслуживания покупателей в приложении Deitel Bookstore. • Реализовать EJB-компонент для хранения информации об оплате и доставке товара. • Создать ЕЛЗ-компоненг для формирования первичных ключей. • Узнать о преимуществах, которые дает декларативная семантика транзакций. • Понять, каким образом осуществляется развертывание учебного приложения Deitel Bookstore. Лучшие инвестиции — это инвестиции в орудия вашего труда. Бенджамин Франклин Творчество состоит не « том, чтобы найти предмет, но в том, чтобы выявить и представить его лучшие качества. Джеймс Рассел Лоуэлл Ш1 Прогнозируемые события не требуют управления извне. Они управляют собой сами. Амелия Барр Как известно, ключом к смыслу предмета и явлений является их интерпретация. Джордж Элиот
574 Глава 12 12.1. Введение В этой главе будут рассмотрены EJB-компоненты с данными, предназначенные для обслуживания покупателей. Хранение информации о посетителях Internet-магазина может сделать процесс приобретения товаров более удобным, поскольку информация об оплате и доставке сохраняется на сервере. Отдел маркетинга Internet-магазина также может воспользоваться собранными данными для распространения рекламных материалов и анализа информации о демографическом составе покупателей. Будет также рассмотрен EJB-компонент с данными, который генерирует уникальные идентификаторы для EJB-компонентов Customer, Order и Address. Экземпляры этих ЕсШ-компонентов создаются при регистрации нового посетителя, а также при размещении покупателем новых заказов. Реляционные базы данных требуют применения уникальных первичных ключей для обеспечения ссылочной целостности и выполнения запросов. Мы предоставляем EJB-компонент Sequen- ceFaetory для генерирования этих уникальных идентификаторов, поскольку не все базы данных способны автоматически генерировать значения первичных ключей. Наконец, будут предоставлены инструкции по развертыванию учебного приложения Deitel Bookstore на эталонной реализации cepsepa приложений J2EE Sun Microsystems.
Практический пример корпоративного приложения. Бизнес-логика: часть 2 575 12.2. Реализация EJB-компонента Customer EJB-компонент с данными Customer представляет покупателя в главной базе данных. В следующих подразделах будет рассмотрена реализация EJB-компонента и соответствующий класс модели. 12.2.1. Удаленный интерфейс Customer Удаленный интерфейс EJB-компонента Customer (рис. 12,1) определяет методы бизнес-логики в EJB-компоненте. Метод getCustomerModel (строки 19—20) формирует сериализуемый (Serializable) объект класса CustomerModel, который содер жит подробную информацию о покупателе. Метод getOrderHistory (строки 23-24) возвращает коллекцию объектов OrderModel, которая содержит информацию о последних сделанных покупателем заказах. Метод getPasswordHint (строка 27) возвращает строку с подсказкой, помогающей покупателю вспомнить забытый пароль. 12.2.2. Реализация CustomerEJB удаленного интерфейса Customer Реализация CustomerEJB (рис. 12.2) удаленного интерфейса Customer (рис. 12.1) содержит переменные экземпляров для каждого из свойств EJB-компонента Customer (строки 25-36). Эти переменные экземпляров являются открытыми (public), чтобы контейнер EJB мог синхронизировать их значения со значениями из соответствующей таблицы базы даннь'х. 1 // Customer.Java 2 // Customer - удаленный интерфейс для компонента EJB Customer. 3 package com.deitel,advjhtpl.bookstore,ejb; 4 5 // Набор базовых библиотек Java 6 import Java.rmi.RemoteException; 7 import java.util.Collection; 8 9 // Стандартные расширение Java 10 import javax.ejb.*; 11 12 // Библиотеки Deitel 13 import com.deitel.advjhtpl.bookstore.model.*; 14 import com.deitel.advjhtpl.bookstore.exceptions.*; 15 16 public interface Customer extends EJBObject { 17 18 // получение сведений о покупателе в виде объекта CustomerModel 19 public CustomerModel getCustomerModel() 20 throws RemoteException; 21 22 // получение архива заказов для покупателя 23 public Collection getOrderHistory{) 24 throws RemoteException, NoOrderHistoryExeeption; 25 26 // получение подсказки пароля для покупателя 27 public String getPasswordHint() throws RemoteException,- 28} Рис. 12.1. Удаленный интерфейс Customer для изменения информации о покупателе, получения архива заказов и выдачи подсказки пароля
1 // CustomerEJB.Java 2 // EJB-компонент с данными Customer представляем покупателя и 3 // содержит имя пользователя, пароль, адрес доставки счета, 4 // адрес доставки товара и информацию о кредитной карте. 5 package com.deitel.advjhtpl.books tore. e jb; 6 7 // Набор базовых пакетов Java fl import j ava.util.*; Э import java.rmi.RemoteException; 10 11 // Пакеты расширении Java 12 import javax.ejb.*; 13 import javax.naming.*; 14 import javax.rmi.PortableRemoteObject; 15 16 // Пакеты Deitel 17 import com.deitel.advjhtpl.bookstore.model.*; 18 import com.deitel.advjhtpl.bookstore.exceptions,*; 19 20 public class CustomerEJB implements EntityBean { 21 private EntityContext entityContext; 22 private InitialContext initialContext; 23 24 // поля, управляемое контейнером 25 public Integer customerID; 26 public String userlD; 27 public String password; 28 public String passwordHint; 29 public String firstName; 30 public String lastSame; 31 public Integer billingAddressID; 32 public Integer shippingAddresslD; 33 34 public String creditCardHame; 35 public String creditCardNumber; 36 public String creditCardExpirationDate; 37 Зв // получение о&ъехта CustomsrKodel 39 public CustomerModel getCustomerModel() throws EJBException 40 { 41 // построение нового объекта Си stonier Model 42 CustomerModel customer = new CustomerHodel(); 43 44 // заполнение объекта CustomerModel данными для этого покупателя 45 customer.setCusfcomerlD{ customerlD ); 46 customer.setUserXD( userlD ); 47 customer.setPassword( password ); 48 customer.setPasswordHint( passwordHint ); 49 customer.setFirstName( firstName ); 50 customer.setLastName( lastName ); 51 52 // использование EJB-компонентa Address для получения 53 // экземпляров компонента Address, содержащих адрес доставки счета и адрес доставки товара 54 try { 55 initialContext = new InitialContext();
Практический пример корпоративного приложения. Бизнес-логика: часть 2 577 56 57 Object object = initialContext.lookup( 58 "java:corop/env/ejb/Address" ); 59 60 AddressHome addressHome = ( AddressHome ) 62 PortableRemoteObject.narrow( object, 62 AddressHome.class ); 63 64 // получение удаленной сошки на адрес доставки счета 65 Address billingAddress = 66 addressHome.findByPrimaryKey( billingAddressid ); 67 68 // добавление объекта AddressModel в объект CustomerModel 69 customer.setBillingAddress( 70 billingAddress.getAddressModel() ); 71 72 // получение удаленной ссылки на адрес доставки товара 73 Address shippingAddress = 74 addressHome,findByPrimaryKey( shippingAddressID); 75 7 6 // добавление объекта AddressModel в объект CustomerModel 77 customer.setShippingAddress{ 78 shippingAddress.getAddressModel() ); 79 80 } // конец блока try 81 82 // обработка исключения при использовании EJB-компонента Address 83 catch { Exception exception ) { 84 throw new EJBException( exception ); 85 } 86 87 // задание информации о кредитной карте в объекте CustomerModel 88 customer.setCreditCardName( creditCardName ); 89 customer.setCreditCardNumber( creditCardNumber ); 90 customer.setCreditCardExpirationDate( 91 creditCardExpirationDate ); 92 93 return customer; 94 95 ] // конец метода getCustomerModel 96 97 // получение архива заказов покупателя 98 public Collection getOrderHistory() 99 throws NoOrderHIstoryException, EJBException 100 { 101 Collection history = new ArrayList {}; 102 103 // использование EJB-компонента Order для получении списка заказов покупателя 104 try t 105 initialContext = new InitialContext() ; 106 107 Object object = initialContext.lookup( 108 "java:comp/env/ejb/Order" ); 109
578 Глава 12 110 OrderHome orderHome = ( OrderHome ) 111 PortableRemoteObjeet.narrow( object, 11Z OrderHome.class ); 113 114 // нахождение заказов для данного покупателя 115 Collection orders = 116 orderHome.findByCustomerlD( customerlD ); 117 118 Iterator iterator = orders.itarator () ;, 119 120 // использование списка заказов для создания архива заказов 121 while ( iterator.hasNextO ) ( 122 Order order = ( Order ) PortableRemoteObject.narrow{ 123 iterator.next(), Order.class ); 124 125 // извлечение объекта OrderModel для заказа 126 OrderModel orderModel = order.getOrderModel()/ 127 128 // добавление каждого из объектов OrderModel в архив заказов 129 History.add{ orderModel ).; 130 J 131 132 } // конец блока try 133 134 // обработка исключения при поиске записей для заказов 135 catch ( FinderException f" inderException } { 136 throw new NoOrderHistoryException( "No order " + 137 "history found for the customer with userID " + 138 userlD ) ; 139 J 140 141 // обработка исключения яри вызове методов EJB-компонента Order 142 catch < Exception exception ) { 143 exception.printStackTraceО; 144 throw new EJBExcoption( exception ); 145 } 146 147 return history; 148 149 } // конец метода getOrderHistory 150 151 // получение подсказки пароля для данного покупателя 152 public String getPasswordHint() 1ЬЗ ( 154 return passwordHint; 155 ) 156 157 // задание сведений о покупателе с помощь» объекта CustomerModel 158 private void setCustomerModel( CustomerModel customer ) 159 { 160 // установка значений из CustomerModel для элементов данных Customer 161 userlD = customer.getCserlDO ; 162 password = customer.getPasswordf); 163 passwordHint = customer .getPasswordHint (} ,-
Практический пример корпоративного приложения. Бизнес-логика: часть 2 579 164 firstName = customer.getFirstName(); 165 last-Name = customer.getLastName(); 166 167 billingAddressID = 168 customer.getBillingAddress().getAddressID(); 169 170 shippingAddressID = 171 customer.getShippingAddress().getAddressID(); 172 173 creditCardName = customer.getCreditCardName(); 174 creditCardNumber = customer.getCraditCardKumber(j; 175 176 creditCardExpirationDate = 177 customer.getCreditCardExpirationDate(); 178 179 } // конец метода setCustomerModel 180 181 // создание EJB-компонента Customer с использованием заданного объекта CustomerModel 182 public Integer ejbCreate( CustomerModel customerModel ) 183 throws CreateException 184 { 185 // извлечение уникального значения для первичного ключа 136 //с использованием EJB-компонента SequenceFactory 187 try { 188 initialContext = new InitialContext(); 189 190 // поиск EJB-компонента SequenceFactory 191 Object object = initialContext.lookup( 192 "Java:comp/env/ejb/SequenceFactory" ); 193 194 SequenceFactoryHome sequenceFactoryHome = 195 ( SequenceFactoryHome ) PortableRemoteObject.narrow( 196 object, SequenceFactoryHome.class ); 197 198 // нахождение последовательности для EJB-компонента Customer 199 SequenceFactory SequenceFactory = 200 sequenceFactoryHome.findByPrimaryKey( "Customer" ); 201 202 // извлечение следующего имеющегося идентификатора customerID 203 cuatomerlD = SequenceFactory.getNextID(); 204 205 // создание EJB-компонентов Address для адресов 206 // доставки счета и товара 207 object = initialContext.lookup( 208 "java:comp/env/ejb/Address" }; 209 210 AddressHome addressHome = ( AddressHome } 211 PortableRemoteObject.narrow( object, 212 AddressHome. class ) ,- 213 214 // получение адреса доставки счета для покупателя 215 AddressModel billingAddressModel = 216 customerModel.getBillingAddress();
580 Глава 12 217 218 // создание ЕJB-компонента Address для адреса доставки счета 219 Address billingAddress = 220 addressHome.create( billingAddressHbdel ); 221 222 // задание идентификатора addressID в объекте AddressModel 223 billingAddressModel.setAddressIDf ( Integer ) 224 billingAddress.getPrimaryKey() ); 225 226 // получение адреса доставки topара для покупателя 227 AddressModel shippingAddressModel = 228 с u s tome rMode1.getShippingAddress (>; 229 230 // создание EJB-компонента Address для адреса доставки товара 2 31 Address shippingAddress = 232 addressHome.create( ShippingAddressModel ) ; 233 234 // задание идентификатора addressID s объекте AddressModel 235 ShippingAddressModel.setAddzressID( ( Integer ) 236 shippingAddress.getPrimaryKeyО ); 237 238 // использование объекта CustomerModel для установки данных для нового покупателя 239 setCustomerModel{ CustomerModel ); 240 241 } // конец блока try 242 243 // обработка исключений при поиске, нахождении и использовании EJB-компонентов 244 catch ( Exception exception ) { 245 throw new CreateException( exception.getMessage{) ); 246 } 247 246 II контейнер EJB будет возвращать удаленную ссылку 249 return null; 250 2 51 } // конец метода ejbCreate 252 253 // выполнение необходимых после создания объекта действий 254 public void ejbPostCreate{ CustomerModel customer ) {} 255 256 // установка контекста EntityContext 257 public void setEntityContext( EntityContext context ) 258 { 259 entityContext = context; 260 } 261 2 62 // сброс контекста EntityContext 263 public void unsetEntityContextO 264 { 265 entityContext = null; 266 J 267 268 // активация экземпляра EJB-компонента Customer 269 public void ejbActivate() 270 {
Практический пример корпоративного приложения. Бизнес-логика: часть 2 581 271 customer-ID = { Integer ) entityContext. getPrimaryKey () ; 272 ) 273 274 // пассивация экземпляра EJB-компонента Customer 275 public void ejbPassivate() 276 { 277 customerlD = null; 278 } 279 280 // удаление экземпляра EJB-компонента Customer 281 public void ejbRercove () {} 282 283 // сохранение данных EJB-компонента Customer в базе данных 284 public void ejbStoret) {} 285 28 6 // загрузка данных EJB-компонента Customer из баз» данных 287 public void ejbLoadO {} 288 ) Рис. 12.2. Реализация CustomerEJВ удаленного интерфейса Customer Метод getCustomerModel (строки 39-95) формирует объект CustomerModel, элементы данных которого содержат значения из компонента CustomerEJB. EJB- компоненты Address хранят информацию об адресах покупателей. EJB-компонент Customer хранит идентификаторы addressID для каждого из объектов Address (адрес доставки счета и адрес доставки товара). Метод getCustomerModel использует интерфейс AddressHoine для получения EJB-компонентов Address для покупателя (строки 65-78). Для каждого EJB-компонента Address метод getCustomerModel получает объект AddressModel и добавляет его в объект CustomerModel. Метод getOrderHistory (строки 98-149) формирует коллекцию, которая содержит объект OrderModel для каждого заказа, размещенного покупателем. Метод findByCustomerlD интерфейса OrderHome возвращает коллекцию заказов (EJB- комноненты Order) для данного покупателя (EJB-компонент Customer) (строка 116). В строках 115-130 извлекается коллекция EJB-компонентов Order и формируется коллекция объектов OrderModel для представления архива предыдущих заказов покупателя. Метод setCustomerModel (строки 158-179) является вспомогательным методом для метода ejbCreate (строки 182-251). Метод setCustomerModel модифицирует информацию, содержащуюся в EJB-компоненте CustomerEJB, используя данные из аргумента CustomerModel. В строках 161-177 извлекаются элементы данных объекта CustomerModel и устанавливаются значения элементов данных EJB-компонента CustomerEJB. Контейнер EJB вызывает метод ejbCreate (строки 182-251) при создании нового экземпляра EJB-компонента Customer. Метод ejbCreate принимает в качестве аргумента объект CustomerModel и использует его для вызова метода setCustomerModel, чтобы осуществить инициализацию элементов данных EJB-компонента CustomerEJB (строка 239). Метод ejbCreate использует метод getNextlD класса Sequenceractory для генерирования уникального идентификатора customerlD для нового покупателя (строка 203). Каждый покупатель имеет адрес доставки счета и адрес доставки товара, которые хранятся в EJB-компоненте Address. В строках 215-236 эти EJB-компоненты Address создаются с использованием данных из объектов AddressModel, задаваемых в аргументе CustomerModel.
582 Глава 12 12.2.3. Собственный интерфейс CustomerHome Интерфейс CustomerHome (рис. 12.3) предоставляет методы create для создания новых EJB-компонентов Customer для покупателей и методы поиска для нахождения имеющихся покупателей. Метод create (строки 17-18) соответствует методу ejbCreate в реализации CustomcrEJB (рис. 12.2). Метод fmdBy Login (строки 21-22) возвращает удаленную ссылку на EJB-компонент Customer для покупателя с заданным идентификатором userlD и паролем password- Этот метод осуществляет аутентификацию покупателей при входе их в Internet-магазин. Метод findByUseiTD (строки 25-26) возвращает EJB-компонент Customer для покупателя с заданным идентификатором пользователя userlD. Метод findByPrimaryKey (строки 29-30) возвращает EJB-компонент Customer для покупателя с заданным идентификатором customerlD. Контейнер EJB предоставляет реализации этих методов, используя RQL-запросы, задаваемые разработчиком при развертывании приложения. 1 // CustomerHome.Java 2 // CustomerHome - собственный интерфейс для ВJB-компонента с данными Customer. 3 package com.deitel.advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 import Java.rmi .RemoteException; 7 8 // Пакеты расширений Java 9 import javax.ejb.*; 10 11 // Лахеты Deitel 12 import com.deitel.advjhtpl.bookstore.model.*; 13 14 public interface CustomerHome extends EJBHome { 15 16 // создание EJB-компонента Customer с использованием заданного объекта CustomerModel 17 public Customer create ( CustomerModel customer-Model ) 18 throws RemoteException, CreateException; 19 20 // нахождение покупателя с указанным идентификатором и паролем 21 public Customer findByLogin( String userID, String password ) 22 throws RemoteException, FinderException; 23 24 // нахождение покупателя с указанным идентификатором userlD 25 public Customer findByCserlD( String userlD ) 26 throws RemoteException, FinderException; 27 28 // нахождение покупателя с указанным идентификатором customerID 29 public Customer findByPrimaryKey( Integer customerID ) 30 throws RemoteException, FinderException; 31 } Рис. 12.3. Интерфейс CustomerHome для создания и нахождения экземпляров EJ8-компонента Customer
Практический пример корпоративного приложения. Бизнес-логика: часть 2 583 12.2.4. Класс CustomerModel CustomerModel (рис. 12.4) — это класс модели для EJB-компонента Customer. Класс CustomerModel содержит частные элементы данных (строки 17-29) для каждого открытого элемента данных в реализации CustomerEJB (рис. 12.2). Класс CustomerModel хранит ссылки на объекты AddrcssModel, содержащие адрес доставки счета и адрес доставки товара для покупателей (строки 24—25). Класс CustomerModel определяет методы set и get для каждого из своих свойств, реализует интерфейс XMLGenerator и предоставляет метод getXML (строки 168-233) для генерирования XML-кода элемента, описывающего покупателя. 1 // CustomerModel.Java 2 // Объект CustomerModel представляет посетителя магазина Deitel 3 // Bookstore и содержит адреса доставки счета и товара, а также информацию о кредитной карте. 4 package com.deitel.advjhtpl.bookstore.model,- 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java.util.*; 9 10 // Пакеты сторонних поставщиков 11 import org.w3c.dora.*; 12 13 public class CustomerModel implements Serializable, 14 XMLGenerator { 15 16 // свойства объекта CustomerModel 17 private Integer cxistomerlD; 18 private String userlD; 19 private String password; 20 private String passwordHint; 21 private String firstName; 22 private String lastName; 23 24 private AddressModel billingAddress; 25 private AddressModel shippingAddress; 26 27 private String creditCardName; 28 private String creditCardNumber; 29 private String creditCardExpirationDate; 30 31 // создание пустого объекта CustomerModel 32 public CustomerModel {) {} 33 34 // задание идентификатора покупателя 35 public void setCustomerlD( Integer id ) 36 { 37 customerID = id; 38 } 39 40 // получение идентификатора покупателя 41 public Integer getCustomerlD() 42 1 43 return customerlD; 44 )
584 Глава 12 // задание идентификатора пользователя public void setOaerlD( String id > userlD = id; // получение идентификатора пользователя public String getUserlDO return userlD; // задание пароля public void setPassword( String customerPassword ) password = customerPassword; // получение пароля public String getPassword{) return password; // задание подсказки для пароля public void setPasswordHint( String hint ) passwordHint = hint; // получение подсказки для пароля public String getPasswordHintO return passwordHint; // задание имени public void satFirstName{ String name ) firstName = name; // получение имени public String getFirstName() return firstName; // зедание фамилии public void setLastNarae( String name ) lastHame = name; // получение фамилии
Практический пример корпоративного приложения. Бизнес-логика: часть 2 public String getLastName() return lastName; // задание адреса доставки счета public void setBillingAddress( AddressModel address } billingAddress = address; // получение адреса доставки счета public AddressModel getBillingAddress() return billingAddress; // задание адреса доставки товара public void setShippingAddress( AddressModel address ) shippingAddress = address; // получение адреса доставки товара public AddressModel getShippingAddress() return shippingAddress; // задание названия кредитной карты public void setCreditCardName( String name ) creditCardName = name; // получение названия кредитной карты public String getCreditCardName() return creditCardName; // задание номера кредиткой карты public void setCreditCardNumber( String number ) creditCardNumber = number; // получение номера кредитной карты public String getCreditCardMumberO return creditCardNumber; // задание даты истечения срока действия кредиткой карты public void setCreditCardExpirationDate( String date ) t
586 Глав; 157 creditCardExpirationDate = date; 158 } 159 160 // получение даты истечения срока действия кредитной карты 161 public String getCreditCardExpirationDate() 162 ( 163 return creditCardExpirationDate; 164 } 165 166 // формирование XML-представления данных для покупателя 167 //с включением всех открытых свойств в виде узлов 168 public Element getXML( Document document ) 169 { 170 // создание элемента customer 171 Element customer = 172 document, createElenient( "customer" ) ; 173 174 // создание элемента customerID 175 Element temp = document.createElement( "customerlD" ); 176 temp.appendChild( document.createTextNode{ 177 String.valueOf( getCustomerlD() ) ) ); 178 customer.appendchild( temp ); 179 160 // создание элемента userlD 181 temp = document.createElement( "userlD" ); 182 temp.appendChild{ 183 document.createTextNode( getUserlDQ ) ); \Bi customer.appendchild{ temp ); 185 186 // создание элемента firstName 167 temp = document. createElement ( "irstName" },- 186 temp.appendChild{ document.createTextNode{ 189 getFirstName() ) ); 190 customer.appendchild{ temp ); 191 192 // создание элемента lastName 193 temp = document.createElement( "lasLHame" ); 194 temp.appendchild( document,createTextNode( 195 getLastName() ) ); 196 customer.appendchild( temp ); 197 198 // создание элемента billingAddress 199 temp = document.createElementt "billingAddress" }; 200 temp,appendchild( billingAddress.getXML( document ) ); 201 202 // создание элемента shippingAddress 203 temp = document.createElement( "shippingAddress" ); 204 temp.appendchild( shippingAddress.getXML( document ) ); 205 206 // создание элемента creditCardName 207 temp = document.createElement( "creditCardName" ); 208 temp.appendchild( document.createTextNode( 209 getCreditCardName0 ) ); 210 customer.appendchild( temp }; 211 212 // создание элемента creditCardNumber
Практический пример корпоративного приложения. Бизнес-логика: часть 2 587 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 } temp = document.createElement( "creditCardNumber" ) temp.appendChild( document.createTextNode( getCreditCardNumber{) ) ) ; customer.appendChild{ temp ); // создание элемента creditCardExpirationDate temp = document.createElement( "creditCardExpirationDate" ); temp.appendChild{ document.createTextNode( getCreditCardExpirationDate() ) ); customer.appendChild( temp ); // создание элемента passwordHint temp = document.createElement( "passwordHint" ); temp.appendChild( document. MeateTextNode( getPasswordHint() ) ); customer.appendChild( temp ); return customer; } II конец метода getXML Рис. 12.4. Класс CustomerModel для сериализации данных о покупателе В таблицах на рис. 12.5, 12.6 и 12.7 представлен перечень параметров развертывания для компонента-сущности EJB Customer. В дополнение к представленным установкам, следует задать для типа транзакции Transaction Type значение Required для всех бизнес-методов. Основные параметры развертывания для EJB-компонента Customer Тип Sean Type Класс Enterprise Bean Class Собственный интерфейс Home Interface i Удаленный интерфейс Remote interface Entity com.deitel.advjhtpl.bookstore.ejb.CustomerEJB com.deitel.advjhtpl.bookstore.ejb.CustomerHome com.deitel.advjhtpl.bookstore.ejb.Customer Рис. 12.5. Основные параметры развертывания для EJB-компонента Customer Параметры управления данными и развертыванием для EJB-компонента Customer Управление персистентностью Persistent Management Container-Managed Persistence Класс первичного ключа Primary Key Class Имя поля первичного ключа Primary Key Field Name Имя базы данных JNDI Database JNDI Name SQL-оператор SQL Statement метода flndByUseriD java.lang.Integer customerlD jdbc/BooJcstore SELECT customerlD FROM Customer WHERE userlD = ?1
;88 Глава 12 Параметры управления данными и развертыванием для EJB-кОМПОнента Customer SQL-оператор SQL Statement метода findByLogin SQL-оператор SQL Statement метода ejbStore SQL-оператор SQL Statement метода ejbCreate SQL-оператор SQL Statement метода ejbRemove SQL-оператор SQL Statement метода findByPrimaryKey SQL-оператор SQL Statement метода ejbLoad SQL-оператор создания таблицы Table Create SQL Statement SQL-оператор удаления -аблицы Table Delete SQL statement SELECT customerlD FROM Customer WHERE userlD = ?1 AHD password = ?2 UPDATE Customer SET billingAddressID = ?, creditCardExpirationData = ?, creditCardName = ?, creditCardNumber = ?, firstName = ?, lastName = ?, password = ?, passwordHint = ?, shippingAddressID = ?, userID = ? WHERE customerlD = ? INSERT INTO Customer { billingAddressID, creditCardExpirationDate, creditCardName, creditCardNumber, customerlD, firstName, lastName, password, passwordHint, shippingAddressID, userlD ) VALUES ( ?, ?, ?, ?, ■) -3 •> •> ■} ',') DELETE FROM WHERE customerlD = ? SELECT customerlD FROM Customer WHERE customerlD = ? SELECT billingAddressID, creditCardExpirationDate, creditCardName, creditCardNumber, firstName, lastName, password, passwordHint, shippingAddressID, usarlD FROM Customer WHERE customerlD = ? CREATE TABLE Customer ( billingAddressID INTEGER, creditCardExpirationDate VARCHARf 255 ), creditCardName VARCHARf 255 ), CreditCardNumber VARCHARf 255 ), customerlD INTEGER, firstName VARCHARf 2SS 1, LastMame VARCHARf 255 ), password VARCHARf 255 }, passwordHint VARCHARf 255 ), ShippingAddressID INTEGER, userlD VARCHARf 255 ), CONSTRAINT pk_Customer PRIMARY KEY ( customerlD ) } DROP TABLE Customer Чк. 12.6. Параметры управления данными \л развертыванием для EJB-компонента :Listomer - Ссылки на EJB-компоненты для компонента Customer Кодовое имя Coded Name Тип Туре Собивенный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name ejb/Order Entity com.deitel.advjhtpl,bookstore.ejb.OrderHome cam.deitel.advjhtpl.bookstore.ejb.Order Order
Практический пример корпоративного приложения. Бизнес-логика-, часть 2 589 Ссылки на EJB-компоненты для компонента Customer Кодовое имя Coded Name ~ил Туре Собственный интерфейс Ноше Удаленный интерфейс Remote Имя JNDi Name Кодовое имя Coded Name ~ип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name ejb/SequenceFactory Entity com.deitel.advjhtpl.bookstore.ejb.SequenceFactoryHotne com.deitel.advjhtpl.bookstore.ejb.SequenceFectory SequenceFactory ejb/Address Entity com.deitel.advjhtpl.bookstore.ejb.AddressHome com.deitel.advjhtpl.bookstore.ejb.Address Address Рис. 12.7. Ссылки на EJB-компоненты для компонента Customer 12.3. Реализация EJB-компонента Address Приложение сохраняет адрес доставки счета и адрес доставки товара для каждого покупателя. Каждый адрес содержит схожую по структуре информацию (улица, город, штат, почтовый индекс), поэтому абстракция этих двух видов адресов помещается в один EJB-компонент Address. Каждый EJB-коыпонент Customer хранит идентификатор адреса доставки счета и идентификатор адреса доставки товара. 12.3.1. Удаленный интерфейс Address Удаленный интерфейс Address (рис. 12.8) содержит методы set и get для обновления данных EJB-компонента Address и извлечения данных из него. Метод getAddressModel (строки 17-18) создает объект AddrcssModel, который содержит подробную информацию для конкретного EJB-компонента Address. 123.2. Реализация AddressEJB удаленного интерфейса Address Класс AddressEJB (рис. 12.9) представляет собой реализацию удаленного интерфейса Address и содержит открытые, управляемые контейнером элементы данных для имени и фамилии контактного лица в адресе (EJB-компоненте Address), а также название улицы, города, штата, почтовый индекс, страну и номер телефона (строки 24-33). 1 // Address.Java 2 // Address - удаленный интерфейс для EJB-компонента с данными Address. 3 package com.deitel.advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 impart Java.rmi.RemoteException;
590 Глава 12 7 8 // Пакеты расширений Java 9 import javax.ejb.*; 10 11 // Пакеты Deitel 12 import, com.deitel.advjhtpl.bookstore.model.*; 13 14 public interface Address extends EJBObject { 15 16 // получение сведений об адресе в виде объекта AddressModel 17 public AddressModel getAddressModel(} 18 throws RemoteException; 19 > Рис 12.8. Удаленный интерфейс Address для изменения информации в адресе 1 // AddressEJB.Java 2 // EJB-кошюкент с данными Address представляет адрес и 3 // содержит названия улицы, города, штата и почтовый индекс. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 7 import java.util.*; 8 import Java. ntii.RemoteException; 9 10 // Пакеты расширений Java 11 import javax.ejb.*,- 12 import javax.naming.*; 13 import javax.rmi.PortableRemotaObject; 14 15 // Пакеты Deitel 16 import com.deitel.advjhtpl.bookstore.model.*; 17 import con.deitel.advjhtpl.bookstore.exceptions.*; 18 19 public class AddressEJB implements EntityBean { 20 private EntityContext entityContext; 21 22 // поля, управляемые контейнером 23 public Integer addresSlD; 24 public String firstName; 25 public String lastName; 26 public String streetAddressLinel; 27 public String streetAddressLine2; 28 public String city; 29 public String state; 30 public String zipCode; 31 public String country; 32 public String phoneNumber; 33 34 // получение объекта AddressModel 35 public AddressModel getAddressModel() 36 { 37 // формирование нового объекта AddressModel 38 AddressModel address = new AddressModel()• 39
Практический пример корпоративного приложения. Бизнес-логика: часть 2 S91 40 // заполнение объекта AddressModel значениями полей 41 // элементов данных EJB-компонента Address 42 address.setAddressID( addressID ); 43 address.setFirstName( firstName }; 44 address.setLastName( lastName ); 45 address.setStreetAddressLinel( streetAddressLinel ); 46 address.setStreetAddressLine2( streetAddressLine2 J; 47 address.setCityf city ); 48 address.setstate( state ); 49 address.setZipCode( zipCode ); 50 address.setCountry( country ); 51 address.setPhoneNumber( phoneNumber ); 52 53 return address; 54 55 } // конец метода getAddressModel 56 57 // установка данных в EJB-компоненте Address с использованием объекта AddressModel 5S private void setAddressModel{ AddressModel address ) 59 ( 60 // обновление элементов данных EJB-компонента Address 61 // Значениями, предоставляемыми объектом AddressModel 62 firstName = address,getFirstMame(); 63 lastName = address.getLastName(); 64 streetAddressLinel = address.getStreetAddressLinel(); 65 streetAddressLine2 = address.getStreetAddressLineJ(); 66 city = address.getCity(); 67 state = address.getStateО; 68 zipCode = address.getZipCode(); 69 country = address.getCountry (); 70 phoneNumber = address.getPhoneNumber(); 71 72 } // конец метода setAddressModel 73 74 // создание EJB-компонента Address с использованием заданного объекта AddressModel 75 public Integer ejbCreate< AddressModel address ) 7 6 throws CreateException 77 { 78 // извлечение уникального значения для первичного ключа 7 9 //с использованием EiTB-компонента SequenceFactory 80 try ( 81 Context initialContext = new InitialContext(); 82 83 // поиск EJB-компонента SequenceFactory 84 Object object = initialContext.lookup( 85 "java:comp/env/ejb/SequenceFactory" ); 86 87 SequenceFaсtorуHome sequenceFactoryHome = 88 ( SequenceFactoryHome ) 89 PortableRemoteObjeat. narrow ( 90 object, SequenceFactoryHome.class ); 91 92 // нахождение последовательности для EJB-компонента Address 93 SequenceFactory SequenceFactory =
592 Глава 12 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 не 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 13S 139 140 141 142 143 144 145 146 sequenceFactoryHome.findByPrimaryKey( "Address" ); // извлечение следующего имеющегося идентификатора addressID addressID = sequenceFactory.getNextID(); // задание идентификатора addressID для адреса (первичный ключ) address.setAddressID( addressID ); // использование объекта AddressModel для установки нового адреса setAddressModel( address ); } // конец блока try // обработка исключения при использовании EJB-коМЛОНента SequenceFactory catch ( Exception exception ) { throw пек CreateException( exception.getMessage() ); } // контейнер EJB будет возвращать удаленную ссылку return null; 1 // конец метода ejbCreate // выполнение необходимых после создания объекта действий public void еjbPostCreate( AddressModel address } {} // установка контекста EntityContext public void setEntityContext( EntityContext context ) entityContext = context; // сброс контекста EntityContext public void unsetEntityContext() entityContext = null; // активация экземпляра EJB-компонента Address public void ejbActivate() addressID = ( Integer ) entityContext.getPrimaryKey() // пассивация экземпляра EJB-компонента Address public void ejbPassivateО addressID null; // удаление экземпляра EJB-компонента Address public void ejbRemove() {}
Практический пример корпоративного приложения. Бизнес-логика: часть 2 593 147 // сохранение данных EJB-компонента Address в базе данных 148 public void ejbStoreO {) 149 150 // Загрузка данных EJB-компонента Address us базы данных 151 public void ejbLoadO {} 152 } Рис, 12.9. Реализация AddressEJB удаленного интерфейса Address Метод getAddressModel (строки 35-55) создает объект AddressModel, устанавливает для его свойств значения открытых элементов данных EJB-компонента Address и возвращает объект AddressModel вызвавшему процессу. Метод setAdd- ressModel (строки 58-72) представляет собой утилитарный метод, который принимает в качестве аргумента объект AddressModel и обновляет значения в элементах данных EJB-компонента AddressEJB. Контейнер EJB вызывает метод ejbCreate (строки 75-115) для создания нового EJB -компонента AddressEJB. Каждый EJB--KOMnoHeHT Address должен иметь уникальный идентификатор addressID для своего первичного ключа. Метод getNextID (строка 97) генерирует этот уникальный идентификатор. В строке 100 устанавливается значение идентификатора addressID в объекте AddressModel. В строке 103 объект AddressModel передается методу setAddressModel для завершения инициализации EJB-компонента AddressEJB. 12.3.3. Собственный интерфейс AddressHome Интерфейс AddressHome (рис. 12.10) предоставляет методы для создания и нахождения EJB-компонентов Address. Метод create (строки 18-19) принимает в качестве аргумента объект AddressModel. Контейнер EJB вызывает метод ejbCreate (рис, 12.9), когда клиент вызывает метод create. Метод findByPrimaryKey (строки 22-23) находит имеющийся EJB-компонент Address, используя его первичный ключ addressID, и возвращает удаленную ссылку на EJB-компонент Address. 12.3.4. Класс AddressModel Класс AddressModel (рис. 12.11) представляет собой класс модели, который реализует интерфейс XMLGenerator и метод getXML для генерирования XML-описания адреса. Класс AddressModel содержит свойства (строки 17-149) для каждого открытого элемента данных в реализации AddressEJB (рис. 12.9). Метод getXML (строки 152-212) формирует элемент XML, содержащий дочерние элементы для каждого из свойств класса AddressModel. 1 // AddressHome.java 2 // AddressHome - собственный интерфейс для EJB-компонента с данными Address. 3 package com.deitel.advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 import java.rmi. ReпloteException; 7 8 // Пакеты расширений Java 9 import javax.eJb.EJBHome; 10 import javax.ejb.*; 11 12 // Пакеты Deitel
594 Глава 12 13 import com.deifcel.advjhtpl.bookstore.model.*; 14 15 public interface AddressHome extends EJBHome { 16 1"? // создание EJB-компонента Address с использованием заданного объекта AddressModel 18 public Address create{ AddressModel address } 19 throws RemofceExoeption, Cre&teException; 20 21 // нахождение адреса с взданним Идентификатором addressID 22 public Address findByPrimaryKey( Integer addressID ) 23 throws RemoteException, FinderException; 24 } ___ ^____ Рис. 12.10. Интерфейс AddressHome для создания и нахождения экземпляров EJ В-компонента Address 1 // AddressModel.Java 2 /f Объект AddressModel представляет адрес покупателя и 3 // содержит названия улицы, города, штата и почтовый индекс. 4 package com.deitel.advjhtpl.bookstore.model; 5 6 // Набор базовых пакетов Java 7 import java.io.*; 8 import java^util.*; 9 10 // Пакеты сторонних поставщиков 11 import org.w3c.dom.*; 12 13 public class AddressModel implements Serializable, 14 XMLGenerabor 1 15 16 // свойства объекта AddressModel 17 private Integer addressID; 18 private String firstName; 19 private String lasfcName; 20 private String streetAddressLinel; 21 private String streetAddreasLlne2; 22 private String city; 23 private String state; 24 private String zipCode; 25 private String country; 26 private String phoneNumber ; 27 28 // формирование пустого объекта AddressModel 29 public AddressModel() {) 30 31 // задание идентификатора addressID 32 public void setAddressID( Integer id ) 33 { 34 addressID = id; 35 } 36 37 // получение идентификатора addressID 38 public Integer getAddressIDО
Практический пример корпоративного приложения. Бизнес-логика: час return addressID; // задание имени public void setFirstHame( String name ) firstName = name; // получение имени public String getFirstName() return firstName; // задание фамилии public void setLastName( String name ) lastName — name; // получение фамилии public String getLastKame{) return lastName; // задание первой строки названия улицы public void setStreetAddressLinel ( String address ) streetAddressLinel = address,- // получение первой строки названия улицы public String getStreetAddressLinel() return streetAddressLinel; ,/ задание второй строки названия улицы public void setStreetAddressLine2( String address ) streetAddressLine2 = address; // получение второй строки названия улицы public String getStreetAddressLine2() return streetAddressLine2; // задание названия города public void setcity< String addressCity J t city = addressCity;
596 Глава 12 / получение названия города public String getCityO return city; // задание названия штата public void setstate( String addressState } state = addressState; // получение названия штага public String getStateO return state; // задание почтового индекса public void setZipCode( String zip ) zipCode = zip; // получение почтового индекса public String getZipCode{) return zipCode; // задание названия страны public void setCountry{ String addressCountry ) country = addressCountry; // получение названия страны public String getCountry() return country; // задание номера телефона public void setPhoneNumber{ String phone ) phoneNumber = phone; // получение номера телефона public String getPhoneNumber{) return phoneNumber;
Практический пример корпоративного приложения. Бизнес-логика: часть 2 151 // построение XML-представления данных о покупателе 152 public Element getXML{ Document document ) 153 { 154 // создание элемента address 155 Element address = document.createElementt "address" ); 156 157 // создание элемента firstName 158 Element temp = document.createElement( "firstName" ); 159 temp.appendChiId{ 160 document.createTextNode( getFirstNamef) ) ); 161 address.appendChild( temp ); 162 163 // создание элемента lastName 164 temp = document.createElement{ "lastName" ); 165 temp.appendChild( 166 document.createTextNode( getLastName() ) ); 167 address.appendChild( temp ); 168 169 // создание элемента StreetAddressLinel 170 temp = document.createElement( "streetAddressLinel" ); 171 temp.appendChild( 172 document.createTextNode( getStreetAddressLinel() ) ); 173 address.appendChildf temp ); 174 175 // создание элемента streetAddressLine2 176 temp = document.createElement( "streetAddressLine2" ); 177 temp.appendChild( ' 178 document.createTextNode( getStreetAddressLine2() ) ); 179 address.appendChild( temp ); 180 161 // создание элемента city 182 temp = document.createElement( "city" ); 183 temp.appendChild{ document.createTextNode( city ) ); 184 address.appendChild{ temp ); 185 186 // создание элемента state 187 temp = document.createElement{ "state" ); 188 temp.appendchiId( 189 document.createTextNode( getState() ) }; 190 address.appendChiId( temp }; 191 192 /J создание элемента zipCode 193 temp = document.createElement( "zipCode" ); 194 temp.appendChild( 195 document.createTextNode( getZipCode() ) ); 196 address.appendChild( temp ); 197 198 // создание элемента country 199 temp = document.createElement( "country" ); 200 temp.appendChild( 201 document.createTextNode( getCountry() ) ); 202 address.appendChild( temp ); 203 204 // создание элемента phoneNumber 205 temp = document. createEl ement ( "phoneNuir.ber" ); 206 temp.appendChild(
598 Глава 12 207 document.ereateTextNode( getPhoneNumberП | ); 208 address.appendChild( temp }; 209 210 return address; 211 212 } // конец метода getXML 213 } Рис, 12.11. Класс Add re ssM ode I для сериализации данных EJ В -компонента Address В таблицах на рис. 12,12, 12,13 и 12.14 представлены параметры для развертывания EJB-компонента с данными Address. В дополнекие к этим установкам, следует задать для типа транзакции Transaction Type значение Required для всех бизнес-методов. Основные параметры для развертывания EJB -компонента Address Тип Bean Type Класс Enterprise Bean Class Собственный интерфейс Home Interface Удаленный интерфейс Remote Interface Entity com.deitel.advjhtpl.bookstore. ejb.AdressEJB com.deitel.advjhtpl.bookstore.ejb.AddressHome com.deitel.advjhtpl.bookstore.ejb.Address Рис. 12.12. Основные параметры для развертываний EJB-компонента Address Параметры управления данными и развертыванием для EJB-компонента Address ^Управление персистентностью Persistent Management Класс первичного ключа Primary Key Class Имя поля первичного ключд Primary Key Field Name Имя базы данных JNDI Database JNDI Name SQL-опера юр SQL Statement метода ejbStore SQL-оператор SQL Statement метода ejbCreate SQL-оператор SQL Statement метода ejbRemove Container-Managed Persistence Java.lang.Integer addressID jdbc/Bookatore UPDATE Address SET city = ?, country = ?, firstName = ?, lastName = ?, phoneNumber = ?, state = ?, streetAddressLinel = ?, I streetAddressLine2 = ?, zipCode = ? WHERE addressID = ? INSERT INTO Address { addressID, city, country, firstName, lastName, phoneNumber, state, StreetAddressLinel, 8treetAddressLine2, zipCode ) VALUES {?, ?, ? ?, ?, ?, ?, ?,?,?) DELETE FROM Address WHERE addressID = ? SQL-оператор SQL Statement ; SELECT addressID FROM Address WHERE addressID = ? метода findByPrimaryKey <
Практический пример корпоративного приложений. Б из нес-логика: часть 2 599 | Параметры управления данными и развертыванием для EJB-компонента Address SQL-оператор SQL Statement метода ejbLoad SQL-оператор создания таблицы Table Create SQL Statement SQL-оператор удаления таблицы Table Delete SQL Statement SELECT city, country, firstNaire, lasfcJfcuee, phormNumber, state, streetAddressLinel, streetAddressLiite2, zipCode FROM Address WHERE addressID = ? CREATE TABLE Address ( addressID INTEGER, city VARCHAR( 255 ), country VARCHAR( 255 ), firstName VARCHAR( 255 ), lastName VARCHAR( 255 ), phoneNumber VARCHAR( 255 ), state VARCHAR! 255 ), streetAddressLinel VARCHAR( 255 ), streetAddressLine2 VARCHAR( 255 ), zipCode VARCHAR( 255 ), CONSTRAINT pk_Address PRIMARY KEY ( addressID ) ) DROP TABLE Address Рис 12.13. Параметры управления данными и развертыванием для EJB-компонента Address Ссылки на EJB-компокенты для компонента Address Кодовое имя Coded Пате Тип Туре Собственный интерфейс Ноте Удаленный интерфейс Remote Имя JNDI Name ejb/SequenceFaetory Entity com.deitel.advjhtpl.bookstore.ejb.SequenceFactory-Home com.deitel.advjhtpl.bookstore.ejb.SequenceFactory SequenceFactory Рис. 12.14. Ссылки на EJB-компоненты для компонента Address 12.4. Реализация EJB-компонента SequenceFactory Одной из фундаментальных концепций реляционных баз данных является применение первичного ключа, уникально идентифицирующего строку в таблице базы данных. Первичный ключ нужен для определения отношений между таблицами в базе данных. Например, в нашем учебном приложении устанавливается связь между каждым заказом Order и покупателем Customer путем помещения первичного ключа custouierlD таблицы Customer в ноле (его называют внешним ключом) таблицы Order. Идентификатор customerlD гарантированно является уникальным, поэтому может быть использован для определения, какой именно покупатель сделал заказ. EJB-компоненты Customer, Order и Address содержат записи таблицы SequenceFactory, из которых онн могут получать первичные ключи, 12.4.1. Удаленный интерфейс SequenceFactory Интерфейс SequenceFactory (рис. 12.15) представляет собой удаленный интерфейс для EJB-компонента SequenceFactory. Метод getNextID (строка 15) возвращает следующий имеющийся первичный ключ.
600 Глава 12 1 // SequenceFactory.j ava 2 // SequenceFactory - удаленный интерфейс для EJB-компонента 3 // с данными SequenceFactory. 4 package cam.de ite1.advjhtpl.bookstore.e jb ; 5 6 // Набор базовых пакетов Java 7 import java.rmi.RemoteException; 8 9 // Пакеты расширений Java 10 import javax.ejb.EJBObject; 11 12 public interface SequenceFactory extends EJBObject { 13 14 // получение следующего имеющегося уникального идентификатора 15 public Integer getNextID() throws RemoteException; 16 } Рис. 12.15. Удаленный интерфейс SequenceFactory для генерирования первичных ключей 12.4.2. Реализация SequenceFactoryEJB удаленного интерфейса SequenceFactory На рис. 12,16 представлена реализация SequenceFactoryEJB удаленного интерфейса SequenceFactory- Контейнер EJB управляет синхронизацией с базой данных открытых элементов данных (строки 19-22). 1// SequenceFactoryEJB.java 2 // EJB-компонент с данными SequenceFactory генерирует уникальные первичные ключи. 3 package com.deltel.advjhtpl.bookstore.ejb; 4 5 // Набор базовых пакетов Java 6 impox t java.xmi.RemoteException; 7 import java.util,ArrayList; 8 9 // Пакеты расширений .Java 10 import javax.ejb.*; 11 import javax.naming.*; 12 import j avax.rmi.PortableRemoteObject; 13 14 public class SequenceFactoryEJB implements EntityBean { 15 private EntityContext entityContext; 16 17 // поля, управляемые контейнером 18 public String tableName,- // имя таблицы с последовательностью идентификаторов 19 public Integer nextXD; // следующий доступный уникальный идентификатор 20 21 // получение следующего имеющегося идентификатора orderID 22 public Integer getNextID() 23 { 24 // сохранение идентификатора nextID для его возврата 25 Integer ID = new Integer! nextID.intValue() ); 26
Практический пример корпоративного приложения. Бизнес-логика: часть 2 601 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 } // инкрементирование для получении следующего уникального идентификатора nextID = new Integer( ID.intValue{) + 1 ); return ID; I // установка контекста элемента данных public void setEntityContext( EntityContext context ) entityContext = context; // сброс контекста элемента данных public void unsetEntityContext() entityContext = null; // активация экземпляра EJB-компонента SequenceFactory public void ejbActivate() tableName = ( String ) entityContext.getFrimaryKey() // пассивация экземпляра EJB-компонента SequenceFactory public void ejbPassivate() tableName = null; // удаление экземпляра EJB-компонента SequenceFactory public void ejbRemove() {} // сохранение данных EJB-компонента SequenceFactory в баае данных public void ejbStoret) {) // загрузка данных EJB-компонента SequenceFactory иа базы данных public void ejbLoad() {} Рис. 12.16. Реализация SequenceFactoryEJB удаленного интерфейса SequenceFactory Метод getNextOrderlD (строки 25-31) вычисляет следующий имеющийся уникальный идентификатор orderlD, инкрементируя текущее значение orderlD (строка 27). Это новое значение сохраняется в EJB-компоненте (строка 28) и возвращается вызвавшему процессу (строка 30). Метод getNextCustomerlD (строки 34-40) инкрементирует текущее значение поля CustomerlD и возвращает значение вызвавшему процессу (строка 39). Метод getNextAddressID (строки 43-49) инкрементирует значение поля addressib и возвращает значение вызвавшему процессу в строке 48. Чтобы EJB-компонент SequenceFactory правильно вычислял уникальные значения orderlD, customerlD и addressID, должен иметься только один экземпляр EJB-компонента SequenceFactory. Если имеется более одного экземпляра EJB- компонента SequenceFactory) это может привести к генерированию повторяю-
602 Глава 12 щихся значений order ID, custom erID или addressID (дубликатов). Для получения нужного экземпляра EJB-компонента SequenceFactory клиентам EJB-компонента SequenceFactory следует использовать только метод find Sequence Factory интерфейса SequenceFactory Home. Разработчик должен определить SQL-запрос, который будет возвращать одну и ту же запись таблицы SequenceFactory при каждом вызове метода findSequencePactory. 12.4.3. Собственный интерфейс SequenceFactoryHome Интерфейс SequenceFactoryHome (рис. 12.17) представляет собой собственный интерфейс для EJB-компонента SequenceFactory. Метод findBy Primary Key (строки 15-16) возвращает удаленную ссылку на EJE-компоненг SequenceFactory для таблицы базы данных с указанным именем. В таблицах на рис. 12.18 и 12.19 представлены параметры развертывания для EJB-компонента с данными SequenceFactory. В дополнение к, этим установкам, следует задать для типа транзакции Transaction Type значение Required для всех бизнес-методов. 1 // SequenceFactoryHome.Java 2 // SequenceFactoryHome - собственный интерфейс для EJB- 3 // компонента с данными SequenceFactory. 4 package com.deitel.advjhtpl.bookstore.ejb; 5 6 // Набор базовых пакетов Java 1 import java.rmi.RemoteException; 8 9 // Пакеты расширений Java 10 import javax.ejb. *,' 11 12 public interface SequenceFactoryHome extends EJBHome { 13 14 // нахождение компонента SequenceFactory с заданным первичным ключом 15 public SequenceFactory findByPrimaryKey( String tableName ) 16 throws RemoteException, FinderException; 17 } , , Рис. 12.17. Интерфейс SequenceFactoryHome для нахождения экземпляров EJB-компонента SequenceFactory [ Основные параметры для развертывания EJB-компонента SequenceFactory Тип Bean Type Класс Enterprise Bean Class Собственный интерфейс Ноше Interface Удаленный интерфейс Remote Interface Entity com.deitel.advjhtpl.bookstore.ejb.SequenceFactoryEJB com.deitel.advjhtpl.bookstore.ejb.SequenceFactoryHome com.deitel,advjhtpl.bookstore.ejb,SequenceFactory Рис. 12.18. Основное параметры для развертывания EJB-кок/понентз SequenceFactory
Практический пример корпоративного приложения. Бизнес-логика: часть 2 603 Параметры управления данными и развертыванием для EJB-компонента SequenceFactory Управление персистентностью Persistent Management Класс первичного ключа Primary Key Class Container-Managed Persistence j ava.lang.S tring Имя поля первичного ключа Primary Key Field Name tableName Имя базы данных JNDI Database JNDI Name jdbc/Bookstore SQL-оператор SQL Statement метода ejbStore UPDATE SequenceFactory SET nextID = ? tableName = ? SQL-оператор SQL Statement метода ejbCreate INSERT INTO SequenceFactory ( nextID, tableName ) VALUES (?,">) SQL-оператор SQL Statement метода ejbRemove DELETE FROM SequenceFactory WHERE tableName SQL-оператор SQL Statement метода findByPrimaryKey SELECT tableName FROM SequenceFactory WHERE tableName = ? SQL-оперэтор SQL Statement метода ejbLoad SELECT nextID FROM SequenceFactory WHERE tableName = ? SQL-оператор создания тзблицы Table Create SQL Statement CREATE TABLE SequenceFactory ( nextID INTEGER, tableName VARCHARt 255 ), CONSTRAINT pk_SequenceFactory PRIMARY KEY ( tableName ) ) SQL-оператор удаления таблицы Table Delete SQL. Statement DROP TABLE SequenceFactory Рис. 12.19. Параметры управления данными и развертыванием для EJB-компонента SequenceFactory 12.5. Развертывание приложения Deitel Bookstore средствами J2EE Для развертывания компонентов приложения Deitel Bookstore на эталонной реализации Java 2 Enterprise Edition (J2EE) требуется инструментальное средство Application Deployment Tool. Ниже будут рассмотрены этапы процесса развертывания EJB-компонента Order. Развертывание других EJB-компонентов осуществляется аналогично. Общие инструкции по развертыванию сеансовых EJB-компо- нентов с состоянием, таких как EJB-компонент ShoppingCart, можно найти в главе 6. Общие инструкции по развертыванию сервлетов приведены в главе 4. 12.5.1. Развертывание компонентов-сущностей EJB с персистентностью, управляемой контейнером Чтобы начать развертывание EJB-компонентов с данными для приложения Deitel Bookstore, выберите пункт New Enterprise Bean... из меню File инструментального средства развертывания Application Deployment Tool (рис. 12.20). Вы увидите интерфейс мастера создания JAR-файла EJB-компонента (рис. 12.21). В поле JAR Display Name содержится текст, который будет появляться в окне Application Deployment Tool для JAR-файла этого EJB-компонента, но не будет
604 Глава 12 оказывать влияния на развертывание приложения. Щелкните на кнопке Add... рядом с полем Content, чтобы добавить файлы классов для EJB-компонента в JAR-архив. ^Дй^: IJK,. ячш^^жт Рис. 12.20. Добавление EJB-компонента в корпоративное приложение рЩЩЕЗЩЖ : (tin птйп№г^' ' tnitfonn ]♦ ВдЫДига. . ,'"-"':!':_, .„, 1, ^ffijjjpisiiisss Logic " | Рис. 12.21. Создание JAR-файлэ для EJB-компонента
Практический пример корпоративного приложения. Бизнес-логика: часть 2 605 Чтобы добавить файлы классов EJB-компонента в JAR-файл, необходимо задать корневой каталог Root Directory, который содержит структуру классов в пакете (рис. 12.22). Например, EJB-компонент Order содержится в пакете com.dei- tel.advjhtpl.bookstore.ejb. Если откомпилированный файл класса помещен в каталог D:\BookStore\com\deitel\advjhtpl\bookstore\ejb, выберите в качестве корневого каталога Root Directory D:\BookStore. Щелкните на кнопке Browse... и воспользуйтесь диалоговым окном выбора файлов, чтобы выбрать корневой каталог Root Directory. После выбора надлежащего корневого каталога укажите файлы классов для удаленного интерфейса, собственного интерфейса, реализации и других классов, необходимых для EJB-компонента (например, OrderModel.class, XMLGenera- tor.class, специфичные для приложения классы исключений и т.д.). Удерживая нажатой клавишу CTRL, можно выбрать несколько файлов за раз. Щелкните на кнопке Add для добавления выбранных файлов классов в JAR-архив EJB-компонента и щелкните на ОК (рис. 12.23). На рис. 12.24 показаны результаты добавления файлов классов в JAR-файл EJB-компонента для EJB-компонента Order. Ы^ашч 2 nwn ШШ^Шш' Рис. 12.22. Задание корневого каталога для классов EJB-компонента Рис. 12.23. Добавление классов EJB в JAR-файл EJB-компонента
606 Глава 12 янимплтеи!!! „ AiAR.ffe Чыеайь^в" ^. fiofSitfn Щ& «&Htosd Be^nj^mi can:*dd а newJhfi la Рис. 12.24. Результаты добавления классов EJB в JAR-файл EJB-компонента После добавления файлов классов в JAR-файл EJB-компонента необходимо указать файлы классов, которые содержат удаленный интерфейс, собственный интерфейс и реализацию EJB-компонента (рис. 12.25). Выберите соответствующий класс из раскрывающегося списка, как показано на рис. 12.25. EJB-компонент Order представляет собой компонент-сущность, поэтому в разделе Bean Type выберите опцию Entity (рис. 12.26), t«?$f 4я mm i ef ей ciass янн. й1Й1*а tafcjwiSii Щят№Я JAR wit 1e Шейка tff EfrJerpuw вэдптмуы: wotfd шЬ create,' ; одса1е№е"^1Я&т1вгрг]»^п№М]ГО1:1М№рш$9& ^*WM>iwid> idispiarnW™, (евСЛрШ.'а.м! ita^iorthi ьтилф^иц» &Ёйжё!1 iOafitejtiM» ■■Jj авМ^кШ^^ ; 7*4 *B«rt J Ntiit't- [|'::V ffowi .; Рис. 12.25. Задание классов, собственною интерфейса и удаленного ишерфейса для EJB-компонента
Практический пример корпоративного приложении. Б из нес-логика: часть 2 607 ESrfpw i ntprpmp tivan yttr&rtf - ШЩ&Ш^ът н» ores tftjjjsaii fcij.wft bt тыл in v»ii"ejej№ ««t .■ ■ "-fiyii $$& * diBpley-rrame, W£«iplkirt entf itqn* Геи Ihft uuneflLrf othijr* икмшйФгмфДОкЙо» ;g3|g итлевйимтФикиМшогыв.Огвя ^■1 ■■StfleMW:b-v.;.P. ..- ^ ^^^^; ', 3in)tiliMi(<№l8c , .;^ T Uff£P!lcim(3&f3?£ 1ГЛ: ■f :-' ZJ Рис. 12.26. Задание Entity в качестве типа Bean Type EJ В-компонента EJB-компонент с данными Order использует персистентность, управляемую контейнером, для синхронизации своих данных с соответствующей таблицей базы данных. На следующем шаге мастера (рис. 12.27) выберите опцию Container- Managed Persistence и установите флажки рядом с каждым из полей, управляемых контейнером. Укажите полное имя класса для класса первичного ключа .■.,BSK?irJHsart<mllNJ> ■ -.ana ы, t4#£Bj* tieii newt.. ' TMiltbfeUs^uwoWdlMpyrilfltij;, . '-'"-.'"" '^-A - '■'■' * '" " -*-: --■ KwQMSf ^|avaIan g Integer ; '№uiafy Key ReU Hair»; "7^--^- C» § Рис. 12.27. Настройка полей, управляемых контейнером, и класса первичного ключа
608 Глава 12 (включая имя пакета) В поле Primary Key Class. Для EJB-компонента Order введите java.Iang.Integer в качестве имени класса первичного ключа. Если ваш EJB-компонент использует класс первичного ключа, определенный разработчиком (например, EJB-компонент OrderProduct), необходимо также указать полное имя пакета (например, com.deitel.advjhtpl.bookstore.ejb.OrderProdiictPK). Выберите в раскрывающемся списке Primary Key Field Name поле, которое содержит первичный ключ (например, orderiD). Если ваш EJB-компонент ссылается на другие EJB-компоненты, эти EJB-kom- поненты должны быть указаны в инструментальном средстве развертывания Application Deployment Tool (рис. 12.28). Щелкните на кнопке Add, чтобы добавить новую ссылку на EJB-компонент. Столбец кодового имени Coded Name содержит строку, используемую для поиска EJB-компонента в каталоге JNDI. Например, чтобы найти EJB-компонент Product, используется строка java:comp/eirv/ ejb/Product. Соответствующим кодовым именем будет ejb/Product. Выберите нужный тип для EJB-компонента (например, Session для сеансового EJB-компонента или Entity для компонента-сущности EJB) из раскрывающегося списка Туре. Укажите полные имена классов (включающие имена пакетов) для собственного и удаленного интерфейсов в столбцах Ноте и Remote. Например, в столбце Ноте для EJB-компонента Product укажите com.deitel.advjh.tpl.bookstore.ejb. ProdnctHome. Введите имя JNDI в поле JNDI Name для EJB-компонента, на который осуществляется ссылка (например, Product). Рис. 12.28. Задание других EJB-компонентов, на которые ссылается данный EJB-компонент Серверы приложений J2EE поддерживают управление транзакциями, семантика которого может быть задана при развертывании приложения. Для каждого из бизнес-методов задайте соответствующий тип транзакции Transaction Type (рис. 12.29), как рассказывалось в главе 7. Па рис. 12.30 представлен листинг ХМL-дескриптора, который был сформирован в результате предыдущих действий. Этот ХМL-дескриптор может быть использован при развертывании данного приложения на любом совместимом с J2EE сервере приложений.
Практический пример корпоративного приложения. Бизнес-логика: часть 2 609 Рис. 12.29. Задание режима контейнерного управления транзакциями для бизнес-методов EJB-компонента Рис. 12.30. ХМL-дескриптор, сформированный средством развертывания Application Deployment Tool Теперь необходимо настроить баау данных, в которой компонент-сущность с персистентностью, управляемой контейнером, будет хранить свои данные. Щелкните на кнопке Deployment Settings... на вкладке Entity (рис. 12.31). Укажите имя JNDI для базы данных в поле Database JNDI Name (рис. 12.32). В эталонной реализации J2EE Reference Implementation это значение соответствует значению, заданному в файле конфигурации default.proper ties (например, j doc/Book Store). Указав имя JNDI для базы данных, щелкните на кнопке Generate SQL Now, чтобы создать необходимые операторы SQL для методов поиска и создания EJB-компонента. Вам будет предложено указать предложение WHERE для любых нестандартных методов поиска, определенных в собственном интерфейсе EJB-компонента (рис. 12.33). Для каждого метода, приведенного в списке EJB Method (например,
610 Глав, Рис. 12.31. Задание параметров развертывания для EJB-компонента gran Tin» Q»gjw£y ■■■„_. .... „ ''фан, ш'тты*сь**\ -т,*шштл' nneeycustomerD(rsi ЗДБЮге ^Create Щ% elbLMd Рис. 12.32. Настройка параметров базы данных для EJB-ког^понента ш._.„т ■t ^t_^ Рис. 12.33. Окно сообщения, указывающее о, что для SQL-запросов требуется предложение WHERE
Практический пример корпоративного приложения. Бизнес-логика: часть 2 611 findByPriinaryKey, ejbStore, Table Create и т.д.), введите соответствующий текст SQL-запроса из таблиц, приведенных в главах 11-12. Например, в таблице на рис. 12.6 представлены соответствующие SQL-запросы для EJB-комповента Customer. Рис. 12.34. Задание SQL-запроса для метода flndByCustomerlD 12.5.2. Развертывание сервлетов Сервлеты в приложении Deitel Bookstore используют параметры контекста и инициализации, которые разработчик предоставляет при развертывании приложения. Это позволяет значительно упростить процесс добавления в приложение поддержки новых типов клиентов. В таблице на рис. 12.35 перечислены все используемые в приложении сервлеты, значения для параметра инициализации XSL_FILE сервлета и псевдонимы сервлетов. Кроме этого, следует установить bookstore в качестве Web-контекста WAR- фай л а сервлетов (рис. 12.36). Сер влет AddToC»rtServlet RemoveFromCartServlet OpdateCartServlet ViewCartServlet Che cXoutSe rvle t viewOrderServlet ViawOrderHistoryServlet Значение параметра инициализации XSL_FILE error.xsl error.xsl error.xsl viewCart.xsl error.xsl vi ewOrde r.xa1 viewOrdecHistory.xsl GetAllProductsServlet 1 products.xsl GetProductServlet | pjroductD«tails. xsl Псевдоним сервлета AddToCart | RemoveFromCart J UpdateCart ViewCart | Checkout | ViewOrder j Vi ewOrderHiatory GetJU. lFzoduets GetProduct
612 Глава 12 Сервлет Produ с tS e archS e rvlet Regis terServle t LoginServlet GetPasswordHinfcServlet Значение параметра инициализации XSL_FILE products.xsl error.xsl login.xsl pssswordHi.nt.xsl Псевдоним сервлета ProductSeareh Register Login GetPasswordHint Рис. 12.35. Параметры развертывания сервлетов приложения Deitel Bookstore Рис. 12.36. Задание корня контекста Context Root для сервлетов приложения Deitel Bookstore Напомним, что сервлеты в приложении Deitel Bookstore получают информацию о конфигурадии клиента из параметра CLIENT_LIST контекста сервлета. Задайте значение для этого параметра контекста, как показано на рис. 12.37. Рис. 12.37. Задание параметра контекста CLIENT J.15T для сервлетов приложения Deitel Bookstore
Практический пример корпоративного приложения. Бизнес-логика: часть 2 613 Сервлеты в приложении Deitel Bookstore используют бизнес-логику EJB-компо- нентов для хранения информации о магазинных тележках покупателей, создания регистрационных записей и т.д. Чтобы дать возможность сервлетам осуществлять доступ к EJB-компонентам, необходимо указать в инструментальном средстве раз^ вертывания ссылки на EJB-компоненты. На рис. 12.38 представлены все необходимые ссылки на EJB-компоненты. Не забудьте указать имя JNDI для каждого EJB-компонента (например, ShoppingCart) и полные имена классов для собственного и удаленного интерфейсов. Рис. 12.38. Ссылки на EJB-компоненты для сервлетов Последний этап развертывания приложения Deitel Bookstore состоит в добавлении документов таблиц стилей XSL и других вспомогательных файлов в WAR-файл сервлетов. В таблице на рис. 12,39 перечислены эти вспомогательные файлы с указанием их относительные маршрутов в WAR-файле сервлетов и описанием каждого из них. Имя файла clients.xml index.html login.html registration.html index.wml login.wml Default.cas *. jpg *.xsl Относительный путь / / / / / / /styles/ /images/ /XSLT/XHTML/ Описание Файл конфигурации, позволяющий осуществлять поддержку клиентов различных типов Страница приветствия для XHTML-кпиентов Форма входа для XHTML-клиентов Форма регистрации для XHTML-клиентов Страница приветствия для WML-кшентов Форма входа для WML-клиентов Каскадная таблица стилей дли XHTML-клиентов Изобрахения облохек для книжной продукции XSi Т-трянсформации (таблицы стилей) для XHTML-клиентов
614 Глава 12 Имя файла navigation.xml *.xsl *.xsl Относительный путь /XSLT/XHTML/ /XSLT/WML/ /XSLT/cHTML/ Описание Заголовок со средствами навигации для XHTML-клиентов XSLT-трансформации (тэбгицы стилей) для WML-клиентов XSLT-трансформации (таблицы стилей) для cHTML-клиентов | Рис. 12.39. Вспомогательные файлы, включаемые в WAR-фэйл сервлетов Закончив настройку EJB-компонентов и сервлетов в инструментальном средстве Application Deployment Tool, выберите пункт Deploy Application из меню Tools, чтобы выполнить развертывание приложения на эталонной реализации сервера приложений J2EE. Доступ к только что развернутому приложению можно осуществить, указав URL http://locaihost:8000/bookstore/index.html в Web-браузере или имитаторе i-mode, либо указав URL http://Iocalhost:8000/bookstore/ index.wml в имитаторе WML. Мы закончили рассмотрение учебного приложения Deitel Bookstore, при разработке которого был использован ряд корпоративных технологий Java. В главе 13, «Серверы приложений», мы познакомимся с тремя наиболее популярными коммерческими серверами приложений, совместимыми с J2EF3: ВЕА WebLngic, IBM WebSphere и iPlanet Application Server. Затем мы обсудим, каким образом осуществляется развертывание учебного приложения Deitel Bookstore на серверах ВЕА WebLogic и IBM WebSphere.
13 Серверы приложений Цели • Познакомиться с несколькими популярными коммерческими серверами приложений. • Познакомиться с открыто доступными альтернативами коммерческим серверам приложений, • Узнать о требованиях, предъявляемых к серверам приложений, совместимым с J2EE. • Уяснить различия между реализациями коммерческих серверов приложений. • Осуществить развертывание учебного приложения Enterprise Java Deitel Bookstore на двух ведущих коммерческих серверах приложений. Что может, быть лучше, чем иметь мало потребностей и самому их удовлетворять. Ральф Вальдо Эмерсон «Наоборот, — продолжил Траляля, — если бы это было так, это бы еще ничего, а если бы ничего, оно бы так и было, но так как это не так, так оно и не этак. Такова логика вещей». Льюис Кэрролл Красноречие — логика вдохновения. Лаймен Бичер ... бассейн, глубина которого безмерна, и тропинки, вдоль которых растут цветы воспоминаний о былом. Кэтрин Мэнсфилд
616 Глава 13 13.1. Введение Java 2 Enterprise Edition представляет собой спецификацию для корпоративных окружений выполнения. Хотя Sun предоставляет эталонную реализацию этой спецификации, для работы реальной системы необходим коммерческий сервер приложений. В этой главе будут рассмотрены три популярных коммерческих сервера приложений, совместимых со спецификацией J2EE: BEA WebLogic, IBM WebSphere и iPlanet Application Server. Мы также познакомимся с сервером приложений JBoss с открытым доступом. Мы осуществим развертывание приложения Deitel Bookstore, рассматриваемого в главах 9-12, чтобы продемонстрировать переносимость приложений, написанных в соответствии с требованиями спецификации J2EE. После изучения этой главы вам станет ясна роль сервера приложений в корпоративных системах. Вы также сможете осуществлять развертывание вя- ших собственных систем на коммерческих серверах приложений. 13.2. Спецификация J2EE и ее преимущества Долгое время никакого стандарта для серверов приложений не существовало. Каждый разработчик сервера приложений предоставлял свой собственный набор интерфейсов прикладного программирования (API) с различными функциональными возможностями. Если бы компания пожеляла перенести свои корпоративные приложения на новую платформу сервера приложений, разработчикам пришлось бы заново писать большое количество кода, а сам процесс переноса становился сложным и дорогостоящим. Корпорация Sun Microsystems совместно с большой группой посталщиков серверов приложений разработала спецификацию Java 2 Enterprise Edition. Спецификация J2EE определяет платформу сервера приложений и вспомогательные API для построения корпоративных систем, которые обладают переносимостью между различными серверами приложений и, поскольку они используют Java, между различными платформами. Спецификация J2EE распространяет основной принцип Java «Написано однажды, работает везде» («Write Once, Run, Anywhere™») на корпоративные приложения. J2EE облегчает переносимость между различными серверами приложений, давая возможность
Серверы приложений 617 разработчикам подключать специфичные для сервера функции, такие как распределенные транзакции и запросы к базе данных, на этапе развертывания. В спецификации J2EE можно выделить несколько основных разделов, таких как поддержка API, безопасность, управление транзакциями и развертывание. Поставщик сервера приложений обязан обеспечить поддернску выполнения API платформы J2EE. В таблице на рис. 13.1 представлен список API, которые являются обязательными для версии 1.2 спецификации J2EE1. Обязательные API Java Data Base Connectivity (JDBC) 2.0 Exteision Remote Method Invocation — Internet Inter-Orb Protocol {RMI - HOP) 1.0 Enterprise Java Bears (EJB) 1.1 Servlets 2.2 Java Server Pages (JSP) 1.1 Java Messaging System (IMS) 1.3 Java Nailing and Directory Interface (JNDI) 1.2 Java Transaction API (JTA) 1.0 JavaMail 1.1 Java Activation Framework (JAF) 1.0 Web-контейнеры требуются требуются требуются требуются требуются тррбуеттт требуются требуются требуются требуются EJB-компоненты требуются требуются требуются нет нет требуется требуется требуется требуется требуется Рис. 13.1. Интерфейсы прикладного программирования, обязательные для сервера приложений 13.3. Коммерческие серверы приложений Чтобы быть сертифицированным как продукт, совместимый с J2EE, сервер приложений должен реализовывать минимум функциональных возможностей, которые определены в спецификации J2EE. Поставщики серверов приложений могут предоставлять функциональные возможности, которые намного превосходят возможности, определяемые спецификацией J2EE, чтобы каким-то образом выделить свой продукт среди других. Например, серверы приложений могут предоставлять усовершенствованные средства развертывания, повышенные меры безопасности, обеспечивать более высокую производительность, предоставлять средства для устранения ошибок и т.д. В этом разделе описываются четыре популярных сервера приложений: ВЕА WebLogic, iPlanet Application Server, IBM "WebSphere и JBoss. 13.3.1. BEA WebLogic 6.02 Компания ВЕА Systems в настоящий момент является ведущим в мире поставщиком серверов приложений. Популярность сервера WebLogic главным образом определяется тем, что он первым появился на рынке и заслужил хорошую репутацию. ВЕА предоставляет сервер приложений общего назначения, стремясь достичь оптимального соотношения между скоростью и стабильностью работы с одной стороны, и основательной поддержкой различных функций, определяемых спецификацией J2EE, с другой стороны. 1 На момент подготовки к изданию перевода книги актуальной была версия 1.3 спецификации J2EE. Версия 1.4 находилась на стадии законченного проекта. — Прим. ред. г На мгомент подготовки к изданию переведя книги ■текущей била версия WebLogic 7.0. — Прим. ред.
618 Глава 13 WebLogic предоставляет пулы данных, которые избавляют от необходимости создавать новые соединения с базой дапных для каждого клиента. Установка соединений с базами данных сопровождается значительными затратами времени: для некоторых серверов баз данных на одно соединение затрачивается до нескольких секунд. Имен пул открытых соединений и выделяя эти соединения клиентам по мере необходимости, WebLogic также предоставляет механизм для «горячего» развертывания. Сервер приложений постоянно проверяет указанный каталог на наличие новых приложений; если сервер приложений находит новое приложение в этом каталоге, WebLogic автоматически развертывает приложение без перезапуска сервера. WebLogic также автоматически сворачивает приложение, если администратор удаляет это приложение из каталога развертывания. Горячее развертывание увеличивает время активной работы сервера и упрощает процесс развертывания. Горячее развертывание может быть полезным для разработчиков при тестировании приложений. Одновременно с этим, ВЕД не рекомендует использовать горячее развертывание в рабочих окружениях. WebLogic использует кластеризацию, которая позволяет повысить степень доступности посредством поддержки режима восстановления после сбоя (failover) для EJB-компонентов и Web-компонентов. Серверы в составе кластера способствуют резервированию: если сервер, осуществляющий транзакцию, выходит из строя, другой сервер может прозрачно для пользователя взять управление на себя, не прерывая при этом транзакцию. Подобная организация также способствует оптимальному распределению нагрузки. Б окружении с оптимизацией распределения нагрузки сервер приложений распределяет запросы по нескольким серверам в зависимости от их загруженности (т.е. от того, как много запросов каждый из серверов на данный момент обрабатывает). Оптимальное распределение нагрузки помогает предотвратить сбои отдельных серверов при большом количестве запросов. Для окружений с одним сервером WebLogic предоставляет организацию множественных пулов: сервис, который осуществляет распределение транзакций между источниками данных.. Б то время, как пул соединений работает с одним источником данных, множественные пулы дают возможность приложению осуществлять доступ к нескольким пулам, тем самым, распределяя запросы среди множества источников данных. Тем самым обеспечивается выравнивание нагрузки для систем с одним сервером приложений. Сервер WebLogic 6.0 полностью совместим со спецификацией J2EE 1.2 и поддерживает многие требования спецификации J2EE 1.31. В составе WebLogic 6.0 имеется Web-сервер, но средство развертывания с графической оболочкой в него не входит, поэтому разработчикам приходится писать код дескрипторов развертывания самостоятельно. WebLogic также способен работать с несколькими понуляр- ными средами разработки Java Development, такими как JBuilder и Visual Cafe. WebLogic предоставляет расширенные возможности по обеспечению безопасности. Управление доступом пользователей реализуются в WebLogic через списки контроля доступа (Access control list — ACL). ACL обеспечивают эффективный метод контроля полномочий пользователей. В WebLogic также предусмотрена поддержка SSL и цифровых сертификатов. 13.3.2. iPlanet Application Server 6.0 Сообщество iPlanet E-Commerce Solutions представляет собой альянс между компаниями Netscape Communications и Sun Microsystems. Альянс iPlanet создал полностью сертифицированный под спецификацию J2EE сервер приложений взамен устаревшего сервера приложений Netscape. Основными целями при разработ- Версия WebLogic 7.0 полностью поддерживает спецификацию J2EE 1.3. — Прим. ред.
Серверы приложений 619 ке сервера приложений iPlanet были производительность, стабильность и полная совместимость с J2EE. Чтобы создать быстродействующий, масштабируемый сервер приложений, было решено интегрировать C++ с Java. Сервер iPlanet предоставляет поддержку режима восстановления после сбоев, организацию пула соединений и имеет ряд уникальных возможностей. Web-коннектор управляет оптимальным распределением нагрузки сервера приложений iPlanet. Web-коннектор управляет коммуникационным взаимодействием между сервером приложений и Web-сервером. Web-коннектор распределяет запросы между экземплярами сервера в зависимости от времени, затрачиваемого сервером на выдачу ответа. Для достижений максимальной управляемости сервер приложений может реализовывать свое собственное распределение нагрузки. Запросы распределяются в соответствии с определенным алгоритмом, задаваемым администратором развертывания в инструментальном средстве настройки. В iPlanet имеется «закрепленное» распределение нагрузки: если компонент помечен как «закрепленный», стандартный алгоритм распределения нагрузки не применяется, и компонент всегда будет выполняться на «закрепленной» машине. «Закрепленное» распределение нагрузки помогает в ситуациях, когда затраты времени на создание соединений между серверами для данного EJB-компонента больше, чем экономия времени за счет распределения нагрузки. iPlanet использует протокол Lightweight Directory Access Protocol (LDAF) для управления безопасностью. Пользователям могут назначаться групповые или индивидуальные полномочия для доступа к отдельным компонентам приложения. Настройка полномочий LDAP усложняет процесс настройки сервера, но позволяет лучше управлять полномочиями пользователей. Сервер приложений iPlanet Application Server интегрирован с серверами iPlanet Directory Server и iPlanet Web Server. На момент написания этой книги альянс iPlanet собирался выпустить новую версию сервера iPlanet Application Server.1 Чтобы скопировать последнюю версию, посетите сайт www.iplanet.coin/ias_deite]. На Web-сайте www.dejtel.com можно найти полное описание процесса развертывания приложения Deitel Bookstore (книжный Internet-магазин) на сервере iPlanet. 13.3.3. IBM WebSphere Advanced Application Server 4.0 IBM WebSphere — популярный сервер, который почти столь же распространен на рынке программных продуктов, как сервер приложений BEA WebLogic. В версию 4.0 внесены значительные усовершенствования в сравнении с предыдущими версиями. В частности, упрощен процесс настройки, уменьшено время выдачи ответа, улучшена система безопасности. Версия 4.0 предоставляет простой пользовательский интерфейс для администрирования и развертывания, уделяя основное внимание скорости работы и масштабируемости. В состав WebSphere входит версия Web-сервера Apache от IBM, поддержка режима восстановления после сбоев, система организации пулов данных и управление безопасностью на уровне пользователя. 13.3.4. Сервер приложений J Boss 2.2.2 Сервер JBoss, объединенный с контейнером сервлетов Tomcat от Apache Software Foundation, на данный момент совместим только со спецификацией J2EE 1.2 и представляет собой сервер приложений с открытым исходным кодом. Программное обеспечение JBoss распространяется в соответствии с открытой лицензи- На момент подготовки к изданию перевода книги версия 6.0 сервера iPlanet Application Server называлась Sun™ ONE Application Server Enterprise Edition. Версия 6.5 находилась на стадии опробования. — Прим. ред.
620 Глава 13 ей Lesser General Public License (LGPL). Исходный код JBoss доступен свободно и может быть использован для построения коммерческих приложений. Предполагается, что JBoss будет обладать совместимостью с последующими спецификациями J2EE. В настоящее время JBoss содержит большинство функциональных возможностей, характерных для коммерческих серверов приложений. JBoss был од- пим из первых серверов приложений, поддерживающих горячее развертывание. При выполнении сервер занимает весьма небольшую область памяти, оставляя значительный объем ресурсов для приложений [3]. В JBoss отсутствует поддержка кластеризации. Это может не иметь особого значения для малых и средних предприятий, но ограничивает возможности применения JBoss на коммерческом рынке. JBoss также не предоставляет инструментальных средств с графическим интерфейсом для осуществления развертывания или настройки. К счастью, большое сообщество пользователей JBoss поможет быстро справиться со значительной частью проблем, которые могут возникнуть при настройке. 13.4. Развертывание приложения Deitel Bookstore на сервере BEA WebLogic Настройка сервера приложений может оказаться довольно сложной задачей. В этом разделе будут рассмотрены все необходимые этапы установки и настройки сервера BEA WebLogic для развертывания учебного приложения Deitel Bookstore. Инструкции по настройке можно найти в документе e-docs.bea.com/wls/docs60/ install/instprg.html. В этих инструкциях по настройке предполагается, что с;\Ьеа является основным каталогом, Deitel — именем домена администрирования WebLogic, a bookstore — именем сервера. Также предполагается, что система управления базами данных Cloudscape установлена в каталоге c:\cloudscape_3.6, и что нашей базой данных является c:\cloudscape_3.6\databases\Bookstore. В зависимости от требований к безопасности, вы можете создать файл с именем passwordJni с паролем, который задается при установке. Если поместить файл password.ini в каталог c:\bea\wlserver6.0\config\deitel\, WebLogic не будет каждый раз запрашивать пароль яри запуске сервера приложений. Сначала выполним настройку WebLogic, чтобы обеспечить поддержку работы с базами данных Cloudscape, Откройте файл c:\bea\wlserver6.0\eonfig\ CaseStudyDS\startWebLogic.cmd в текстовом редакторе, таком как Notepad. Ближе к концу файла замените строку set CLASSPATH = .;. lib\weblogic_sp.jar; .\lib\weblogic.jar на строку set CLASSPATH = .;. lib\weblogic_sp.jar; .\lib\weblogic.jar; e:\cloudscape_3,6\lib\cloudscape.jar которая содержит описание пути к пакету Cloudscape в переменной окружения CLASSPATH. Следующая строка содержит параметры для запуска сервера: %JAVA_HOME%\bin\java -hotspot -ms64m ~юх64т -classpath %CLASSPATH% -Dweblogic.Domain=deitel -Dweblogic.Name=bookstore -Dbea.home=c:\bea -Djava.security.policy=c:\bea\wlserver6.0\lib\weblogio.pol icy -Dweblogic.management.password=%WLS_PW% weblogic.Server Чтобы упростить поиск базы данных, мы устанавливаем значение c:/cloud- scape_3.6/dat abases/ для свойства cloudscape.system.home в командной строке. Далее, запустите файл startWeblogic.cmd либо дважды щелкнув на нем мышью, либо введя его имя с консоли. Если окно консоли загружается, и через некоторое время появляется текст, подобный следующему:
Серверы приложений 621 <WebLogic Server started> <Notiee> <WebLogicSeirver> <SSLListThread listening on port 7002> <Notice> <WebLogicServer> <ListenThread listening on port 7001> то сервер приложений готов к настройке. Введите localhost:7001/console в строке адреса вашего Web-браузера. Появится окно с запросом вашего сетевого пароля. Введите system для User Name и пароль, который был задан в процессе установки WebLogic. Ваш Web-браузер должен отобразить страницу, представленную на рис, 13.2. Это консоль администрирования для управления большинством функций сервера приложений. Рис. 13.2. Консоль администрирования сервера WebLogic (публикуется с разрешения ВЕА Systems) Теперь настроим пул данных JDBC и источник данных. В главной панели администрирования выберите Connection Pools под заголовком JDBC. В верхней части правой панели должна иметься ссылка Create new JDBC Connection Pool. Установите параметры, как показано на рис. 13.3. Укажите Books tor ePool в качестве имени Name пула. Установите URL jdbc:cloudscape:Bookstore и имя класса драйвера Driver Classname COM.cloudscape.core.JDBCDriver. Наконец, установите для свойств password и server значения none. После ввода этих значений выберите Apply. Перезапускать сервер нужно лишь в том случае, если вам потребовалось внести дополнительные изменения. Далее, откройте вкладку Targets, выберите Bookstore из перечня имеющихся серверов, щелкните на направленной вправо стрелке, чтобы добавить этот пул соединений к серверу книжного магазина, а затем щелкните на Apply. В правой панели выберите CaseStudyDS>Service>JDBC>Date Source, а затем Create new JDBC data source. Задайте имя источника данных (например, BookstoreData- Source), введите jdbc/Bookstore в качестве имени JNDI, BookstorePool — в каче-
622 Глава 13 стве имени пула и выберите Create. Наконец, выберите Bookstore на вкладке Targets и щелкните на Apply. Остановите сервер, закройте окно команд WebLogic и перезапустите сервер с помощью командного файла starWebLogic.cmd, Рис. 13.3. Параметры пула соединений JDBC Теперь создадим дескриптор развертывания для нашего приложения. Извлеките содержимое архива bookstore.eaf в новый каталог. Для извлечения этих файлов можно использовать любую утилиту: zip или jar. Чтобы извлечь файлы с помощью утилиты jar, имеющейся в пакете JDK, введите команду jar xvf bookstore.ear Далее, извлеките содержимое архива ejb-jar-ic.jar во временный каталог с именем ejb-jar. Создайте текстовый файл с именем weblogic-ejb-jar.xml и сохраните его в каталоге ejb-jar\META-INF. Этот файл представляет собой дескриптор развертывания, специфичный для WebLogic. Дескриптор развертывания weblogic-ejb-jar.xml (рис. 13.4) определяет опции кэширования имен, сохраняемости, управления транзакциями и другие опции для EJB-компонентов в приложении Deitel Bookstore. В строках 6-8 задается DTD для дескриптора. В данном случае в качестве типа документа (doctype) задается WebLogic 5Л.0, поскольку наше приложение использует версию EJB 1.1. Элемент weblogic-ejb-jar (строки 11-356) содержит информацию о развертывании для всех EJB-компоиентов в JAR-файле. Элемент weblogic-enterprise-bean (строки 13-71) содержит информацию о развертывании для EJB-комтюнента Customer. Элемент ejb-name (строка 14) задает имя этого компонента. Элемент ejb-name должен соответствовать имени, найденному в документе ejb-jar.xmi для WebLogic, чтобы корректно идентифицировать текущий компонент. В строках 17-21 содержатся дескрипторы свойств кэширования. Элемент шах-bean-in-caclie (строка 18) определяет максимальное число активных экземпляров, которые разрешает иметь контейнер. По достижении этого предела контейнер Е JB будет пассивировать бездействующие экземпляры EJB-компонента. Элемент cache-strategy (строка 19) определяет, каким образом EJB-компоненты будут котировать данные. Допустимыми значениями являются Read-Write или Read-Only. Значение по умолчанию Read-Write дает возможность клиентам записывать информацию в компонент в составе стандартной транзакции, а метод ejbStore вызывается контейнером по завершении транзакции. Значение Read-Only не разрешает вызывать метод ejbStore, по дает возможность периодически обновлять компонент информацией, содержащейся в ос-
Серверы приложений 623 новном источнике данных. Это может быть полезно, например, для компонента, который осуществляет передачу котировок ценных бумаг. В строке 19 этот компонент определяется как компонент, предназначенный для использования в составе стандартной транзакции. Элемент read-timeout-secotids для компонентов типа Read-Write но используется, а для компонентов типа Read-Only его значение задает интервалы времени в секундах между последовательными обновлениями базы данных. Если значение установлено в 0, компонент типа Read-Only обновляется только при его создании. 1 <?мп1 version = "1.0" encoding = "OTF-8"?> 2 3<!-- weblogic-ejb-jar.xml - дескриптор развертывания приложения --> 4<!-- Bookstore описывает специфические для weblogic свойства для каждого компонента --> 5 6 «C'DOCTYPE weblogic-ejb-jar PUBLIC 7 '-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EM" 8 'http://www .bea.com/servers/wls510/dtd/weblogic-ejb-jar.dtd'> 9 10 <!-' Собственный дескриптор weblogic EJB-коыпонента --> 11 <weblogic-ejb-jar> 12 13 <weblogic-enterprise-b©an> 14 <ejb-name>Customer</ejb-name> 15 16 <!-- задание свойств кэширования --> 17 <caching-descripfcor> 18 <max-beans-in-cache>100</max-beans-in-cache> 19 <cache-strategy>Read-Write</cache-strategy> 20 <read-timeout-seconds>0</read-timeout-seconds> 21 </caching-descriptor> 22 23 <!-- задание карты соответствий типов для компонента --> 24 <persistence-descriptor> 25 26 <persistence-type> 27 <type-identifier> 28 HebLogic_CMP_PDBMS 29 </type-±dentifier> 30 <type-version>5.1.0</type-version> 31 <type-storage> 32 META-INF/weblogic-cmp-rdbms-jar-Customer.xml 33 </type-storage> 34 </persistence-type> 35 36 <persistence-use> 37 <type-identifier> 38 WebLogic_CMP_HDBMS 39 </type-idsntifier> 40 <type-version>5.1.0</type-version> 41 </pers±stence-use> 42 </persistence-descriptor> 43 44 <!-- параметры транзакции -*•> 45 <transaction-descriptor> 4 6 <trans-timeout-seconds>200</trans-timeout-seconda>
624 Глава 13 47 </transaction-descriptor> 4В 49 <!— задание соответствий ссылок и имен ejb-компонентов —> 50 <reference-descriptor> 51 <ejb-reference-description> 52 <ejb-ref-name>ejb/Order</ejb-rer"-name> 53 <jndi-name>ejb/Order</jndi-name> 54 </ejb-reference-description> 55 56 <ejb-reference-description> 57 <ejb-ref-name>ejb/SequenceFaetory</ejb-ref-name> 58 <jndi-najne>ejb/SequenceFactory</jndi-name> 59 </ejb-reference-description> 60 61 <ejb-reference-description> 62 <eib-ref-name>ejb/Address</ejb-ref-riame> 63 <jndi-naine>ejb/Address</jndi-name> 64 </ejb-reference-description> 65 66 </reference-descriptor> 67 68 <!-- присвоение имени JHPI ВJB-компоненту —> 69 <jndi-name>ejb/Cu8tomer</3ndl-name> 70 71 </weblogic-enterprise-bean> О— конец дескриптора Customer --> 72 73 <!-- дескриптор weblogic EJB-компонентов SequenceFactory —> 74 <weblogic-enterprise-bean> 75 76 <ejb-name>SequenceFactory</ejb-naae> 77 78 <!-- управление поведением при кэшировании компонентов --> 79 <caching-descriptor> 80 <max-beans-in-cache>100</max-beans-iiL-cache> 81 <idle-tlmeout-seconds>20</idle-timeout-seconds> 82 <cache-strategy>Read-Write</cache-strategy> 83 <read-timeout-ssconds>0</read-timeout-seconds> 84 </caching-descriptor> 85 86 <!-- задание соответствия между компонентом и дескриптором смр —> 87 <persistence-descriptor> 88 <persistence-type> 89 <type-identifier> 90 WebLogic_CMP_RDBMS 91 </type-identifier> 92 <type-version>5.1.0</type-vers±on> 93 <type-storage> 94 META-IHF/weblogic-cmp-rdbms-jar-Sequence.xn»l 95 </type-storage> 96 </persistence-type> 97 98 <persistence-use> 99 <type-identifier> 100 WebLogic_CMP_RDBMS ' 101 </type-identifier>
Серверы приложений 102 <type-version>5.1.0</type-version> 103 </porsistence-use> 104 </persistence-descriptor> 105 106 <!-- параметры управления транзакцией —> 107 <transaction-doscriptor> 108 <trans-tin\eout-seconds>200</trans-timeout-seconds> 109 </transaction-descriptor> 110 111 <!-- присвоение компоненту имени JHDI --> 112 <jndi-name>ejb/SequenceFactory</jndi-name> 113 114 </weblogic-enterprise-bean> 115 <!-- конец дескриптора SequenceFactory —> 116 117 <!-- дескриптор weblogic для EJB-компонента Order --> 116 <weblogic-enterprise-bean> 119 <ejb-name>Order<l/ejb-name> 120 121 <!-- определяет свойства кэшированияг установленные по умолчанию —> 122 <caching-descriptor> 123 <max-beans-in-cache>100</max-beans-in-cache> 124 <idle-timeout-seconds>20</idle-timeout-seconds> 125 <eache-strategy>Read-Write</caehe-strategy> 126 <cead-timeout-seconda>u</iead-titfte«yut.-8econds> 127 </eaching-descriptor> 128 129 <!-- задание соответствия между компонентом и дескриптором СМР --> 130 <persistence-descriptor> 131 <persistence-type> 132 <type-identifi.er> 133 WebLogic_CMP_RDBMS 134 </type-iderttifier> 135 <type-version>5.1.0</type-version> 136 <type-storage> 137 META-INF/weblogic-cmp-rdbms-jar-order.xiril 138 </type-storage> 139 </persistence-type> 140 141 <persistence-use> 142 <type-identifier> 143 WebLogic_CMP_RDBMS 144 </type-ideiitifier> 145 <type-version>5.1.0</type-version> 146 </persistence-use> 147 </persistence-descriptor> 148 149 <!-- определение атрибутов транзакции --> 150 <transaction-descriptor> 151 <trans-timeovit-seconds>200</trans-timeout-seconds> 152 </transaction-descriptor> 153 154 <!-- задание соответствий между ссылками на компоненты и именами JNDI -->
626 Глава 13 155 <reference-descriptor> 156 <ejb-reference-description> 157 <ejb-ref-name>ejb/SequenceFactory</ejb-ref-name> 15B <jndi-nanie>ejb/SequenceFactory</jndi-name> 159 </ejb-reference-description> 160 161 162 <ejb-reference-description> 163 <ejb-ref-name>ejb/OrderProduct</ejb-ref-name> 164 <jndi-namo>ejb/OrderProd4ict</3nd.i-naine> 165 </ejb-i:eference-description> 166 167 <eib-referenc:e-'description> 168 <ejb-ref-name>ejb/Customer</ejb-ref-name> 169 <jndi-name>ejb/Customer</jndi-name> 170 </ejb-reference-description> 171 172 </reference-descriptor> 173 <jndi-name>ejb/Order</jndi-name> 174 175 </weblogic-enterpr±ae-bean> <!— конец дескриптора Order --> 176 177 <!— дескриптор развертывания weblogic для EJB-компонента Address —> 178 <weblogic-enterprise-bean> 179 <ejb-name>Address</ejb-name> 180 181 <!— определение свойств кэширования для компонента —> 182 <caching-descriptor> 183 <max-beans-in-cache>l0 0</max-beans-in-cac:he> 184 <idle-timeout-seconds>20</idle-timeout-seconds> 185 <cache-strategy>Read-Write</cache-strategy> 186 <read-timeout-seconds>0</read-timeout-seconds> 187 </caching~descriptor> 186 189 <!-- задание соответствия между EJB-компонентом и определенным дескриптором amp --> 190 <persistence-descriptor> 191 <persistence-type> 192 <type-identifier> 193 WebLogic_CMP_RDBMS 194 </type-identifier> 195 <type-version>5,1.0</type-version> 196 <type-storage> 197 METft-INF/weblogic-cmp-rdbms-jar-address.xml 198 </type-storage> 199 </persistence-type> 200 201 <persistenco-use> 202 <type-identifier> 203 WebLogic_CMP_RDBMS 204 </type-identifier> 205 <type-version>5.1.0</type-version> 206 </persistence-use> 207 </persistence-descriptor> 208
Серверы приложений 6 209 <!-- определение параметров транзакции—> 210 <transaction-descriptor> 211 <trans-timeout-seconds>200</trans-tinieout-seeonds> 212 </transaction-descriptor> 213 214 <!-- задание карти соответствий для ссыпок на имена JNDI компонентов —> 215 <reference-descriptor> 216 <ejb-reference-description> 217 <ejb-ref-name>ejb/SequenceFactory</ejb-ref-name> 218 <jndi-name>ejb/SequenceFactory</jndi-name> 219 </ejb-referonce-description> 220 </reference-descriptor> 221 222 <!-- присвоение имени JNDI этому компоненту --> 223 <jndi-name>ejb/Address</jndi-name> 224 225 </weblogic-enterprise-bean> <!-- end Address descriptor --> 226 227 <!— дескриптор развертывания weblogic для EJE-компонента Order-Product —> 228 <weblogic-enterprise-bean> 229 <ejb-name>OrderFroduct</ejb-naine> 230 231 <!-- задание свойств кэширования по умолчанию --> 232 <caching-descriptor> 233 <max-be-ans-in-cache>100</niax-beans-in-cache> 234 <idle-timeout-seconds>20</idle-timeout-seeonds> 235 <cache-atrategy>Read-Write</cache-strategy> 236 <read-timeout-seconds>0</read-timeout-seconds> 237 </caching-descriptor> 238 239 <!-- задание соответствия этого компонента определенному дескриптору СМР--> 240 <persistence-descriptor> 241 <persistence-type> 242 <type-identifier> 243 WebLogic_CMP_RDBMS 244 </type-identifier> 245 <type-version>5.1.0</type-version> 246 <type-storage> 247 META-INF/weblogic-cmp-rdbms-jar-orderProduct.xml 248 </type-storage> 249 </persistence-type> 250 251 <persistence-use> 252 <type-identifier> 253 WebLogic_CMP_RDBMS 254 </type-ident-i£ier> 255 <type-version>5.1.0</type-version> 256 </persistence-use> 257 </persistence-descriptor> 258 259 <!-- задание соответствий для ссылок на имена JNDI компонента --> 260 <reference-descriptor>
628 Глава 13 261 <ejb-reference-description> 262 <ejb-ref-name>ejb/Product</ejb-ref-name> 263 <jndi-name>ejb/Product</3ndi-naioe> 264 </ejb-reference-deBcription> 265 </reference-descriptor> 266 267 <!-- присвоение имени JNDI этому компоненту —> 268 <jndi-name>ejb/OrderProduct</jndi-name> 269 270 </weblogic-enterprise-oean> 271 <l-- конец дескриптора OrderEroduct —> 272 273 <!-- дескриптор развертывания weblogic EJB-компонекта Product — 274 <weblogic-enterprise-bean> 275 <ejb-name>Product</ejb-name> 276 277 <!-- определение свойств кэширования для EJB-компонента --> 278 <caching-descriptor> 279 <idle-timeout-seconds>20</idle-timeoiit-secojnds> 280 <cache-strategy>Read-Write</cache-strategy> 281 <read-timeout-seconds>0</read-timeout-seconds> 282 </eaching-descriptor> 283 284 <!— задание соответствия между этим компонентом и его дескриптором СМР --> 285 <persistence-descriptor> 286 <persistence-type> 287 <type-identifier> 288 WebLogic_CMP_RDBMS 289 </type-identifier> 290 <type-version>5.1.0</type-version> 291 <type-storage> 292 META-INF/weblogic-cmp-rdbms-jar-product.xml 293 </type-storage> 294 </persistence-type> 295 296 <persistance-use> 297 <type-identifier> 298 WebLogic_CMP_RDBMS 299 </type-identifier> 300 <type-version>5.1.0</type-version> 301 </persistence-use> 302 </persistence-descriptor> 303 304 <!-- определение параметров транзакции —> 305 <transaction-descriptor> 306 <trans-timeout-seconds>200</trans-timeout-aeconds> 307 </transaction-descriptor> 308 309 <!-- присвоение имени JNDI --> 310 <jndi-r»ame>e5b/Product</jndi-name> 311 312 </weblogic-enterprise-bean> <!— конец дескриптора Product —> 313 314 <!-- дескриптор развертывания weblogic EJB-компонекта ShoppingCart —>
Серверы приложений 629 315 <weblogic-enterprise-bean> 316 <e3b-name>SboppingCart</ejb-name> 317 318 <!— определение свойств кэширования, установленных по умолчанию —> 319 <caching-dascriptor> 320 <max-beans-in-cache>100</niax-beans-in-cache> 321 <idle-timeout-seconds>20</idle-timeout-seconds> 322 <cache-strategy>Read-Write</cache-strategy> 323 <read-timeciut-seconds>0</read-timeout-seconds> 324 </caching-descriptor> 325 326 <!— назначение каталога для хранения компонента --> 327 <persistence-descriptor> 328 <stateful-session-pexsistent-store-dir> 329 /config/deitel/ 330 </stateful-session-persistent-store-dir> 331 </pexsistence-descriptor> 332 333 <!-- определение атрибутов транзакции —> 334 <transaction-descriptor> 335 <trans-timeout-seconds>200</trans-timeout-seconds> 336 </transaction-descriptor> 337 339 <!— задание соответствия между ссылками на EJB-компоненты и именами JNDI --> 339 <reference-descriptor> 340 341 <ejb-re;fe,rence-descripfcion> 342 <ejb-ref-name>ejb/Product</ejb-ref-name> 343 <j ndi~name>ejb/Produc t</j ndi-name> 344 </ejb-reference-description> 345 346 <ejb-reference-d©scription> 347 <e3b-ref-name>ejb/0rder</ejb-ref-name> 348 <indi-name>ejb/Order</3ndi-name> 349 </ejb-reference-descripti©n> 350 351 </re£erence-descriptor> 352 <jndi-name>ejb/ShoppingCart</jndi-name> 353 354 </weblogic-enterprise~bean> <!-- конец дескриптора ShoppingCart --> 355 356 </weblogic-ejb-jar> <!— конец дескриптора weblogic —> Рис. 13.4. Документ weblogic-ejb-jar.xml определяет параметры развертывания для приложения Bookstore Элемент persistence-descriptor (строки 24-42) определяет свойства персистент- ности EJB-компонентов. Компонент Customer использует псрсистсптность, управляемую контейнером (CMP). WebLogic требует, чтобы каждый компонент с СМР имел свой собственный XML-дескриптор. При этом блок управления персистентностью в этом коде просто идентифицирует имя и тип дескриптора управления персистентностью. Элемент persistence-type (строки 26-34) содержит элементы type-identifier (строки 27-29), которые должны иметь значение WebLo-
630 Глава 13 gic_CMP_EDBMS. Элемент type-version имеет значение 5.1.0 для EJB 1.1 и 6.0 для EJB 1.2. Номер версии должен соответствовать номеру, указанному в DTD weblogie-ejb-jar. Элемент type-storage (строки 31-33) задает местоположение дескриптора СМР: файла, который мы создадим далее. Может быть определено несколько типов сохраняемости persistence-type. В следующем блоке, persistence-use, выбирается, какие типы сохраняемости использовать. В строках 36-41 WebLogic предписывается использовать тип сохраняемости, заданный в предшествующем элементе. На данный момент элемент transaction-descriptor содержит только одну опцию: trans-timeout-seconds (строка 46). Если транзакция продолжается свыше указанной длительности, она будет отменена. Элемент reference- descriptor (строки 50-66) связывает каждую ссылку с корректным именем JNDI. Каждый блок содержит элементы ejo-ref-name и jndi-name для вычисления ссылок на другие EJB-компоненты. Дескрипторы для всех других компонентов-сущностей BJB имеют тот же формат, что и дескриптор для Customer, Единственными различиями являются ссылка на файл дескриптора RDBMS и ссылки на каждый из EJB-компонентов. Сеансовый компонент EJB без состояния ShoppingCart (строки 315-354) требует задания несколько иной информации о развертывании. Для сеансовых компонентов без состояния файлы дескрипторов сохраняемости RDBMS не нужны. Вместо этого для них требуется указать каталог для хранения сохраняемых сеансов и каталог для хранения пассивированных сеансов. Элемент stateful-session- persistent-store-dir в составе элемента persistence-descriptor определяет, где контейнер должен хранить пассивированные сеансы [4]. В таблице на рис. 13.5 приведены некоторые необязательные элементы в документе weblogic-ejb-jar.xml. Полный перечень можно найти на сайте edocs.bea.com. Если элемент не определен, WebLogic использует значение но умолчанию. Дескриптор RDBMS описывает взаимодействие компонента-сущности EJB с базой данных. Дескриптор задает пул соединений и имя таблицы, устанавливает соответствие между полями EJB-компонента и полями базы данных., а также определяет пользовательские средства поиска для методов собственного интерфейса. ж^ ЫИ11 Родительский элемент caching- descriptos caching- descriptor persistence- descriptor persistence- descriptor Элемент max-beans-in- free-pool ini tial-bean s- in-free-pool i s-modified- method-name delay-wpdates- until-end-of-tx Описание Допускается дли сеансовых компоненюи EJB без состояния. Определяет максимальное число свободных компонентов, которые могут храниться в пуле. По умолчанию ограничений нет, j Допускается для сеансовых компонентов EJB без | состояния. Определяет число изначальных j экземпляров компонентов. Значение по умолчанию I равно 0, Имя метода, который вызывается при сохранении EJB-компонечта. Метод должен возвращать булево . значение. Если метод возвращает true, EJB-компонент сохранен. При установке значения false таблица базы данных компонентов обновляется после выполнения каждого из методов, Если установлено значение true, база данных обновляется по завершении транзакции. Значение по умолчанию - true.
Серверы приложений 631 Родительский элемент persistence- descriptor persistence- descriptor reference- descriptor resource- descriptor resourcede scrip tor security- role - assignment security- role- assignment weblogic- enterprise- bean Элемент £inder-call- ejbload db-is-shared гезоигсе- deacriptor res-ref-name jndi-патае role-name principal-name enable-call- by- reference ' - Описание Допускается для компонентов-сущностей. Значение true указывает, что компонент загружается после первого к нему обращения с помощью метода поиска. Значение false указывает, что компонент загружается при первом вызове. Значение по умолчанию - false. Допускается для компонентов-сущностей. При значении false предполагается, что компонент имеет эксклюзивный доступ к базе данных и не перезагружает данные. При значении true данные перезагружаются перед каждой транзакцией. Значение по умолчанию - true. Содержит описание мастеров ресурсов, перечисленных в документе ejb-jar.xml Имя ссылки на ресурс, содержащееся в документе eyb-jar.xml Присваивает имя JNDI мастеру ресурсов. Имя роли а системе безопасности, определенное в документе ejb-jar.xml. Связывает имя роли с администратором, определенным в конфи!урации сервера WebLogic. За справкой о допустимых именах администраторов обратитесь к сайту edocs.bea.com. Если EJB-компоненты, размещенные на одном сервере, nt;peMdKjicn по ссылке, задание дли з-ют элемента Значения false приведет к передаче переменных по значению. | Рис. 13.5. Необязательные теги документа weblogic-ejb-jar.xml, не используемые в тексте Дескриптор weblogic-cmp-rdbms-jar-address.xnil (рис. 13.6) следует определению DTD для WebLogic версии 5.1. Элемент webLogic-rdbms-bean (строка 11) является базовым элементом дескриптора. В файле дескриптора может быть только один элемент weblogic-rdbms-bean. Элемент pool-name в строке 14 должен содержать имя пула данных, уже определенное в конфигурации WebLogic. Значение table-name должно соответствовать имени таблицы в источнике данных, которую использует компонент. В нашем примере EJB-компонент Address осуществляет запись в таблицу Address (строка 17). Элемент attribute-map содержит карту соответствий между полями EJB-компонента и полями базы данных, определенными в документе ejb-jar.xml. В нашем примере имена полей базы данных совпадают с именами полей EJB-компонента, но это не обязательно. Определение соответствий в дескрипторе развертывания дает возможность администратору развертывания настраивать EJB-компоненты под конкретные базы данных, не внося изменений в код EJB-компонента. Элемент object-link (строки 21-24) содержит соответствия между полями EJB-компонента и полями базы данных для zipCode. Имена полей компонента и полей базы данных задаются, соответственно, в элементах bean-field и dbms-column.
63 2 Глава 13 1 <?xml version = "1.0" encoding = "UTF-8"?> 2 3 <!— weblogic-cntp-rdbms-jar-address.xml - дескриптор развертывании --> 4 <!-- rdbms для EJB-компонента Address, определяющий свойства базы данных --> 5 6<!DOCTYPE weblogic-rdbms-bean PUBLIC 7 '-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB RDBMS Persistence//EH' 8 'http://www.bea.com/servers/wls510/dtd/ weblogic-rdbms-persistence.dtd'> 9 10<'-- основной блок дескриптора rdbms —> 11 <weblogic-rdbms-baan> 12 13 <!-- задание пула данных для BookstorePool --> 14 <pool-name>BookstorePooK/pool-name> 15 16 <!— задание ADDRESS в качестве таблицы данных--> 17 <table-name>Address</table-name> 18 19 <!-- задание соответствий между полями EJB и полями базы данных --> 20 <attribute-map> 21 <object-link> 22 <bean-field>zipCode</bean-fiald> 23 <dbms-column>zipCode</dbm8-column> 24 </object-link> 25 26 <objact-link> 27 <bean-field>state</bean-field> 28 <dbms-column>state</dbms-column> 29 </object-link> 30 31 <object-link> 32 <bean-field>addressID</bean-field> 33 <dbms-colutnn>addresslD</dbHis-col,umn> 34 </c>bject-link> 35 36 <object-link> 37 <bean-field>streetAddressLine2</bean-field> 38 <dbms-column>streetAddressLine2</dbms-colmnn> 39 </object-link> 40 41 <object-link> 42 <bean-field>country</bean-field> 43 <dbms-coliann>country</dbms-column> 44 </object-link> 45 46 <object-link> 47 <bean-field>streetAddressLinel</bean-field> ■48 <dbms-coltrnin>streetAddressLinel</dbms-column> 49 </object-link> 50 51 <object-link> 52 <bean-field>city</bean-field> 53 <dbni5-colunin>city</dtims-column>
Серверы приложений 633 54 </object-lin)c> 55 56 <object-link> 57 <bean-field>firstName</bean-field> 58 <dbms-colmnn>firstName</dbms-calmnn> 59 </ob3ect-link> 60 61 <object-link> 62 <bean-field>lastName</bean-field> 63 <dbms-eolumn>lastName</dbms-column> 64 </ob3ect-link> 65 66 <object-link> 67 <bean-field>phoneNumber</be an-field> 68 <dbms-column>phoneKumber</dbms-column> 69 </object-link> 70 </attribute-map> 71 72 <options> 73 <use-quoted-names>false</use-quoted-names> 74 </options> 75 7 6 </webloglc-rdbms-bean> <'-- конец дескриптора RDBMS Address --> Рис. 13.6. Документ Weblogk-cmp-rdbms-jar-address.xml определяет свойства контейнерного управления персистентносгью базы данных WebLogic для EJB компонента Address XML-дескриптор WebLogic weblogic-cmp-rdbms-jar-Customer.xml (рис. 13.7) представляет собой дескриптор развертывания базы данных для EJВ-компонента Customer. Дескрипторы развертывания по своей структуре почти идентичны. Для каждого EJB-компонента необходимо предоставить карту соответствий полей, имя таблицы базы данных и пользовательские (создаваемые разработчиком) запросы для методов поиска. 1 <?xml version = "1.0" encoding « "UTF-8"?> 2 3 <!-- weblogic-cmp-rdbms-jar-Custoiuer. xml ejb - дескриптор для —> 4 <!— EJB-компонента CustomerEJB, определяющий свойства rdbms для WebLogic --> 5 6 <!DOCTYPE weblogic-rdbms-bean PUBLIC 7 '-//ВЕЛ Systems, Inc.//DTD WebLogic 5.1.0 EJB RDBMS Persistence//EN' 8 'http;//www.bea.com/servers/wls510/dtd/ weblogic-rdbms-persistence.dtd'> Э 10 <?— элемент, содержащий свойства rdbms для EJB-компонента Customer —> 11 <weblogie-rdbms-bean> 12 13 <!-- назначение этого компонента пулу с именем BookatorePool --> 14 <pool-name>BooJcstorePool</pool-name> 15 16 <!-- назначение этого компонента таблице с именем CDSTOMER —> 17 <table-name>Customer</table-iiame> 18
19 <!-- элемент, содержащий карту соответствия полей --> 20 <attribute-map> 21 22 <!-- соответствие полей для customerlD --> 23 <object-linlt> 24 <bean-field>customerID</bean-field> 25 <dbms-coliHon>customerID</dDbras-colmnn> 26 </object-link> 27 28 <!-- соответствие полей для creditCardExpirationDate --> 29 <object-link> 30 <bean-field>creditCardExpirationDate</bean-field> 31 <dbms-colwm>creditCardExpirationDate</dbms-column> 32 </object-link> 33 34 <!-- соответствие полей для shippingAddressID —> 35 <objeet-link> 36 <bean-field>shippingAddressID</bean-field> 37 <dbms-colmnn>shippingAddressID</dbnia-column> 38 </object-link> 39 40 <\— соответствие полей для billingAddressID —> 41 <object-linJc> 42 <bean-field>billingAddressID</bean-field> 43 <dbms-colujnn>billingAddxessID</dbms-column> 44 </ob;ject-link> 45 4 6 <!-- соответствие полей для passwordHint --> 47 <object-link> 48 <bean-field>paaswordHint</bean-field> 49 <dbms-colujnn>passwordHint</dbma-coluiiiii> 50 </object-link> 51 52 <?-- соответствие полей для creditCardName --> 53 <object-linJc> 54 <bean-field>creditCardName</bean-field> 55 <dbms-column>creditCardHame</dbma-col'UJiin> 56 </object-link> 57 58 <\— соответствие полей для firstName —> 59 <0b3ecfc-li.nk> 60 <bean-field>firstName</bean-field> 61 <dbiBS-column>firstName</dbms-column> 62 </object-link> 63 64 <\— соответствие полей для password --> 65 <object-linlc> 66 <bean-field>password</bean-field> 67 <obms-column>password</dbms-col'umn> 68 </ob3ect-link> 69 70 <!-- соответствие полей для lastName —> 71 <object-link> 72 <bean-field>lastName</bean-£ield> 73 <dbma-column>lastNanie</dbms-columii> 74 </object-link> 75 76 <•-- соответствие полей для userlD —>
Серверы приложений 635 77 <object-linfc> 78 <bean-field>userID</bean-field> 79 <dfoms-column>userID</dbms-column> 80 </ob3ect-link> 81 82 <!-- соответствие полей для creditCardNumber --> 83 <object-link> 84 <bean-field>creditCardNumber</bean-field> в 5 <dbms-column>credi tCardN«inber</dbms-column> 86 </object-linfc> 87 В 8 </attribute-map> 89 90 <!-- список нестандартных методов поиска --> 91 <fiiwter-list> 92 93 <!--метод поиска для findByUserlD —> 94 <finder> 95 <method-name>f indByUserID</metliocl-name> 96 <method-params> 97 <method-param>Java.lang.String</method-param> 98 </method-params> 99 100 <!-- получение полей, для которых значение userlD совпадает со строкой параметра --> 101 <finder-query> 102 <![CDATA[< lifce userlD $0 )]]> 103 </finder-query> 104 </finder> 105 106 <!--метод поиска для findByLogin —> 107 <finder> 108 <method-name>find8yLogin</method-name> 109 <method-params> 110 <njethod-param>;java. lang. String</method-param> 111 <nethod-paran>j ava.lang,Str±ng</methQd-param> 112 </method-params> 113 114 <<— поля, для которых значения userlD и password совпадают со значениями параметров --> 115 <finder-query> 116 <![CDATA[(S ( like userlD $0 )( like password $1 ))]]> 117 </finder-query> 118 </finder> 119 120 </finder-list> 121 122 <!— дополнительные опции --> 123 <options> 124 <use-quoted-names>false</use-quoted-names> 125 </options> 126 127 </weblogic-rdbms-bean> 128 <!— конец дескриптора rdbms для CustomerEJB —> Рис. 13.7. Документ WebLogic-cmp-rdbmHar~Cust°'r|er.xml определяет свойства контейнерного управления персистентностью базы данных WebLogic для EJ В-компонента CustomerEJB
636 Глава 13 Элемент finder-list (строки 91-120) содержит методы поиска для нестандартного EJB-компонента. WebLogic требует предоставлять элемент finder для каждого нестандартного метода поиска я собственном интерфейсе. Вы должны определить имя метода, параметры и запрос* который будет использоваться. Значением элемента method-value (строка 95) является строка, которая соответствует методу поиска, определенному в собственном интерфейсе. Элемент method-params (строки 96-98) содержит элемент method-param (строка 97), который определяет полный тип всех передаваемых параметров (например, Java, la rig. String). Finder-query представляет собой запрос, записанный на языке Web Logic Query Language (WQL). Выражения WQL следует помещать в разделы CD ATA, чтобы не пользоваться специальными символами, В таблице на рис. 13.8 представлены операторы и примеры синтаксиса WQL. Операндами выражений могут быть параметры, литералы, поля bean-fields в составе этого дескриптора или другие выражения. Синтаксис $п означает, что оператором является параметр, а п соответствует порядковому номеру передаваемого параметра, начиная с 0. Литералы должны всегда заключаться в одинарные кавычки. В строках 107-118 определен метод FindByLogin, который принимает два параметра и возвращает только те поля, которые отвечают этим двум параметрам. Оператор = < > <= >= i & 1 like isNull isNotNull orderBy Функция J Пример синтаксиса равно меньше больше меньше или равно больше или равно не (not) и (and) или (ог) равенство строке, % соответствует групповому символу замещения проверка на равенство null проверка на не равенство null упорядочение по именам столбцов, debc указывает на упорядочение по убыванию ( = ID $0» { < price $0) ( > quantity $0) ( <= operandi operandi) ( >= operandi operandi) ( t (=quantity '0')) (6 (<price $01 txjuantity $Ш (1 (>quantity '0') (>onOrder $0) ( like ,%Java%') (ieNuLL bookID) (isNotNull bookID) (like '%Java%■ orderBy 'Price') Рис. 13.8. Некоторые операторы языка WebLogic Query Language и их гримеры На рис. с 13.9 по 13.12 представлены дескрипторы контейнерного управления персистейтностью (СМР), которые следуют одной и той же базовой структуре. 1 <?xml version = "1.0й encoding = "OTF-8"?> 2 3<i-- weblogic-cmp-rdbms-jar-order.xml ejb - дескриптор для —> t <!— ЕЛЗ-компо&енФа OrderEJB, определяющий свойства rdbms для WebLogic —> 5 6 <!DOCTYPE weblogic-rdbms-bean PUBLIC 7 '-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB RDBMS Persistence//EH'
Серверы приложений 637 8 'http://www.bea.com/servers/wls510/dtd/ weblogic-rdbms-persistence.dtd'> 9 10 11<!-- элемент, содержащий свойства rdbms для OrderEJB --> 12 <weblogic-rdbras-bean> 13 14 <!— назначение этого компонента пулу с именем BookstorePool —> 15 <pool-name>BookstorePooi</pool-name> 16 17 18 <!-- назначение этого компонента таблице с именем CUSTCHERORDEKS —> 19 <table-name>CustomerOrders</table-name> 20 21 <!-- элемент, содержащий карту соответствий полей —> 22 <attribute-map> ^ 23 24 25 <?-- соответствие полей для orderDate —> 26 <object-link> 27 <bean-field>orderDate</bean-field> 28 <dbras-column>orderDate</dbms-column> 29 </object-link> 30 31 32 <!-- соответствие полей для shipped —> 33 <object-link> 34 <bean-field>shipped</bean-field> 35 <dbms-column>ehipped</dbms-column> 36 </object-link> 37 38 39 <!— соответствие полей для customerID —> 40 <object-link> 41 <bean-field>customerID</bean-field> 42 <dbms-column>customerID</dbnis-column> 43 </ob3ect-link> 44 45 <!— соответствие полей для orderlD --> 4 6 <object-link> 47 <bean-field>orderID</bean-field> 48 <dbms-column>orderID</dbms-column> 49 </object-link> 50 51 </attribute-map> 52 53 <finder-list> 54 55 <!-- метод поиска для findByCustomerlD —> 56 <finder> 57 <jnethod-name>findByCustomerID</mathod-name> 58 <method-params> 59 <method-param>java.ling.Integer</method-param> 60 </method-params> 61
638 Глава 13 62 <<— возвращает перечисление, для которого cuatomerlD = параметр --> 63 <finder-query> 64 <![СОАГА[( = customerlD $0 )]]> 65 </finder-guery> 66 </finder> 67 68 </finder-list> 69 70 <!— дополнительные опции --> 71 <options> 72 <use-<juoted-naJnes>false</use-quoted-names> 73 </options> 74 75 </weblogic-rdbms-bean> 76 <!-- конец дескриптора rdbms для OrderEJB —> Рис, 13.9. Документ Weblogk-cmp-rdbms-jar-order.xml определяет свойства контейнерного управления персистентностъю базы данных WebLogic для EJ В-компонента OrderEJB 1 <?xml version = "1.0" encoding = "UTF-8"?> 2 3 <P— weblogie-cn£>-rdbms-jar-orderProduct. xml ejb - дескриптор для —> 4<!-- EJB-хомпонента OrderProductEJB, определяющий свойства rdbms для WebLogic --> 5 6 <!DOCTYPE weblogic-rdbms-bean PDBLIC 7 '-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB BDBMS Persistence//EN' 8 'http://wwM.bea.com/servers/wls510/dtd/ weblogic-rdbms-persistence.dtd'> 9 10 <!— элемент, содержаний свойства rdbms для OrderProductsEJB —> 11 <weblogic-rdbms-bean> 12 13 <!— назначение этого компонента пулу с именем BookstoreFool —> 14 <pool-name>BooIcstorePool</pool-name> 15 16 <!— назначение этого компонента таблице с именем QRDERPRODUCT —> 17 <table-name>OrderProduct</table-nairie> 18 19 <!-- элемент, содержаний карту соответствий полей --> 20 <attribute-map> 21 22 <!-- соответствие полей для quantity --> 23 <object-link> 24 <bean-field>quantity</bean-field> 25 <dbms-column>quantity</dbms-coliimn> 26 </object-link> 27 28 <!-- соответствие полей для ISBN --> 29 <object-linlc> 30 <bean-field>ISBN</bean-field> 31 <dbms-coltimn>ISBN</dbms-coluran> 32 </object-link> 33
Серверы приложений 639 34 <!— соответствие полей для orderlD —> 35 <object-link> 36 <bean-field>order!D</bean-field> 37 <dbms-column>orderIIX/dbms-column> 38 </obiect-link> 39 40 </attribute-map> 41 42 43 <f±nder-list> 44 45 <!-- метод поиска для findByOrderlD —> 46 <finder> 47 <method-name>findByOrderID</method-name> 48 <method-params> 49 <method-param>java.lang.lnteger</method-param> 50 </method-params> 51 52 <!— выборка полей, для которых значение orderlD совпадает с параметром --> 53 <finder-query> 54 <![CDATA[( like orderlD $0 )]]> 55 </£inder-query> 56 </finder> 57 58 </finder-list> 59 60 <!— дополнительные опции --> 61 <options> 62 <use-quoted-names>false</uae-quoted-names> 63 </options> 64 65 </weblogic-rdbms-bean> <j— конец дескриптора OrderProduct --> Рис. 13.10. Документ Weblogk-cmp-rdbms-jar-orderProduct.xml определяет свойства контейнерного управления перс и стентн остью базы данных WebLogic для EJB-компонента OrderProduct 1 <?xml version = Э1.0" encoding = ЭГЛТ-8"?> 2 3 <!— weblogic-cmp-rdbms-jar-product.xml - дескриптор для --> 4 <!-- EJB-компонента ProductsJB, определяющий свойства rdbms для WebLogic —> 5 6 <!DOCTYPE weblogic-rdbms-bean PUBLIC 7 '-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB RDBMS Persistence/ZEN1 8 'http://www.bea.com/servers/wls510/dtd/ weblogic-rdbms-persistence.dtd' > 9 10 <!— элемент, содержаний свойства rdbms для ProductEJB --> 11 <weblogic-rdbms-bean> 12 13 <!-- назначение этого компонента пулу с именем BooltstorePool --> 14 <pool-name>Books torePooK/pool-name> 15
642 Глава 13 20 <attribute-map> 21 22 <!— соответствия полей для addressID —> 23 <object-link> 24 <bean-field>tableMame</bean-field> 25 <dbms-column>tableSanie</dbma-column> 26 </o.toject-liiik> 27 28 <!-- соответствия попей Для primaryKey --> 2 9 <obj ect-1ink> 30 <beAn-field>nextlD</bean-field> 31 ^dbms-coluiMi>nextID</dbms-column?- 32 </object-link> 33 34 </attribute-map> 35 36 <options> 37 <use-quoted-namfis>false</use-quoted~names> 38 </options> 39 40 </wefalogic-rdbros-bean> Рис. 13.12. Документ Weblogic-cmp-rdbms-jar-sequence,xml определяет свойства контейнерного управления персистентностью бааы данных WebLogic для EJB-компонента SequenceFactory , Наконец, для ссылок, на EJB-xomitohbhtki в сервлетах должны быть установлены соответствия с именами JNDI для каждого EJB-компонента. Эти соответствия определены в дескрипторе развертывания weblogie.xml в каталоге WEB-INF Web-приложения. На рис. 13.13 представлена карта соответствий для сервлетов приложения Bookstore. Необязательный элемент description содержит краткое описание Web-яриложения. Элемент reference-descriptor является единственным обязательным элементом для нашего приложения. Этот элемент устанавливает соответствия между ссылками, определенными в документе web.xml, и именами JNDI EJB-компонентов, на которые указывают ссылки. Мы присвоили ссылкам имена, совпадающие с именами JNDI, но это не является обязательным. Каждый дескриптор ссылки помещен внутрь элемента ejb-reference-description. При установке соответствий требуется, чтобы элементы ejb-ref-name и jndi-name содержались внутри элемента ejb-reference-description. E строках 22-29 устанавливается соответствие между ссылкой на EJB-компонент ShoppingCart и его именем JNDI. 1 <?xml version = "1.0" encoding = "UTF-8"?> 2 3 <!— Дескриптор развертывания weblogie.xml для сервлетов, —> 4 <!-- устанавливающий соответствия между ссылками на еjb-хомпонеяты и именами JNDI —> 5 6 <!DOCTYPE weblogic-web-app PUBLIC 7 "-//BEA Systems, Inc.//DTD Web Application 6.0//EM" 8 "http://www.bea.com/servers/wlsfiOO/dtd/weblogic-web-jar.dtd"> 9 10<!-- основной блох дескриптора --> 11 <weblogic-web-app> 12 13 <!-- Необязательный блок, содержащий описание war-файла. --> 14 <description>
Серверы приложений 643 15 Bookstore servlets 16 </description> 17 18 <!-- блок, содержащий карту соответствий для ссыпок на ejb-компоненты —> 19 <reference-descriptor> 20 21 <!-' индивидуальная карта соответствий --> 22 <ejb-reference-description> 23 24 <!--имя ссылки, определенное в web.xml —> 25 <ejb-ref-name>ejb/SlioppingCart</ejb-ref-name> 26 27 <!— имя JNDI, заданное в weblogie-ejb-jar.xml --> 28 <3ndi-name>ejb/ShoppingCart</jndi-name> 29 </ejb-refer/ence-description> 30 31 <!-- индивидуальная карта соответствия —> 32 <ejb-refereace-deacription> 33 <e j b-re £-name>ejb/Product</e j b-ref-name> 34 <jndi-name>ejb/Product</jndi-najBe> 35 </ejb-reference-description> 36 37 <!— индивидуальная карта соответствия --> 38 <ejb-reference-description> 39 <ejb-re£-nanie>ejb/Customer</ejb-ref-name> 40 <jndi-narae>ejb/Customer</jndi-name> 41 </ejb-reference-description> 42 43 <!"" индивидуальная карта соответствия --> 44 <ejb-reference-description> 45 <ejb-re£-name>ejb/Order</ejb-ref-name> 46 <jndi-name>ejb/Order</jndi-name> 47 </ejb-reference-description> 46 49 </reference-descriptor> 50 51 </weblogic-web-app> 52 <!-- конец дескриптора сервлета —> Рис. 13.13. Дескриптор развертывания Weblogic.xmt Web-приложения После того как все дескрипторы развертывания для нашего примера рассмотрены, можно приступать к процессу развертывания. В нашем приложении используется синтаксический анализатор Xalan версии 2.1, поэтому укажите путь к файлам xalan.jar и xerces.jar при установке переменной окружения CLASSPATH в командном файле startWeblogic.cmd. Ваша запись для установки переменной окружения classpath должна выглядеть следующим образом: set CIASSPATH=.; c:\xalan-j_2_l_0\xerces,jar; c:\xalan- j_2_l_0\xalan.jar; ,\lib\weblogXc_sp.jar; .\ejb\weblogic.jar; с:\cioudscape_3.6\ lib\cloudscape.jar Следует заменить c:\xalan-j_2_l_0 на фактический путь к JAH-файлам Xalan. Выполните командный файл startWeblogic.cmd и укажите в адресной строке вашего Web-браузера URL localhost:7001/bookstore, чтобы получить доступ в книжный Internet-магазин.
644 Глава 13 13.5. Развертывание приложения Deitel Bookstore на сервере IBM WebSphere В этом разделе будет рассмотрен процесс настройки и развертывание приложения Deitel Bookstore на сервере IBM WebSphere 4.0. Конкретную информацию по установке сервера приложений в вашей системе можно найти в документации на сервер WebSphere. Предполагается, что Cloudscape установлен в каталоге C:\cloudscape_3.6, сервер приложений WebSphere установлен в каталоге c:\Web- Sphere\AppServer, а сервер IBM HTTP Server (входящий в пакет установки) установлен в каталоге c:\IBM HTTP Server. Также необходимо скопировать файлы xalan.jar и xerces.jar в каталог c:\WcbSphere\AppServer\lib\ejit, чтобы ваше приложение имело доступ к синтаксическому анализатору XML и XSLT-преобра- зователю. Далее, выполните сценарий StartServerBasic, содержащийся в каталоге WebSphere\AppServer\bin, чтобы запустить сервер приложений. По завершении загрузки сервера укажите URL localhost:9090/admin в Web-браузере. Вы можете указать имя пользователя на странице входа: это имя используется для отслеживания изменений, а не для целей безопасности. Чтобы настроить драйвер JDBC и источник данных, выберите Resources > JDBC Drivers, затем щелкните на кнопке New в правой панели. Введите e:\eloudseape_3.6\lib\cloudscape.jar в качестве пути Server Class Path, Bookstore в качестве имени Name и COM.cloudscape.co- re.LocalConncctionPoolDatflSource в качестве имени класса реализации Implementation Class Name. Введя эти значения, щелкните на ОК и сохраните конфигурацию. Раскройте созданный перед этим драйвер (Bookstore) и выберите Data Source. Щелкните на кнопке New и заполните поля следующим образом. В поле Name введите Bookstore, в поле JNDI Name введите jdbc/Bookstore, а в поле Database Name — полный путь к базе данных (например, c:\cloudscape_3.6\data- bases\bookstore\). Щелкните на ОК и сохраните конфигурацию. Далее, необходимо сформировать дескрипторы развертывания для сервера WebSphere. В состав WebSphere входит инструментальное средство для создания дескрипторов развертывания. Откройте средство Application Assembly Tool, выполнив сценарий сборки, содержащийся в каталоге WebSphere/ AppServer/bin. По завершении загрузки инструментального средства откройте вкладку Existing в окне приветствия. Затем введите полный путь к архиву bookstore.ear, либо выберите опцию просмотра, чтобы найти этот файл. Щелкните на Open, чтобы открыть файл и начать настройку параметров развертывания. В левой панели раскройте Bookstore > EJB modules > EJBs > Session Beans > Shopping Cart. Выберите EJB-компонент ShoppingCart и откройте вкладку Bindings в главной панели (в нижней части правой панели). Введите ShoppingCart в качестве имени JNDI Name. Выберите EJB References из правой верхней панели. Проверьте, что выбрано ejb/Order, и воспользуйтесь полем с раскрывающимся списком Link, чтобы выбрать соответствующую ссылку. Вы также должны открыть вкладку Bindings на главной панели и ввести Order в качестве имени JNDI Name для ссылки. Выполните аналогичные действия, чтобы установить соответствие между ссылкой ejb\Product и EJB-компонентом Product. Выберите Bookstore > EJB modules > EJBs > Entity Beans > Address. Откройте вкладку Bindings в главной панели, введите Address в качестве имени JNDI Name и установите jdbc/Bookstore в качестве имени JNDI Name источника даппых. Выберите EJB References в верхней панели и установите соответствие между ссылкой ejb/SequenceFactory и компонентом SequenceFactory с именем JNDI Name SequenceFactory. Настройте ссылки на EJB-компоненты для других EJB таким же образом.
Серверы приложений 645 Определив имена JNDI и ссылки на EJВ-компоненты для EJB Customer, выберите Method Extensions. Вы должны определить собственные запросы поиска (finder) для методов findByUserlD и findByLogin. Чтобы определить собственный запрос, выберите метод, установите флажок Finder Descriptor и задайте соответствующее предложение WHERE (рис. 13.14). Предложение WHERE будет автоматически помещено в операторьг SQL SELECT и FROM: вам нужно лишь определить его. WebSphere определяет имя таблицы, исходя из имени EJB-компонента, поэтому для Того, чтобы задать другую таблицу, необходимо переименовать EJB-компо- нент. При развертывании приложения Deitel Bookstore не забудьте выбрать EJB-компонент Order и изменить имя EJB-компонента на CustomerOrders, которое представляет собой имя таблицы, хранящей информацию о заказе. Компонент Customer Customer Order OrderPtoduct Product ProduCt Метод findByLogin findByUserlD findByCustomerlD findByOrderlD findByAllProductS findByTitle Предложение WHERE userlD = ? AND password « ? userlD = ? customerID = ? orderlD = ? j 1 *= 1 title like ? Рис. 13.14. Предложения WHERE для методов поиска в приложении Deitel Bookstore Чтобы настроить сервлеты в WebSphere, необходимо выбрать Bookstore > Web Modules > Servlets > EJB references и установить соответствия для каждой из ссылок, как это делалось для EJB-компонентов. Установив соответствия для ссылок, можно приступить к формированию кода для развергывання. Выберите Generate code for deployment из меню File. В текстовом поле Deployment module location задайте путь для сохранения развертываемого EAR-архива. Введите путь к файлам xalan.jar и xerces.jar в поле Dependent Classpath. В качестве типа базы данных Database type введите Generic/SQL-92, в качестве имени базы данных Database Name введите jdbc/Bookstore, а в качестве схемы Schema — АРР. Заполнив эти поля, нажмите кнопку Generate Now. Теперь можно выполнить развертывание приложения на Web-странице инструментального средства администрирования (localhost:9090/admin). В левой панели выберите Nodes > Computer Name > Enterprise Applications. Щелкните на кнопке Install, затем воспользуйтесь кнопкой Browse для нахождения развертываемого EAR-файла (например, DepJoyed_Bookstore.ear) и щелкните на Next, На следующей странице представлены соответствия имен JNDI. Дважды проверьте каждое из значений, прежде чем нажать Next. На следующей странице представлены ссылки на EJB-компоненты. На следующей странице проверяются установленные соответствия с базой данных. Каждое поле должно содержать значение jdbc/Bookstore. В поле Database type должно содержаться Generic/SQL-92, a в поле Schema — АРР. Следующая страница определяет соответствие между серв- летом и хостом default_host. На следующей странице сбросьте флажок повторного развертывания приложения. Щелкните на Next, подтвердите введенные Значения и щелкните на Finish, чтобы осуществить развертывание. После развертывания приложения щелкните на ссылке в верхней части страницы, чтобы регенерировать конфигурацию подключаемых модулей Web-сервера. Сохраните конфигурацию и перезапустите сервер, выполнив сценарий stopServer, а затем сценарий startServerBasic. Наконец, введите URL localhost/bookstore, чтобы запустить приложение Deitel Bookstore.
646 Глава 13 13.6. Ресурсы в Internet и во Всемирной паутине sexverwatch.internet.com/appservers.html Сайт новостей по серверам приложений, содержащей также обзоры и сравнительный анализ популярных серверов приложений, www.app£Qrver~zone,com "Web-сайт Application Server Zone содержит технические статьи, сравнительный анализ программных продуктов и другую информацию, относящуюся к серверам приложений. Java.sun.com/j2ee Сайт корпорации Sun, посвященный J2EE. Содержит описание спецификации J2EE, новости, средства загрузки и поддержки SDK, Резюме • Java 2 Enterprise Edition — это спецификация для выполнения корпоративных приложений. Хотя Sun предоставляет эталонную реалиаац»к> згой спецификации, реальные системы должны использовать сервер приложений от коммерческого поставщика. • Корпорация Sun Microsystems, совместно с большим сообществом поставщиков серверов приложений, разработало спецификацию Java 2 Enterprise Edition. J2EE определяет платформу сервера приложений и API поддержки для построения корпоративных приложений, которые являются переносимыми между различными серверами приложений, и, поскольку они используют Java, между платформами. • В спецификации J2EE можно выделить несколько разделов, таких как поддержка API, безопасность, управление транзакциями и развертывание. Поставщик сервера приложений должен обеспечить поддержку выполнения API платформы J2EE, • Чтобы быть сертифицированным как удовлетворяющий требованиям спецификации J2EE, сервер приложений должен реализовьгвать минимум функциональных возможностей, определенных В спецификаций J2EE. Поставщики серверов приложений также могут предоставлять возможности, которые не входят в спецификацию J2EE, чтобы как-то выделить свой продукт среди других. • Компания ВЕА предоставляет сервер приложений общего назначения, который обеспечивает «золотую середину» между скоростью и стабильностью, а также основательную поддержку различных функциональных возможностей, не предусмотренных спецификацией J2EE, таких как организация пулов данных, «горячее* развертывание, кластеризацию, поддержку режима восстановления после сбоев для EJB-компонентов и Web-компонентов, а также оптимальное распределение нагрузки. • Для окружений с одним сервером WebLogic предоставляет множественные пулы-, сервис, который осуществляет распределение транзакций между источниками данных. В то время как пул соединений способен работать только с одним источником данных, множественные пулы дают возможность приложению осуществлять доступ к нескольким пулам, тем самым распределяя запросы между весколькими источниками данных. • iPianet E-Coinnierce Solutions — это альянс между компаниями Netscape Communications и Sun Microsystems. Основная цель iPianet — добиться высокой производительности, стабильности и полной совместимости со спецификацией J2EE. iPianet предоставляет поддержку режима восстановления после сбоев, пулы соединений и несколько уникальных возможностей. • Web-коннектор управляет распределением нагрузки б сервере приложений iPianet. Web-коннектор управляет взаимодействием между сервером приложений и Web-сервером. Web-коннектор распределяет запросы между экземплярами сераера в зависимости от времени, затрачиваемого сервером ня выдачу ответа. • В iPianet имеется поддержка «закрепленного? распределения нагрузки: если компонент помечен как +закрепленный*, стандартный алгоритм распределения нагрузки не используется, и компонент выполняется на «закрепленной» машине. • iPianet использует протокол Lightweight Directory Access Protocol (LDAP) для управления безопасностью. Пользователям могут назначаться групповые и индивидуальные полномочия для доступа к составным частям приложения. Настройка полномочий LDAP позволяет более гибко управлять полномочиями пользователей. ■ IBM WebSphere представляет собой популярный сервер приложений, который почти столь же распространен на рынке, что и сервер ВЕА WebLogic. Версия 4.0 предоставляет
Серверы приложений 647 простой пользовательский интерфейс для администрирования и развертыьания, уделяя главное внимание скорости и масштабируемости. В состав WebSphere входит версия Web-сервера Apache от IBM, средства поддержки режима восстановления после сбоев, средства организации пулов данных и средства управления безопасностью па уровне пользователя. Сервер JBoss в сочетании с контейнером сервлетов Tomcat от Apache Software Foundation, который в настоящее время является совместимым только со спецификацией J2EE 1.2, представляет собой сервер приложений с открыто доступным кодом. Предполагается, что JBoss будет совместим с последующими спецификациями J2EE. В настоящее время в. JB03S реализовано большинство функциональных возможностей коммерческих серверов приложений. Терминология Access Control List (ACL) — список управления доступом application server —сервер нрилйженин attribute-map, элемент BEA WebLogic, сервер bean-field, элемент cache-strategy, элемент data pool — пул данных data source — источник данных dbmj-column, элемент deployment descriptor — дескриптор развертывания description, элемент ejb-name, элемент ejh-reference-description, элемент ejb-ref-name, элемент ejbStore, метод failover — восстановление после сбоев finder, элемент finder-list, элемент finder-query, элемент hot deployment — «горячее» развертывание ГВМ WebSphere, сервер iPlanet Application Server, cepRPp J2EE specification — спецификация J2EE JBoss, сервер jndi-name, элемент life cycle — жизненный цикл load balancing — балансирование (распределение) нагрузки max-beans-in-eache, элемент method-name, элемент method-param, элемент method-params, элемент multi pool — множественный пул object-link, элемент persistence-descriptor, элемент persistence-type, элемент persistence-use, элемент pool-name, элемент read-timeout-seconds, элемент reference-descriptor, элемент stateful-session-persistent-store-dir, элемент Sticky load balancing1 — «закрепленное» распределение нагрузка table-name, элемент three-tier — трехуровневое (приложение) transaction management — управление транзакциями transaction-descriptor, элемент trans-timeout-seconds, элемент type-identifier, элемент type-storage, элемент type-version, элемент Web connector — Weh-коннектор WebLogic Query Language (WQL), язык запросов WebLogic_CMPJKDBMS, элемент weblogic_ejb_jar, элемент weblogic-enterprise_bean, элемент weblogic-rdbms-bean, элемент weblogic-web-app, элемент Используемые источники 1. Shannon В, «Java™ 2 Platform Enterprise Edition Specification, vl.2>, 17 декабря 1999 г., <java.sun,com/j2ee/dowiiload.html>. 2. «BEA WebLogic Server® Datasheet», <www.bea.com/products/wcblogic,/serYer/data- sheet/shtml>, 3. «Frequently Asked Questions», <www.jboss.org/faq.jsp>. 4. «Using WebLogic Server RDBMS Persistence», 2000, <wrww.weblogic.com/docs51/clasa- docs/API_ejb/EJB_enviranment.html#1022233>. 5. «weblogic-cmp-rdbms-jar.xml Properties», 2000, <www.weMogie.eom/docs51/classdocs/ APl_eib/EJB_reference.html#1026608>,
f4 Введение в Web-сервисы и SOAP Цели • Понять, что собой представляет протокол Simple Object Access Protocol (SOAP) и каким образом он использует XML. • Уяснить структуру сообщений SOAP. • Научиться создавать приложения Java, которые отправляют и принимают сообщения SOAP, Ничего не происходит до тех пор, пока что-нибудь не будет продано. Артур Г. Мот л и Людям пора учиться управлять в мире, состоящем по большей части из множества вождей и одного индейца. Индеец — это, конечно, компьютер, Томас А. Уислер ...чудеса всегда объясняются просто. Амелия Барр Сходство есть повторение внешних сеойс объектов... Чинг Хао ...если вы понимаете, что вам хочет, сообщить природа, радуйтесь, ибо душа ваша жива... Элеовора Дьюс
650 Глава 14 14.1. Введение Функциональная совместимость, или беспрепятственное взаимодействие и обмен информацией между различными программными системами, является главной целью предприятий и организаций, которые активно используют в своей деятельности компьютеры и компьютерные сети. Многие приложения используют для передачи данных Internet. Некоторые из этих приложений выполняются на клиентских системах, имеющих небольшую вычислительную мощность, поэтому для обработки данных они обращаются к методам, размещенным на удаленных машинах. Многие приложения используют собственные форматы данных, что затрудняет, или даже делает невозможным взаимодействие с другими приложениями. Большинство приложений также расположено за брандмауэрами (или межсетевыми экранами): защитными барьерами, которые ограничивают коммуникационный обмен между сетями. Протокол простого доступа к объектам Simple Object Access Protocol (SOAP) решает эти проблемы. Объединяя сильные стороны, присущие HTTP и XML, он обеспечивает полностью расширяемый режим взаимодействия между программными системами. Web-сервисы — новое слово в технологии распределенных систем. Спецификация Open Net Environment (ONE) корпорации Sun Microsystems и инициатива .NET корпорации Microsoft обеспечивают инфраструктуры для написания и развертывания Web-сервисов. Имеется несколько определений Web-сервиса. Web- сервисом может быть любое приложение, имеющее доступ к Web, например, Web-страница с динамическим содержимым. В более узком смысле Web-сервис — это приложение, которое предоставляет открытый интерфейс, пригодный для использования другими приложениями в Web. Спецификация ONE Sun требует, чтобы Web-сервисы были доступны через HTTP и другие Web-протоколы, чтобы дать возможность обмениваться информацией посредством XML-сообщений и быть найденными через сервисы поиска. SOAP предоставляет средства взаимодействия на базе XML для многих Web-сервисов. Web-сервисы могут обеспечить высокую степень совместимости между различными системами [1]. Гипотетический Web-сервис, разработанный в соответствии с архитектурой ONE Sun, может принимать форму, в которой реестр сервисов публикует описание Web-сервиса в виде документа Universal Description, Discovery and Integration (UDDI). Клиент, например, Web браузер или клиент GUI Java, ищет службу каталогов для требуемого Web-сервиса. Клиент использует информацию, полученную от сервиса поиска, для отправки XML-сообщения через HTTP Web-серверу, на котором размещен Web-сервис. Сервлет обрабатывает клиентский запрос. После этого сервлет осуществляет доступ к серверу приложений, который предоставляет средства Enterprise JavaBeans. Компоненты EJB, в свою очередь, обращаются к базе данных, которая хранит информацию для Web-сервиса. После обращения к базе данных EJB-компонент отправляет сервлету запрошенную информацию.
Введение в Web-сервисы и SOAP 651 Сервлет форматирует информацию для представления ее клиенту (например, создает страницу JavaServer Page). Сервер HTTP отправляет ответ в виде XML обратно клиенту. Клиент осуществляет синтаксический анализ ответа и отображает информацию пользователю [1]. -Огромный потенциал Web-сервисов определяется отнюдь не технологией, используемой для их создания. HTTP, XML и другие протоколы, используемые Web-сервисами, не новы. Функциональная совместимость и масштабируемость Web-сервисов подразумевает, что разработчики могут быстро создавать большие приложения и более крупные Web-сервисы из меньших Web-сервисов. Спецификация Sun Open Net Environment описывает архитектуру для создания интеллектуальных Web-сервисов. Интеллектуальные Web-сервисы используют общее операционное окружение. Совместно используя контекст, интеллектуальные Web- сервисы могут выполнять стандартную аутентификацию для финансовых транзакций, предоставлять рекомендации и указания в зависимости от географического местоположения компаний, участвующих в электронном бизнесе. На момент написания этой книги на пути разработки интеллектуальных Web-сервисов имелось два основных препятствия. Во-первых, пока не существует общепринятых стандартов для совместного использования контекста. Web-сервисами. Во-вторых, пока не обеспечивается безопасность и конфиденциальность транзакций, осуществляемых Web-сервисами. 14.2. Простой протокол доступа к объектам (SOAP) Корпорации IBM, Lotus Development Corporation, Microsoft, Develop-Mentor и Userland Software разработали протокол SOAP, который представляет собой протокол, основанный на HTTP-XML. Он позволяет приложениям взаимодействовать между собой через Internet, используя для этого ХМЬ-дшсументы, называемые сообщениями SOAP. Протокол SOAP совместим с любой объектной моделью, поскольку он включает только те функции и методы, которые абсолютно необходимы для формирования коммуникационной инфраструктуры. Таким образом, SOAP является независимым от платформы и конкретных приложений, а для его реализации может быть использован любой язык программирования. SOAP поддерживает практически любой транспортный протокол. Например, SOAP привязан к протоколу HTTP и следует модели запрос-ответ HTTP. SOAP также поддерживает любые методы кодирования данных, которые позволяют приложениям, основанным на SOAP, посылать в сообщениях SOAP информацию практически любого типа (например, изображения, объекты, документы и т.д.). Сообщение SOAP содержит конверт, который описывает содержимое, предполагаемого получателя сообщения и требования к обработке сообщения. Необязательный элемент header (заголовок) сообщения SOAP содержит инструкции по обработке для приложений, которые принимают сообщение. Например, для реализаций, которые поддерживают транзакции, заголовок может задавать параметры для данной транзакции. Заголовок также может содержать информацию о маршрутизации. С помощью заголовка header поверх SOAP могут надстраиваться более сложные протоколы. Записи в заголовке могут модульно расширять сообщение для таких задач, как аутентификация, управление транзакциями и проведение платежей. Тело SOAP-сообщения содержит специфичные для приложения данные, предназначенные для предполагаемого получателя сообщения, SOAP можно использовать для осуществления удаленного вызова процедур Remote Procedure Call (RPC), который представляет собой запрос, посылаемый другому компьютеру для выполнения определенной задачи. RPC использует словарь XML для задания вызываемого метода, передаваемых ему параметров и универсаль-
652 Глава 14 ного идентификатора ресурса (URI) целевого объекта. Вызов RPC привязывается к HTTP-запросу, так что сообщение отправляется через HTTP-запрос post. Сообщение-ответ SOAP представляет собой документ HTTP-ответа, который содержит результаты вызова метода (например, возвращаемые значения, сообщения об ошибках и т.д.)- SOAP также поддерживает асинхронные RPC-вызовы, при которых программа, инициировавшая RPC-вызов, не ждет ответа от удаленной процедуры. На момент написания этой книги SOAP еще находится на стадии развития, и разработка многих технологий, основанных на SOAP, только начинается. Чтобы реализовать преимущества, предоставляемые SOAP, необходимо установить высокоуровневые спецификации и стандарты, которые используют эту технологию. Несмотря на это, SOAP является перспективным стандартом для XML-распределенных вычислений, предоставляя невиданный ранее уровень расширяемости и функциональной совместимости. На рис. 14.1-14.4 представлен пример сервиса SOAP, использующий реализацию API SOAP Apache, версия 2.2 (доступна по адресу xml.apache.org/soap)1. Для RPC SOAP требуется средство работы с сервлетами, например, Tomcat (ja- karta.apache.org) и синтаксический анализатор Apache Xerces для Java (доступен по адресу xnil.apachc.org/xerces-j/ind.cx.litnil). В документации SOAP (docs/ins- tall/index.html) содержатся инструкции по установке как для сервера, так и для клиента. На рис 14,1 представлен класс SimpleService, который размещается на Сервере и содержит метод getWelcome. Приложение Java, представленное на рис. 14.4, вызывает этот метод с использованием RPC. 1 // Рис. 14.1. SimpleService.java 2 // Реализация запрашиваемого метода на сервере 3 4 public class SimpleService { 5 6 public String getWelcome( String message > throws Exception 7 { 6 String text = 9 "Welcome to EOAP!\nHere is your message: " + message; 10 11 return text; // ответ 12 ) 13) Рис. 14.1. Класс SimpleService Метод getWelcome (строки 6-12) возвращает строковые данные. Чтобы сделать этот метод доступным для клиентов (т.е. сделать возможным работу с ним через RPC), необходимо предоставить серверу имя метода, который обрабатывает запрос, т.е. необходимо осуществить разеертывание сервиса. Чтобы выполнить развертывание сервиса, сначала скопируйте файл SimpleService.class в каталог jakarta-tomcat/classes. Если вы создали файл архива Java (JAR), скопируйте JAR-файл в каталог jakarte-tomcat/lib. Создайте ката лог classes или lib, если они не существуют, В Jakarta-Tomcat указание на файлы в этих каталогах включается в описание переменной окружения CLASSPATH. Выполните развертывание сервиса с помощью инструментального средства развертывания XML-SOAP, входящего в состав пакета SOAP (в каталоге webapps/ soap). Чтобы выполнить приложение, введите URL Iocalhost:8080/soap/admin 1 На момент подготовки к изданию перевода книги актуальной была версия 2.3.1 API Apache SOAP. — Прим, ред.
Введение в Web-сервисы и SOAP 653 в адресной строке Web-браузере. На рис. 14.2 и 14.3 представлено средство администрирования, которое позволяет развертывать, удалять и получать список сервисов. Поле ID на рис. 14,2 содержит URI (urn:xml-simple-message), который идентифицирует сервис клиенту. Этот URI определяется программистом. Если один сервис имеет такой же URI, что и другой сервис, клиент не сможет их различить; как следствие, возможны ошибки. Поле Scope задает границы существования объекта, создаваемого (на сервере) для обработки запроса SOAP. Объект может существовать в пределах запроса (Request), сеанса (Session) или приложения (Application). Значение Request подразумевает, что сервер удаляет объект после отправки ответа. Значение Session подразумевает, что объект существует в течение всего сеанса взаимодействия клиента с сервером. Значение Application подразумевает, что объект доступен для всех запросов в течение времени жизни приложения. В поле Method (рис. 14,2) задаются методы, доступные для запроса SOAP, в данном случае, метод getWelcome. В поле Provider Туре задается язык реализации сервиса. Поддерживаются языки Java, JavaScript, Perl и Bean Markup Language (BML). Для примеров, рассматриваемых в этой главе, используется Java. В поле Provider Class указывается класс, который реализует сервис: Simple Service, Поля Script Language, Script File и Script используются только '. Нг Ш Ив* FimifcM 'Т9?&*'"нф tap: /to«lhMt: SOaO/*™^dmin/triiJe *■ 1"и=гт1 Л - Miceosttft interne* Еюйогег >ЖГ± kite' Ш: Deploy '"Ш. Un-deploy W ... Apache SOAP Atlmin ^~ Service Deployment Descriptor Templar 'Л jPropeity | ilD '[ui DetaiU rn ■/m'i-v^efl'^er-seivice ;Scopc ;l deques'. "j [Methods , l[ge Weeth e rid от rti eii a n iCWhitcspsce ssparat^insi ofrcethod aarne?) iProvider iTypt 3 I F&r IIser-Defoed Pre v-.der Type, Enter FULL Class Name: ■N'-niber of Opnons: \ Key Value T :M I Java I Provicsr Class prtEl.advjJ'.tpl.soap.weatfier.WealheTSeivice Providtr :S»abt? I Mo r\ . Ji ii£ j glwalWilindt ■ jri"; ^ Рис. 14.2. Инструментальное средстве- администрирования из пакета SOAP
654 Глава 14 для сервисов, реализованных на поддерживаемых языках написания сценариев. Поле Type Mapping позволяет вручную устанавливать карту соответствий между типами Java и XML. Реализация Apache SOAP предоставляет отображения по умолчанию для большинства типов Java и для классов Java, которые следуют паттернам проектирования JavaBeans. Заполнив форму, щелкните на кнопке Deploy в нижней части формы, чтобы осуществить развертывание сервиса. Щелкните на кнопке List, чтобы получить список сервисов, и убедитесь, что развертывание сервиса завершилось успешно (рис. 14.S). Инструкции относительно других способов развертывания (например, через командную строку) можно найти по адресу docs\ guide\index.html. .jjflUJE hSWfaa»m*fln8q(^lai*i»Tpndei: bbnl ~ Z_H z|i<^° jif**1"! suAPAd ruol Murcisofi; Jrtterotrt twrianaf ШШШшЗшБт Apache SOAP Admin Deployed Service Information rurit:xnil-$imine-mes&age' Service Deployment Descriptor £' I.. Tlxiptity Petails Un-dcplOjf Scope |uni:xrn3-simp]e-iijcs sage .-•«i -• =b; Prouder type Trovid'r Class |TJse Staac Class ^Methods [Typt Mapping? Java jSimp3eStrviCff !£alsc !gct Welcome i * Mapping Registry ClIss !©**• ~^Т~7:"ШГ^Ш^ ШПШ!ЩШ-<1&«вЩГ Рис. 14.3. Огисание развернутого сервиса На рис. 14.4 представлен клиентский код для RPC-вызова, При выполнении программа посылает запрос SOAP серверу, которым в данном случае является этот же локальный компьютер. Клиент отправляет сообщение как параметр для удаленного метода. (Это сообщение может быть задано в командной строке; по умолчанию приложение использует сообщение Thanhs!) Когда сервер вызывает метод, тот возвращает клиенту сообщение Welcome to SOAP! Неге is your message: Thanks • // Рис. Id.4 . GetMessage.Java // Программа, которая создает RPC-выэов SOAP // икпоря пакетов Java import java.io.*; import java.net.*; import j ava,util.*;
Введение в Web-сервисы и SOAP 8 9 // импорт пакетов сторонних поставщиков 10 import org.apache.soap,*; 11 import org.apache.soap.rpc.*; 12 13 public class GetHessage { 14 15 // метод main 16 public static void main( String args[] ) { 17 String encodingStyleURI = Constants.NS_URX_SGAP_ENC; 18 String message; 19 20 if ( args.length != 0 ) 21 message = args[ 0 ]; 22 else 23 message = "Thanks'"; 24 25 // попытка удаленного вызова процедуры SOAP 26 try { 27 URL url = new URL( 28 "http://localhost:8080/soap/servlet/rpcrouter" ); 29 30 // формирование вызова 31 Call reinoteMethod = new Call О ; 32 remoteMetliod. setTargetObjectURI ( 33 "urn:xml-simple-message" ); 34 35 // задание имени вызываемого удаленного метода 36 remoteMethod.setMethodName( "getWelcome" ); 37 remoteMethod.setEneodingStyleURI( encodingStyleURI ); 38 39 // задание параметров для удаленного иетода 40 Vector parameters = new Vector (); 41 42 parameters.add£lement( new Parameter{ "message", 43 String.class, message, null ) ); 44 remoteMethod.setParams( parameters ); 45 Response response; 46 47 // busob удаленного метода 48 response = remoteMethod. invoice ( url, "" ); 49 50 // получение ответа 51 if < response.generatedFault() ) { 52 Fault fault = response.gefcFaulfc(); 53 54 System.err.println( "CALL FAILED:\nFault Code = " 55 + fault.getFaultCode()+ "\nFault String = " 56 + fault. getFaultStringO )»' 57 } 58 59 else { 60 Parameter result = response.getReturnValue(); 61 62 // отображение результатов вызова 63 System.out.println( result,getValue() ); 64 }
656 Глава 14 65 ) 66 67 // сереасаат исключения при указании неверного URL €8 catch ( MalformedURLException malformedUKLException ) { 69 malfonriedURLExceptior. .printStackTrace () ; 70 System.exit( 1 ); 71 } 72 73 // перехват исключения SOAPException 74 catch ( SOAPException soapException ) { 75 System.err.println( "Error message: " + 76 soapException.getMessage[) ); 77 System.exit( 1 }; Рис. 14.4. Клиент, инициирующий запрос SOAP В строке 10 импортируется пакет SOAP, который обеспечивает API для реализации SOAP. Пакет org.apache.aoap.rpc в строке 11 предоставляет реализацию RPC, использующую SOAP. В строке 17 задается стиль кодирования, используемый для сообщения. Протокол SOAP, поддерживает множество стилей кодирования, но не имеет стиля кодирования по умолчанию. В данном случае используется стандартная кодировка ЯРС (WS_UR\_SOAP_ENC). R строках 27-28 яядается URL сервера, которому клиент отправляет содержимое rpcrouter сообщения message. Этот документ, который представляет собой сервлет Java, получает конверт SOAP с помощью HTTP-метода post. Используя URI, заданный в сообщении SOAP, он ищет сервисы, развернутые на сервере, чтобы реализовать экземпляр соответствующего объекта, в данном случае, объекта SimpleService. Объекты класса Call осуществляют вызовы удаленных методов. В строке 31 реализуется экземпляр объекта Call, и ему назначается ссылка remoteMethod. В строках 32-33 устанавливается URI удаленного метода. В строке 36 задается имя вызываемого метода, getWelcnm«. Затем в строке 37 задается стиль кодирования для сообщения. В строках 40-44 формируются параметры, передаваемые удаленному методу для обработки. Каждый параметр должен содержаться в своем собственном объекте, а параметрические объекты должны быть помещены в контейнер Veclor. В строках 42-43 формируется новый параметр для метода путем построения объекта Parameter. Первым параметром, передаваемым конструктору, является имя переменной или ссылки (message), вторым параметром является класс, которому принадлежит объект Parameter (String), третьим параметром является значение параметра (объект message), а четвертый параметр задает тип кодировки параметра (null указывает на использование кодировки, принятой по умолчанию). Метод setParams в строке 44 устанавливает параметры для объекта remoteMethod.
Введение в Web-сервисы и SOAP 657 Удаленный метод активизируется в строке 48 вызовом метода invoke. Метод принимает два параметра: URL сервера, которому будет посылаться сообщение SOAP, и значение заголовка SOAPAction, который задает цель запроса. В качестве второго параметра может быть задано null, если заголовок SOAPAction использоваться не будет. Метод invoke возбуждает исключение SOAPException (строки 74-78), если при отправке запроса SOAP возникает ошибка. После активизации метода на сервере, результат возвращается клиенту и сохраняется в объекте, идентифицируемом ссылкой response (строка 48). Этот объект принимает сообщение об ошибке, если на сервере возникает проблема, например, не удается обнаружить соответствующие сервисы. В строках 51-57 определяется, является ли полученное сообщение сообщением об ошибке. Если ошибок не было, в строках 59-64 результат выводится на экран. 14.3. Служба погоды, реализованная посредством SOAP В этом разделе описан простой Web-сервис, реализованный с помощью Java и SOAP, в котором для отправки информации от сервера к клиенту используется RPC SOAP1. Обязательные программные компоненты такие же, что и в примере из предыдущего раздела. Класс WeatherService (рис. 14.5) предоставляет метод getWeatherlnformation, который класс WeatherServiceCHent вызывает через RPC SOAP. Чтобы вызов был успешным, класс WeatherService должен присутствовать в каталоге classes машины сервлетов Tomcat. RMI-версия метода gfetWeatherfnforniation возвращает список (List) объектов We a the r Bean. SOAP не поддерживает прямую передачу этих объектов Java, поэтому версия RPC SOAP метода getWeatherlnformation возвращает контейнер (объект Vector) строк (String). Метод updateWeatherConditions (строки 16-79) прочитывает информацию о погоде с Web-страницы прогноза погоды и сохраняет ее в списке List объектов WeatherBean. В строках 22—23 создается объект URL для Web-страницы прогноза погоды. В строках 26-27 вызывается метод opcnStream класса URL для открытия соединения с ресурсом с заданным UEL. Объект этого соединения помещается в объект-обертку BnfferedRead. В строках 30-76 выполняется скрэпинг HTML (т.е. извлечение данных с Web- страницы) для получения информации о прогнозе погоды. В строке 30 определяется строковый разделитель — "TAV12" — который задает начальную точку на Web-странице, с которой начинается поиск информации о погоде. В строках 33-34 осуществляют данных чтение с Web-сграннцы прогноза погоды, пока не будет достигнута сигнальная метка. Это позволяет пропускать информацию, не нужную для этого приложения. В строках 38-41 определены две строки, которые представляют заголовки столбцов для информации о погоде. В зависимости от времени дня, заголовки столбцов будут иметь вид "CITY WEA HI/LO WEA HI/LO" после утреннего обновления (обычво около 10:30 по стандартному восточному времени) или "CITY WEA LO/HI WEA LO/EI" после вечернего обновления (обычно около 22:30 по стандартному восточному времени). 1 В главе 2 (в оригинале это глава 13) книги «Технологии программирования на Java 2. Книга 2d это приложение реализовано с помощью технологии RMI. — Прим.. ред.
658 Глава 14 В строках 55-74 осуществляется чтение информацию о погоде в каждом городе и запись этой информации в объекты WeatherBean. Каждый объект WeatherBean содержит название города, температуру воздуха и описание погоды. В строке 51 создается контейнер Vector для хранения объектов WeatherBean. В строках 66-71 листинга строки, полученные с Web-страницы прогноза погоды, добавляются в контейнер weatherbiformatura (строка IS). Первые 16 символов в строке inputLine относятся к названию города, следующие 6 символов описывают погоду (т.е. содержат прогноз), а следующие 6 символов представляют верхний и нижний предел температуры. Последние два столбца данных относятся к прогнозу погоды на следующий день, и в этом примере игнорируются. В строке 76 закрывается объект BufferedRead и связанный с ним поток ввода lnpntStream. В строке 99 возвращается контейнер weatherlnformation.' 1 // WeatherService.java 2 // WeatherService предоставляет метод для извлечения 3 // информации о погоде с сайга National Heather Service. 4 package com.deitel.advjhtpl.soap.weather; 5 6 // Набор базовых пакетов Java 7 import Java. io. * ; 8 import j ava.net.URL; 9 import java.util.*; 10 11 public class WeatherService { 12 13 private Vector weatherlnformation; // объекты WeatherBean 14 15 // получение информации о породе с сайта NWS 16 private void updateWeatherConditions(} 17 { 18 try { 19 System.out.printin( "Update weather information..." ); 20 21 // страница прогноза погод» Национальной метеослужбы США 22 URX url = new URL( 23 "http://iwin.nws.noaa.gov/iwin/us/traveler.html" ); 24 25 // задание текстового потока ввода для чтения содержимого Web-страницы 26 BufferedReader in = new BufteredRea.de r( 27 new InputstreamReader( url.openStream() ) ); 28 29 // определение начала данных на Web-странице 30 String separator = "TAV12"; 31 32 // нахождение разделительного символа на Web- странице 33 while { !in.readLineО.startsWith{ separator ) ) 34 ; // отсутствие действий 35 36 // строки, представляющие на Web-странице Travelers 37 // Forecast заголовки для погоды в дневное и конное время 38 String dayHeader = 39 "С1ТУ WEA HI/LO WEA HI/L0"; 4Q String nightHeader = 41 "CITY WEA Х.О/Н1 WEA LO/BI" ; 42
Введение в Web-сервисы и SOAP 659 43 String inputLine = ""; 44 45 // нахождение заголовка, которым начинается информация о погоде 46 do { 47 inputLine = in.readLine(); 48 > while ( !inputLine.equals( dayHeader ) && 49 !inputLine.equals( nightHeader ) ); 50 51 weatherlnformation = new Vector (),- // создаюге колгейнера Vector 52 53 // создание компонентов WeatherBean, содержащих данные 54 // о погоде, и сохранение их в контейнере weatherlnformation 55 inputLine = in.readLine(); // получение данных для первого города 56 57 // Часть строки ввода inputLine, содержащая нужные данные, 58 // имеет длину в 28 символов. Если длина строки 59 // превосходит 28 символов, выполнить обработку данных. 60 while ( inputLine.length() > 28 ) { 61 62 // Подготовка строк для компонента WeatherBean для 63 // каждого города. Первые 16 символов относятся 64 // к названию города. Далее, шесть символов относятся 65 // к описанию информации о погоде. Следующие шесть символов представляют наибольшую и наименьшую температуру HI/L0 или L0/HI. 66 weatherlnformation.add{ 67 inputLine,substring( 0, 16 ) ) ; 68 weatherlnformation.add( 69 inputLine.substring{ 16, 22 ) ); 70 weatherlnformation.add( 71 inputLitte.substring( 23, 29 > ); 72 73 inputLine = in.readLine(); // получение данных для следующего города 74 } 75 76 in.close (}; // закрытие соединения с Web-сервером MWS 77 78 System.out.println( "Weather information updated." ); 79 } 80 81 // обработка неудачного соединения со службой National Weather Service 82 catch{ java.net.ConnectException connectException ) { 83 connectException.printStackTrace(>; 84 System.exit( 1 ) ,- 85 } 86 87 // обработка других исключений BB catch( Exception exception ) { 89 exception.printstackTrace() ; 90 System.exit( 1 ); 91 )
660 Глава 14 92 } 93 94 // реализация интерфейсного метода для сервиса WeatherService 95 public Vector getWeatherlnfoxmation() 96 { 97 updateWeatherConditionsО; 98 99 return weatherlnformation; 100 } 101 } Рис. 14.5. Реализация класса WeatherService с использованием SOAP Класс WeatherBean (рис. 14,6) хранит данные, которые класс WeatherService извлекает с Web-сайта прогноза погоды. Этот класс хранит название города, температуру и текстовое описание погоды. В строках 64—85 предоставляются методы get для каждого фрагмента информации. В строках 25-45 загружается файл свойств, который содержит имена файлов с условными изображениями характера погоды. Этот статический блок обеспечивает доступность файлов изображений сразу же после того, как виртуальная машина осуществит загрузку класса WeatherBean в память, 1 // WeatherBean.java 2 // WeatherBean содержит информацию о погоде для одного города. 3 package com.deitel.advjhtpl.rmi.weather; 4 5 // Набор базовых пакетов Java 6 import Java.awt.*; 7 import Java.io.*; 8 import java.net.*; 9 import java.util.*; 10 11 // Пакеты расширений Java 12 import javax.swing.*; 13 14 public class WeatJverBean implements Serializable \ 15 16 private String cityName,- // название города 17 private String temperature; // температура в городе 18 private String description; // описание погоды 19 private Imageleon image; // изображение характера погоды 20 21 private static Properties imageNames; 22 23 // инициализация объекта imageNames при загрузке класса 24 // Weatherlnfo в память 25 static { 26 imageNames = new Properties(); // создание таблицы свойств 27 28 // загрузка описаний погоды и имен изображений иэ 2 9 // файла свойств 30 try { 31 32 // получение URL для файла свойств 33 UKL url = WeatherBean.class.getResource( 34 "imagenames.properties" );
Введение в Web-сервисы и SOAP 661 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 £5 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 64 85 86 } } // загрузка содержимого файла свойств imageNames.load( new FileInputStream( url.getFile() ) ) // обработка исключений при открытии файла Catch ( IOException ioException ) { ioException.printStackTrace(); I } // конец блока static // конструктор WeatherBean public WeatherBean{ String city, String weatherDescription, String cityTemperature ) { cityName = city; temperature = cityTemperature; description = weatherDescription. trim() ,- URL url = WeatherBean.class.getResource( "images/" + imageNames.getProperty( description, "noinfo.jpg" ) ) // получение имени изображения или использование // имени noinfo.jpg, если описание погоды не найдено image = new Imagelconf url j; } // получение названия города public String getCityNameО return cityName; I получение температуры public String getTemperature() return temperature; // получение описания погоды public String getDescription() return description; // получение условного изображения характера погоды public XmageXcon getlmage() return image; Рис. 14.6. Класс WeatherBean хранит сведения о погоде в одном городе
662 Глава 14 Класс WeatherServiceClient (рис. 14.7) осуществляет удаленный процедурный вызов SOAP метода getWeatherlnformation класса WeatherService, В строках 31-32 устанавливается URL сервиса SOAP. В строке 35 создается новый объект Call, который хранит информацию, необходимую для выполнения вызова удаленной процедуры. В строках 3G—37 устанавливается UR1, который уникально идентифицирует сервис метеопрогнозов в машине сервлетов, В строках 40-41 задается имя метода для удаленного вызова процедуры, В строках 42-43 устанавливается кодировка, используемая при вызове. В строке 46 создается объект Response и вызывается метод invoke на объекте Call с указанием URL в качестве параметра. Объект Response содержит ответ на вызов удаленной процедуры. В строке 49 определяется, не возникла ли в результате ответа ошибка (объект Fault). В этом случае в строках 52-54 выводится код ошибки. Если ошибок не возникло, в строке 58 извлекается объект, возвращенный вызовом удаленной процедуры. В строках 60-61 осуществляется приведение типа Object к типу Vector. В строках 64-65 создается список List и вызывается метод createBeans (строки 95-107) с передачей контейнером (Vector) строк в качестве параметра. Метод createBeans преобразует контейнер (объект типа Vector) строк в список (List) объектов WeatherBeans. В строках 68-69 создается модель List Model списка объектов WeatherBean. В строках 73-77 создается компонент JList, содержащий информацию, полученную в результате вызова удаленной процедуры, и список JList отображается в окне JFrame. 1 // WeatherServiceClient.Java 2 // WeatherServiceClient осуществляет доступ к удаленному об'ьекту 3 // WeatherService через SOAP, чтобы извлечь информацию о погоде. 4 package com.deitel.advjhtpl.soap.weather; 5 6 // набор базовых пакетов Java 7 import Java.util.*; в import java.net,*; 9 10 // Пакеты расширений Java 11 import javax.swing.*; 12 13 // Пакеты сторонних поставщиков 14 import org.apache.soap.*; 15 import org.apache.soap.rpc.*; 1Й 1*? /7 пакеты Deitel 18 import com.deitel.advjhtpl.rmi.weather.*; 19 20 public class WeatherServiceClient extends JFrame { 21 22 // конструктор WeatherServiceClient 23 public WeatherServiceClient( String server } 24 ( 25 super ( "SOAP WeatherService Client" ); 26 27 // соединение с сервером и получение информации о погоде 28 try ( 29 30 // URL удаленного объекта SOAP 31 URL url = new URL{ "http://" + server + ":8080/soap/" 32 + "servlet/rperouter" ); 33 34 // формирование удаленного вызова SOAP
Введение в Web-сервисы и SOAP 663 35 Call remoteMethod = new Call(); 36 remoteMethod.setTargetObjectURI( 37 "urn:xml-weather-service" ); 38 39 // задание имени вызываемого удаленного метода 40 гemo teMe thod.se tMe thodName ( 41 "getWeatherlnformation" ); 4 2 remoteMe thod.se tEncodingStyleUKI( 43 Constants.NS_URI_SQAP_ENC ); 44 45 // вызов удаленного метода 46 Response response = remoteMethod. invoke { url, "" ); 47 48 ff получение ответа 4 9 if ( response.generatedFault() } { 50 Fault fault = response.getFault(); 51 52 System.err.println( "CALL FAILED:\nFauIt Code = " 53 + fault.getFaultCode0 + "KnFault String = " 54 + fault.getFaultString() ); 55 ) 56 57 else \ 5$ Parameter result = response. getReturnValuef) .' 59 60 Vector weatherStrings = ( Vector ) 61 result.getValue(); 62 63 // получение информации о погоде из объекта результата 64 List weatherInformation = createBeans( 65 weatherStrings ); 66 67 // создание модели WeatherListModel для информации а погоде 68 ListModel weatherListModel = 69 new WeatherListModel( weatherlnformation ); 70 71 // создание списка JList, установка для него 72 // интерфейса CellRenderer и добавление его в макет 73 JList weatherJList = new JList( weatherListModel ); 74 weatherJList.setCellRenderer( new 75 WeatherCellRendererO ); 76 getContentPane().add( new 77 JScrollPanef weather JList ) ) ; 78 } 79 80 } // конец блока try 81 32 // обработка неверного URL 83 catch ( MalformedURLException malformedURLException ) { 84 malformedURLException.printStaclcTraceO ; B5 } 86 87 // обработка исключения SOAP BB catch ( SOAPException soapException ) { 89 soapException.printStackTrace();
664 Глава 14 90 } 91 92 } // конец конструктора WeatherServiceClient 93 94 // создание списка List, объектов WeatherBean из контейнера строк. weatherStrings 95 public List createBeans( Vector weatherStrings ) 96 { 97 List list = new ArrayList(); 98 for ( int i = 0; ( weatherStrings.size{) - 1 > > i; 99 1 += 3 ) { 100 list.add( new HeatherBean ( 101 ( String ) weatherStrings.elementAt( i ), 102 ( String ) weatherStrings.elementAt( i + 1 )r 103 ( String } weatherStrings.elementAt( i + 2 ) ) ); 104 ) 105 106 return list; 107 } 108 109 // выполнение приложения WeatherServiceClient 110 public static void main( String args[] ) 111 { 112 WeatherServiceClient client = null; 113 114 // если IP-адрес сервера или хост-имя не заданы, использовать 115 // "looalbost"; иначе использовать указанное хост-имя 116 if ( args.length == 0 ) 117 client = new WeatherServiceClient{ "localhost" ); 11B else 119 client = new WeatherServiceClient( args[ 0 ] ); 120 121 // настройка и отображение окна приложении 122 client.setDefaultCloseOperatiOn( JFrame.EXIT_OH_CL0SE ); 123 client pack О; 124 client.setResizable( false ); 125 client.setVisible ( true ); 126 } 127 } ^ Рис. 14,7. Реализаций класса WeatherServiceClient с использованием SOAP Класс WeatherListModel (рис. 14.8) представляет собой модель типа ListModel, которая содержит объекты WeatherБеап., отображаемые в списке JList. В этом примере мы продолжим применять паттерны проектирования, на этот раз, введя адаптерный паттерн проектирования Adapter, который дает возможность взаимодействовать друг с другом компонентам, имеющим несовместимые интерфейсы. Паттерн проектирования Adapter имеет множество аналогий в реальном мире. Например, электрические вилки для бытовых приборов, используемых в США, не совместимы с электрическими розетками, применяемыми в Европе. Чтобы пользоваться американскими электроприборами в Европе, пользователю необходимо вставить переходник-адаптер между электрической розеткой и вилкой. С одной стороны, этот адаптер обеспечивает интерфейс, совместимый с американской электрической вилкой. С другой стороны, адаптер обеспечивает интерфейс, совместимый с европейской электрической розеткой. Класс WeatherList Model играет роль
Введение а Web-сервисы и SOAP 665 адаптера в паттерне проектирования Adapter. Интерфейс List Java не является совместимым с интерфейсом класса JList, поскольку компонент JList способен извлекать элементы только из модели ListModel. В связи с этим мы предоставляем класс WeatherListModel, который адаптирует интерфейс List к интерфейсу JList. Когда JList вызывает метод getSize класса WeatherListModel, объект WeatherListModel вызывает метод size интерфейса List. Когда объект JList вызывает метод getElemeatAt класса WeatherListModel, модель WeatherListModel вызывает метод get класса JList и т.д. 1 // WeatherListModel.Java 2 // WeatherListModel расширяет класс AbstractListModel, чтобы предос- 3 /J тавитъ модель ListModel для хранения списка объектов WeatherBeans. 4 package com.deitel.advjhtpl.rmi.weather; 5 6 // Набор базовых пакетов Java 7 import java.util,*; 6 9 // Пакет» расширений Java 10 import ;javax. swing. AbstractListModel; 11 12 public class WeatherListModel extends AbstractListModel { 13 14 У/ список элементов в модели ListModel 15 private List list; 16 17 // конструктор без параметров WeatherListModel 18 public WeatherListModel() 19 1 20 // создание нового списка для объектов WeatherBean 21 list = new ArrayList(); 22 } 23 24 // конструктор WeatherListModel 25 public WsatherListModel( List itemList } 26 ( 27 list = itemList; 28 } 29 30 // получение разкерв списка 31 public int getSize() 32 { 33 return list.size(); 34 } 35 36 // получение ссылки типа. Object на элемент с заданным индексом 37 public Object getSlementAt( int index ) 38 { 39 return list.get( index ); 40 > 41 42 // добавление элемента в модель WeatherListModel 43 public void add( Object element ) 44 { 45 list.add( element ); 46 fire!ntervalAdded( this, list.sized, list.size() ); 47 J
666 Глава 14 48 49 // удаление элемента из модели WeatherListModel 50 public void remove( Object element ) 51 { 52 int index = list. itidexQf ( element ); 53 54 if ( index != -1 ) { 55 list.remove( element >; 56 firelntervalRemoved( this, index, index ); 57 } 58 59 } // конец метода remove 60 61 // удаление всех элементов из модели WeatherListModel 62 public void clear О 63 I 64 // получение исходного размера списка 65 int size = list.size(); 66 67 // очистка всех элементов в списке 68 list, clear О; 69 70 // уведомление слушателей об изменении содержимого 71 fireContentsChangedf this, 0, size ); 72 } 73 ) Рис. 14.8. Класс WeatherListModel является реализацией интерфейса ListModel и хранит информации о погоде Класс JList использует интерфейс ListCcllRenderer для воспроизведения каждого элемента, содержащегося в модели ListModel объекта JList. Класс WeatherCellRenderer (рис. 14.9) является подклассом класса DefaultListCell- Renderer и используется для отображения объектов WeatherBean в списке JList. Метод fjetListCellRendererComponent создает и возвращает объект Weatherltem (рис. 14.10) для заданного объекта WeatherBean. Класс Weatherltem (рис. 14.10) является подклассом класса JPanel и используется для отображения информации о погоде, хранящейся в объекте WeatherBean. Класс WeatherCellRenderer использует экземпляры класса Weatherltem для отображения информации о погоде в списке JList. В блоке static (строки 22-29) осуществляется загрузка объекта backgroundlmage класса Imagelcon в память, когда виртуальная машина загружает сам класс Weatherltem. Это обеспечивает, что объект backgroundlmage будет доступен всем экземплярам класса Weatherltem. Метод paintComponent (строки 38-56) осуществляет вывод фонового изображения backgroundlmage (строка 43), названия города (строка 50), температуры (строка 51) и изображения Imagelcon для объекта WeatherBean, которое характеризует погодные условии (строка 54). 1 // WeatherCellRenderer.Java 2 // WeatherCellRenderer - специализированный интерфейс 3 // ListCellRenderer для объектов WeatherBean в списке JList. 4 package com.deitel.advjhtpl.rmi.weather; 5 6 // Набор базовых пакетов Java 7 import java.awt.*;
Введение в Web-сервисы и SOAP 667 8 9 // Пакеты расширений Java 10 import javax.swing.*; 11 12 public class WeatherCellRenderer extends DefaultListCellRenderer { 13 14 // возврат объекта Weatherltem, который отображает информацию о погоде в городе 15 public Component getListCellRendererComponent( JList list, 16 Object value, int index, boolean isSelected, boolean focus ) 17 ( 18 return new Weatherltem{ ( WeatherBean ) value ); 19 } 20 } __ _ Рис. 14.9. Класс WeatherCellRenderer является видоизмененным классом ListCellRenderer для отображения объектов WeatherBean в списке JList 1 // Weatherltem.Java 2 // Weatherltem отображает информацию о погоде в городе в панели JPanel. 3 package com,deitel.advjhtpl. rmi.weather; 4 5 // Набор базовых пакетов Java 6 import java.awt,*; 7 import java.net.*; 8 import java.util.*; 9 10 // Пакеты расширений Java 11 import javax.swing.*; 12 13 public class Weatherltem extends JPanel { 14 15 private WeatherBean WeatherBean; // информация о погоде 16 17 // фоновый рисунок rmagelcon 18 private static ImageIcon backgroundlmage; 19 20 // блок инициализации static загружает файл изображения, 21 // когда класс Weatherltem загружается в память 22 static { 23 24 // получение CTRL для фонового изображения 25 URL url = Weatherltem.class.gatResource( "images/back.jpg" ) 26 27 // фоновое изображения для информации о погоде в каждом из городов 28 backgroundlmage = new ImageIcon( url ),- 29 } 30 31 // инициализация компонента Weatherltem 32 public Weatherltem( WeatherBean bean ) 33 { 34 WeatherBean = bean; 35 } 36
668 Глава 14 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 } // отображение информации о погоде В городе public void paintComponerit ( Graphics g ) { super,paintCoraponent( g ) ; // рисование фока backgroundlmage.paintlcon( this, q, 0, 0 ); // задание шрифта к цвета для рисования, // затем отображение названия города и температуры Font font = new Font{ "SansSerif", Font.BOLD, 12 ); g.setFont{ font ); g.setColor( Color.white ); g.drawStringf weatherBean.getCityName(), 10, 19 ); g.drawstring{ weatherBean-getTemperature(), 130, 19 )• // вывод условного изображения характера погоды weatherBean.getlmageО.paintlconС this, g, 253, 1 1; } // конец метода paintComponent // установка в качестве желательных размеров окна для // компонента Weatberltem высоты и ширины фонового изображения public Dimension getPreferredSize() { return new Dimension( backgroundlmage.getlconWidthO , backgroundlmage.getlconHeight() ); Рис. 14.10. Класс Westherftem отображзет информацию о погоде в одном городе Развертывание сервиса службы погоды осуществляется точно так же, как и развертывание ранее рассмотренного нами сервиса передачи сообщений. Запустите средство работы с сервлетами Tomcat и откройте инструментальное средство администрирования SOAP (localhost:8080/soap/admin), как показано на рис, 14.11. *&щщ%% 1*ьЩЩ mmTJfwi^H ttM Щ^Щ^^^.. ' $Ш$Й':1ж!''' *?f * Ж Л\ «МЧИ» «-«ЯВЙвЯ- tft: ^tuuh^i ^iBcg,«4p/#l4VMaz .itrti ' ■1Щ$&Я&*$?*~т *.ЫМ№. - .....,.,....:.:;.. '■■■'&Ш'. ЗЗЯКг Apache SOAP Admi ^ЪлЕ do УК wuir to de [od*y" S}to*,'.j;, ■ •' ,, ЖШ "'"Ж,: Рис. 14.11. Страница средства администрирования Apache SOAP Admin
Введение в Web-сервисы и SOAP 669 Щелкните на Deploy и введите информацию в форму, как показано на рис. 14.12. Следует вводить полное имя пакета для WeatherService. Осуществив развертывание сервиса, выполните класс WeatherServiceClient. В результате вызова удаленной процедуры извлекается информацию о погоде, а клиент затем отображает ее (рис. 14.13). it * ..У*.'. ***' ■""■'и 'т«й '■ >«»*. j »»>**■» '^Ща^ряшЪ ai'-aig JSHWtiU-АН- 'el-Si U **s №utort,9wys«ft'«*T*V™*»* Ня4 шд **м Apache SUA I* Admin Л Deploy л Service Service Deployment Descriptor Tif fScft?e IfRaquait *} 'jt'^n + ,-* ;^t. ^ I -(WMespace icp^ated bst *f aictbod aimer;) s ^JT^ "1 j F ы ^J aer-"tbie^ f'roviitr Ty?eh Enter XJLL Clal S Nmm Number C-fOptOw [ Key ^Е—^ ~ jprjraier Ui^T |3impleteM;a ИГ -^ Рис. 14.12. Шаблон дескриптора развертывания Service Deployment Descriptor Template SOAP Apache ^5 DAP weatherSe ALBANY NY ANCHORAGE A1LANTA ATLANTIC CITY BOSTON tvice СПщгА ^|| 86'M f.2'5V B5 S9 ?&гЦ 8066 H^J L ft 0 li ■■=131*1 t '21 -1; Ш it Д1 &j fe - Л BURLINGTON VI BL№tj CHARLESTONVW 86'6J Ш1 Рис. 14.13. Клиент SOAP WeatherService Client
670 Глава 14 14.4. Ресурсы в Internet и во Всемирной паутине WMw.siin.com/9oftnaie/siinD-ne/index .html Сайт корпорации Sun Microsystems, посвященный спецификации Web-сервисов Open Net Environment (ONE), xml -apache.org/soap С этого сайта можно загрузить реализацию SOAP Apache. Здесь также можно найти документацию и иную информацию. xml.apache. ocg/xerces-j/index.html С этого сайта можно загрузить синтаксический анализатор Apache Xerces для Java, a также документацию. Jakarta.apache.org Web-сайт средства работы с сервлетами Apache Jakarta Tomcat. Резюме • Web-сервисом может быть любое доступное через Web приложение, например, Web-страница с динамическим содержимым. • В более узком смысле Web-сервис — это приложение, предоставляющее общедоступный интерфейс, который может использоваться другими приложениями в Web. • Архитектура Open Net Environment (ONE) требует, чтобы Web-сервис был доступен через протокол HTTP и другие Web-протоколы, осуществлял взаимодействие на баае XML-сообщений и был зарегистрировал сервисом поиска. • Web-сервисы могут обеспечивать высокий уровень совместимости между различными системами. Функциональная совместимость и расширяемость Weh-cepencos подразумевает, что разработчики могут быстро создавать большие приложения и более крупные Web-сервисы из более мелких. • Спецификация Open Net Environment, разработанная корпорацией Sun, описывает архитектуру для создания интеллектуальных Web-сервисов. Согласно заявлению Sun, интеллектуальные Web-сервисы совместно используют общее операционное окружение с другими сервисами. • SOAP представляет собой протокол па базе HTTP-XML, который дает возможность црило- жениям взаимодействовать через Internet, используя XML-документы, называемые XML-сообщениями. • SOAP является не зависящим от используемой платфоркы протоколом, я может быть реализован на любом языке программирования. SOAP поддерживает транспортировку с применением практически любого приемлемого протокола. • Сообщение SOAP содержит конверт, который описывает содержимое, предполагаемого получателя и требования к обработке сообщения. Необязательный элемент header сообщения SOAP содержит дополнительную информацию по обработке для приложений, которые получают сообщение SOAP. • С помощью заголовка поверх SOAP могут быть надстроены более сложные протоколы. Тело сообщения SOAP содержит специфичные для приложения данные, предназначенные предполагаемому получателю сообщения. » SOAP может использоваться для удаленного вызова процедур (Remote Procedure Call RPC), который представляет собой запрос, посылаемый другому компьютеру для выполнения определенной задачи. RPC использует словарь XML для указания метода, подлежащего активизации, передаваемых методу параметров и URL целевого объекта. » Поскольку различные кампании используют различные платформы, приложения и форматы данных, обмен данными может вызвать затруднения. В связи с этим партнеры но бизнесу устанавливают такие протоколы к форматы данных, которые способствуют эффективному ведению электронной коммерции.
Введение в Web-сервисы и SOAP 671 Терминология application-to-application (A2A) integration Remote Procedure Call (RPC) — удаленный — интеграция на уровне приложение — вызов процедур приложение request-response ~ запрос-ответ asynchronous RPC — асинхронный удален- schema — схема ный вызов процедур setMethodName, метод класса Call Call, класс setParams, метод класса Call deploying a service — развертывание сервиса Simple Object Access Protocol (SOAP) — про- distributed. object architecture — архитекту- токол простого доступа к объектам ра распределенных объектов Sun Open Net Environment — спецификация Fanlt, класс открытого сетевого окружения Sun firewall — брандмауэр, межсетевой экран synchronous RPC — синхронный удаленный Hypertext Transfer Protocol (HTTP) вызов процедур iovoke, метод класса Call Universal Description, Discovery and loosely coupled messaging — слабосвязанный Integration (UDDI) — универсальное опи- обмен сообщениями санке, обнаружение и интеграция messaging — обмен и передача сообщений Web services — Web-сервисы org.apache.soap.rpc XML-SOAP admin tool — инструментальное Parameter, класс средство администрирования XML-SOAP Упражнения для самоконтроля 14.1. Ответьте, является ли каждое из следующих высказываний истинным или ложным. Если высказывание ложно, объясните, почему. a) SOAP — это технология, которая способствует передаче данных через сеть, b) Чтобы протокол SOAP работал, он должен быть привязан к HTTP. c) Чтобы взаимодействовать посредством SOAP, программные системы должны иметь одинаковую распределенную объектную архитектуру. d) Тело сообщения SOAP может содержать удаленный вызов процедуры. 14.2. Заполните пропуски в следующих высказываниях: a) Удаленный вызов процедуры RPC SOAP требует указания имени вызываемого метода, его параметров и , b) SOAP содержит информацию, которая описывает содержимое, предполагаемого получателя и требования по обработке сообщения SOAP. c) SOAP допускает передачу через межсетевое экраны, поскольку он использует в качестве транспортного механизма. d) RPC SOAP используют модель HTTP. Ответы на упражнения для самоконтроля 14.1. а) Истинно. b) Ложно. SOAP может быть привязан к другим протоколам. c) Ложно. SOAP является не зависящим от используемой платформы протоколом. d) Истинно. 14.2. а) требований к обработке сообщения. b) Конверт. c) HTTP. d) запрос-ответ. Упражнения 14.3. Напишите серверный класс, содержащий метод sort, который может осуществлять сортировку заданных чисел. Напишите клиентскую программу, которая может осуществлять удалнный вызов RPC SOAP метода sort, отправляя при этом множество неотсортированных значений. Отобразите на клиенте результаты сортировки.
672 Глава 14 14.4. Модифицируйте класс WeatherServiceClient, чтобы обновлять информацию в нем через задаваемые пользователем интервалы времени. Измените настройки Tomcat, чтобы сделать объект WeatherService постоянно действующим (персистштным). Это позволит более эффективно осуществлять обновления. 14.5. Создайте клиентскую и серверную составляющие приложения с архитектурой, подобной той, которая использовалась для службы погоды, но получающие информацию о ценах с сайта сопоставления цен, такого как shopper.cnet.com.. 14.6. Создайте серверный класс, который способен хранить и извлекать строки. Выполните развертывание класса, чтобы он постоянно присутствовал на сервере (являлся перси- стентным). После этого создайте клиента, который хранит и извлекает строки с сервера. 14.7. Напишите простой одиоравговый сервис для неотложного обмена сообщениями. Создайте серверный класс, содержащий метод, который открывает окно с текстом сообщения при вызове этого метода клиентом. Клиент предоставляет пользователю возможность вводить текст сообщения и вызывать метод серверного класса для отображения сообщения на другом компьютере. Используемые источники 1. D. Savarese, «ONEWeb to Rule Them ALL». Java Pro August 2001: p. 58.
Этот файл был взят с сайта http://all-ebooks.com Данный файл представлен исключительно в ознакомительных целях. После ознакомления с содержанием данного файла Вам следует его незамедлительно удалить. Сохраняя данный файл вы несете ответственность в соответствии с законодательством. Любое коммерческое и иное использование кроме предварительного ознакомления запрещено. Публикация данного документа не преследует за собой никакой коммерческой выгоды. Эта книга способствует профессиональному росту читателей и является рекламой бумажных изданий. Все авторские права принадлежат их уважаемым владельцам. Если Вы являетесь автором данной книги и её распространение ущемляет Ваши авторские права или если Вы хотите внести изменения в данный документ или опубликовать новую книгу свяжитесь с нами по email.