/
Автор: Курняван Б.
Теги: компьютер компьютеризация издательство лори компьютерные приложения сервлеты
ISBN: 0-07-219147-3
Год: 2005
Текст
Создание web-приложений
на языке Java с помощью
сервлетов, JSP и EJB
Буди Курняван
Издательство «ЛОРИ»
Java for the Web with Servlets, JSP, and EJ В
Budi Kumiawan
Copyright 2002 All rights reserved
Создание Web-приложений на языке Java
с помощью сервлетов, JSP и EJB
Буди Курняван
Переводчик О. Труфанов
Научный редактор А. Головко
Корректор Т. Килимник
Верстка В. Странникова
Copyright © 2002 by New Rider Publishing. All rights reserved.
201 West 103rd Street, Indianapolis, Indiana 46290
An Imprint of Pearson Education
U.S.A.
ISBN 0-07-219147-3
© Издательство «ЛОРИ», 2005
Изд. № : OAI (03)
ЛР№: 070612 30.09.97 г.
ISBN 5-85582-242-7
Подписано в печать 15.06.2005 Формат 70x100/16
Бумага офсет №1 Гарнитура «Ньютон». Печать офсетная
Печ. л. 55. Тираж 1500. Заказ № ”19/ 05
Цена договорная
Издательство «Лори»
123100, Москва, Шмитовский пр., д. 13/6, стр. 1 (пом. ТАРПЦАО)
Телефон для оптовых покупателей: (095)2564)2-83
Размещение рекламы: (95)259-01-62
www.lory-press.ru
Типография ООО «Тиль-2004»
Москва, Дербеневская наб., 7
СОДЕРЖАНИЕ
ВВЕДЕНИЕ........................................................13
Протокол пересылки гипертекста (HTTP)..........................15
Системная архитектура..........................................17
Java 2 Enterprise Edition (J2EE)............................... 18
Разработка web-приложений на Java..............................20
Обзор содержания................................................21
Часть I. Создание Java-приложений для Web..........................25
Глава 1. Технология сервлетов...................................27
Достоинства сервлетов.........................................28
Архитектура сервлетных приложений........................... 30
Как работает сервлет.......................................... 31
Контейнер сервлетов Tomcat....................................31
Шесть шагов создания сервлета.................................33
Заключение.................................................... 38
Глава 2. Как устроены сервлеты..................................39
Пакет javax.servlet...........................................40
Жизненный цикл сервлета....................................... 40
Получение данных конфигурации.................................44
Сохранение объекта ServletConfig..............................47
Контекст сервлетов............................................48
Совместное использование информации сервлетами................50
Запросы и ответы..............................................53
Класс оболочки GenericServlet.................................59
Создание сервлетов, поддерживающих потоки выполнения..........61
Заключение....................................................66
Глава 3. Создание сервлетных приложений.........................67
Класс HttpServlet.............................................68
Интерфейс HttpServletRequest..................................72
HttpServletResponse...........................................81
Отправка кода ошибки..........................................85
Отправка специальных символов.................................85
Буферизация ответа............................................90
Заполнение элементов HTML.....................................90
Пересылка запроса.............................................92
Заключение................................................... 102
Глава 4. Доступ к базам данных с помощью JDBC..................103
Пакет java.sql.............................................. 104
Четыре шага для получения доступа к базе данных............. 109
Сервлет Login, использующий базу данных..................... 118
Фактор одиночной кавычки.................................... 125
Вставка данных в таблицу с помощью RegistrationServlet...... 128
Вывод всех записей на экран................................. 141
Страница поиска............................................. 143
Сетевая утилита SQL......................................... 148
Пппбпр.мя nnnnenwk'u лтипытлгл гпр.пиирниа 1SS
6
Содержание
Создание пулов соединений .................................... 157
Заключение.................................................... 158
Глава 5. Управление сеансом..................................159
Основы управления сеансом..................................... 160
Перезапись URL.............................................. 162
Скрытые поля.................................................. 177
Выбор метода................................................. 219
Заключение.................................................. 220
Глава 6. События приложения и сеанса.............................221
Прием событий приложения.......................................222
Прием событий HttpSession........;.............................232
Заключение.....................................................238
Глава 7. Фильтрация сервлетов....................................239
Обзор API......................................................240
Базовый фильтр.................................................242
Отображение фильтра на URL.....................................245
Фильтр регистрации.............................................246
Конфигурация фильтра...........................................249
Фильтр, проверяющий ввод пользователя .........................250
Фильтрация ответа..............................................257
Цепочка фильтров...............................................261
Заключение.....................................................266
Глава 8. Основы JSP........................................... 267
Недостатки сервлетов...........................................268
Создание простой страницы JSP................................. 270
Принцип работы JSP.............................................274
Сгенерированный код сервлета JSP...............................275
API JSP........................................................277
Анализ сгенерированного сервлета...............................280
Неявные объекты................................................285
Заключение.....................................................289
Глава 9. Синтаксис JSP...........................................290
Директивы......................................................291
Элементы создания сценариев....................................304
Стандартные элементы действий..................................316
Комментарии....................................................318
Преобразование в синтаксис XML.................................319
Заключение.....................................................320
Глава 10. Разработка JSP Bean....................................321
Вызов Bean на странице JSP................................... 322
Краткая теория JavaBeans.......................................323
Обеспечение доступа к Bean.....................................325
Доступ к свойствам с помощью jsp:getProperty и jsprsetProperty.330
Настройка значения свойства в запросе..........................333
Инициализация кода JavaBeans...................................334
Пример SQLToolBean.............................................336
Заключение.....................................................344
Глава И. Применение пользовательских тегов JSP...................345
Создание пользовательского тега................................346
Содержание
7
Роль дескриптора развертывания............................352
Дескриптор библиотеки тегов................................352
Синтаксис пользовательского тега..........................355
API пользовательских тегов JSP............................356
Жизненный цикл обработчика тегов..........................357
Заключение................................................371
Глава 12. Программируемая загрузка файлов...................372
Ключевые вопросы программируемой загрузки файлов..........373
Использование Bean загрузки файлов от компании Brainysoftware.com. 375
Заключение................................................376
Глава 13. Пересылка файлов..................................377
Запрос HTTP...............................................377
Клиентский HTML...........................................384
HTTP-запрос пересылки файла...............................385
Пересылка файла...........................................389
File Upload Bean..........................................393
Пересылка нескольких файлов...............................396
Заключение................................................396
Глава 14. Конфигурирование системы безопасности.............397
Наложение ограничений безопасности........................397
Использование нескольких ролей............................406
Аутентификация на основе формы............................407
Аутентификация на основе дайджеста........................410
Методы, связанные с системой безопасности.................411
Ограничение отдельных методов............................ 413
Заключение................................................414
Глава 15. Кэширование.......................................415
Кэширование данных в текстовом файле......................416
Кэширование в оперативной памяти..........................422
Заключение................................................. 428
Глава 16. Развертывание приложений..........................429
Структура каталога приложения.............................429
Дескриптор развертывания..................................431
Псевдонимы сервлетов и отображение........................454
Псевдонимы JSP и отображение..............................457
Упаковка и развертывание web-приложения...................459
Заключение................................................459
Глава 17. Проектирование web-приложений на языке Java.......460
Архитектура Model 1..................................... 460
Архитектура Model 2.......................................466
Заключение................................................469
Глава 18. Разработка приложений е-коммерции.................470
Спецификация проекта......................................471
Структура базы данных.....................................471
Проектирование страницы...................................473
Подготовка................................................473
Структура приложения......................................474
Создание проекта..........................................478
8
Содержание
Глава 19. E-Books на основе XML..............................500
Оглавление.................................................501
Трансляция XML в объектное дерево..........................502
Проект................................................... 505
Предварительное формирование оглавления....................519
Заключение.................................................519
Глава 20. Управление документами на основе Web...............520
Проект docman..............................................521
Заключение.................................................564
Часть II. Программирование клиента
с помощью JavaScript............................................565
Глава 21. Основы JavaScript..................................567
Введение в JavaScript.................................... 568
Объектная модель JavaScript................................578
Обработчик событий.........................................579
Объекты Window и String....................................580
Заключение.................................................582
Глава 22. Основы программирования на стороне клиента.........583
Проверка поддержки JavaScript..............................583
Проверка с помощью переадресации...........................584
Работа с браузерами, не поддерживающими JavaScript.........587
Работа с различными версиями JavaScript....................588
Включение файла JavaScript.................................589
Проверка операционной системы..............................590
Проверка поколения браузера................................592
Проверка типа браузера...................:.................593
Проверка языка браузера................................... 593
Использование динамических имен переменных.................594
Заключение................................................ 595
Глава 23. Переадресация......................................596
Предупреждение нарушения переадресации.....................596
Использование meta-тега refresh............................597
Использование объекта location.............................598
Возврат на предыдущую страницу.............................600
Перемещение вперед.........................................601
Перемещение с помощью элемента SELECT......................601
Заключение.................................................603
Глава 24. Проверка ввода на стороне клиента..................604
Функция isEmpty............................................605
Функция trim ..............................................606
Функция trim АП............................................608
Функция isPositivelnteger................................. 609
Функция isValidPhoneNumber.................................610
Функция isMoney............................................611
Функции is US Date и isOZDate..............................613
Преобразование форматов даты...............................616
Преобразование типов данных: строка в число................616
Преобразование типов данных: число в строку................618
Содержание
9
Использование функций проверки............................ 619
Заключение.................................................621
Глава 25. Работа с cookie на клиентской стороне...............622
Создание cookie с помощью тега <МЕТА>......................623
Создание cookie с помощью document.cookie..................624
Создание cookie с помощью функции setCookie................625
Чтение cookie в браузере...................................627
Удаление cookie в браузере.................................629
Проверка с помощью JavaScript возможности получения
cookie браузером............................................630
Проверка возможности получения cookie браузером
без помощи JavaScript.......................................631
Заключение.................................................632
Глава 26. Работа с деревьями объектов.........................633
Объект Array...............................................633
Истинное удаление элемента массива....................... 637
Создание объекта...........................................640
Иерархия объектов..........................................640
Заключение.................................................654
Глава 27. Управление апплетами................................655
Поддержка Java.............................................656
Готовность апплета.........................................656
Изменение размера апплета..................................658
Вызов метода апплета.......................................658
Получение свойства апплета.................................660
Задание свойства апплета...................................661
Прямое использование классов Java..........................662
Взаимодействие апплета и JavaScript........................663
Доступ к объектной модели документа в апплете..............666
Вызов функций JavaScript из апплета........................667
Анализ оператора JavaScript в апплете......................669
Задание параметра апплета..................................670
Коммуникации между апплетами с помощью JavaScript..........671
Прямые коммуникации между апплетами...................... 674
Заключение.................................................676
Часть III. Разработка масштабируемых приложений
с помощью EJB.............................................677
Глава 28. Enterprise JavaBeans................................679
Введение в EJB.............................................680
Преимущества EJB...........................................680
Архитектура приложения EJB.................................681
Шесть ролей EJB............................................682
Типы EJB.................................................. 683
Создание EJB...............................................683
Работа EJB.................................................687
Создание клиентского приложения............................690
Создание экземпляра EJB....................................693
Вызов EJB на странице JSP..................................695
- ГАГ
10
Содержание
Глава 29. EJ В сеанса......................................697
Определение EJB сеанса..................................697
EJB сеанса с поддержкой и без поддержки состояния.......698
Создание EJB сеанса.....................................699
Пример сетевого книжного магазина Tassie................705
Заключение..............................................726
Глава 30. EJB сущности.....................................727
Понятие о EJB сущности..................................727
Удаленный интерфейс.....................................728
Домашний интерфейс......................................729
Класс первичного ключа..................................731
EJB сущности............................................731
Два типа EJВ сущности...................................736
Создание EJB сущности типа BMP..........................736
Создание EJB сущности типа СМ Р.........................756
Заключение..............................................764
Глава 31. Язык запросов EJB................................765
Синтаксис EJB QL........................................766
BNFEJBQL................................................773
Заключение..............................................775
Глава 32. Служба сообщений Java.......................... 776
Введение в обмен сообщениями............................776
API JMS.................................................777
Области действия обмена сообщениями API JMS.............778
Объектная модель JMS....................................779
Создание клиента J MS...................................785
Заключение..............................................789
Глава 33. EJB, управляемые сообщениями.....................790
Введение в EJB, управляемые сообщениями.................790
Интерфейс прикладного программирования..................791
Создание управляемого сообщениями EJB...................792
Заключение..............................................798
Часть IV. Приложения.........................................799
А. Установка и конфигурирование Tomcat..................801
В. Справочник по пакету javax.servlet...................807
С. Справочник по пакету javax.servlet.http..............829
D. Справочник по пакету javax.servlet.jsp...............850
Е. Справочник по пакету javax.servlet.jsp.tagext........860
Е Установка и конфигурирование J Boss...................873
G. Дополнительные ресурсы...............................878
Об авторе
Буди Курняван является консультантом по информационным технологиям,
применяемым в Интернете. Он специализируется на объектно-ориентирован-
ном программировании и обучает технологиям Java и Microsoft. Буди является
автором наиболее популярного Java Upload Bean компании BrainySoftware.com,
который лицензирован Commerce One (NASDAQiCMRC) и куплен такими
крупными корпорациями, как Saudi Business Machine Ltd (www.sbm.com.sa),
Baxter Healthcare Corporation (www.baxter.com) и др.
Буди получил степень Masters of Research по электротехнике в университете
Сиднея, Австралия. Темой его исследования была цифровая обработка изоб-
ражений. Буди написал ряд книг по вычислительной технике, а также опубли-
ковал более десяти статей в таких престижных журналах по Java, как Java-Pro,
JavaWorld, JavaReport и www.onjava.com O’Reilly. Буди ведет еженедельный раз-
дел Servlets/JSP в Java Insight. Связаться с ним можно по адресу
b • id i @brai n у soft ware. com.
О рецензентах
Крис Крейн и Лан By имеют богатый практический опыт. Когда книга была
написана, они просмотрели весь материал на предмет качества технического
материала, его организации и изложения. Их замечания были критически важ-
ными в процессе подготовки книги.
Крис Крейн в настоящее время преподает в Мемориальном университете
Ньюфаундленда, где он ведет курсы по программированию, охватывающие
широкий спектр языков и концепций программирования: от разработки на-
стольных приложений с помощью Visual Basic, C++ и Java до распределенных
приложений масштаба предприятия, использующих EJB и web-службы
Microsoft .NET. Крис возглавляет свою собственную консалтинговую компа-
нию, разрабатывающую приложения уровня предприятия для компаний по
всей Канаде и США. Он является сертифицированным специалистом МСР,
MCSD, МСТ, CCNA, CCAI и SCP/Java2.
Лан By имеет степень магистра компьютерных наук. Она работала в Persistent
Software в Силиконовой долине. Там ее усилия были сосредоточены на Java и
EJB. Позже она перешла в myCFO Corporation, где была вовлечена в проек-
тирование и разработку систем автоматизации. Сейчас она трудится в Blue
Martini Software и отвечает за автоматизацию с помощью Java и web-програм-
мирования.
12
Об авторе
Благодарности
В процесс подготовки этой книги было вовлечено много людей, без участия
которых она никогда не стала бы реальностью.
Прежде всего я хотел бы поблагодарить редактора Дебору Хиттель-Шоаф
за ее профессионализм и гибкость. Она действительно заботится о своих ав-
торах.
Спасибо редактору Грант Манро за ее терпение. Она привела в порядок все
главы.
Два прекрасных рецензента, Лан By и Крис Крейн, помогли мне значительно
улучшить содержимое. Я также хотел бы их поблагодарить.
Наконец, хочу сказать большое спасибо всем сотрудникам издательства New
Riders, которые помогали мне в подготовке чертежей, поиске ошибок, состав-
лении индекса, компоновке и т. д.
Специальная благодарность моему другу Кену за предоставленное мне
прекрасное место жительства (и доступ в Интернет) во время моего Рожде-
ственского визита в Ванкувер. Его компания и поездки дали мне отличный
отдых.
Ваше мнение о книге
Как читатель этой книги вы являетесь наиболее важным критиком и коммен-
татором. Мы ценим ваше мнение и хотим знать, что сделано хорошо, что мож-
но было бы улучшить, что следовало бы переиздать.
Вы можете послать факс, e-mail или написать мне непосредственно. Сооб-
щите, что вам понравилось или не понравилось в этой книге, что нужно сде-
лать, чтобы наши книги стали лучше.
Однако должен сказать, что я не в состоянии решить ваши технические про-
блемы. Кроме того, в связи с большим объемом получаемой почты я не смогу
ответить на все сообщения.
Не забудьте указать название книги и автора, а также свое имя и телефон
или номер факса. Ваши комментарии будут внимательно просмотрены и пе-
реданы автору и редакторам, которые работали с книгой.
Факс:317-581-4663
E-mail: stephanie.wall@newriders.com
Почтовый адрес: Staphanie Wall
Associate Publisher
New Riders Publishing
201 West 103-rd Street
Indianapolis, IN 46290 USA
ВВЕДЕНИЕ
Сеть Интернет все еще молода и уязвима. Ее история не слишком длинна. Вна-
чале в Паутине все было просто статическими страницами. Читатель наверняка
помнитто время, когда web-сайт состоял лишь из страниц HTML. Web-сайт имел
максимум одну страницу, которая чаще всего называлась домашней страницей.
Термины «Интернет-приложение» и «web-приложение» появились, когда
было введено динамическое содержимое. В свободной интерпретации web-
приложение представляет собой web-сайт, содержимое которого создается
динамически перед отправкой браузеру. Прежде чем изучать работу web-при-
ложения, необходимо понять, как работает Интернет.
При перемещении в Интернете обычно запрашивается некоторый файл,
находящийся на компьютере, местоположение которого определяется универ-
сальным указателем ресурса (URL). Компьютер, где хранится файл, называется
web-сервером. Основная функция этого компьютера состоит в обслуживании в
Интернете любого, кому требуются размещенные на нем файлы. Так как невоз-
можно знать, когда пользователь посетит и воспользуется web-приложением,
web-сервер должен функционировать все время.
Когда в поле Address или Location браузера вводится URL, происходят сле-
дующие вещи:
• Браузер клиента создает соединение TCP/IP с сервером.
• Браузер посылает запрос серверу.
• Сервер посылает ответ клиенту.
• Сервер закрывает соединение.
Отметим, что после отправки запрошенной страницы браузеру, сервер всегда
закрывает соединение независимо от того, будет или нет клиент запрашивать
другие страницы на сервере.
I Конкурентная борьба в отрасли
С момента возникновения Интернета технологии Web становятся все
более и более важными, а web-приложения все более и более
распространенными. Использование web-браузера не ограничивается
одним лишь перемещением по статическим страницам в Интернете.
Нередко web-браузер применяется теперь как клиент приложения.
Многие считают, что тот, кто контролирует Интернет, контролирует
будущее вычислительной техники — или даже само будущее. Это было
продемонстрировано борьбой двух компаний за доминирование в области
web-браузеров в конце 1990-х гг. Корпорация Microsoft — игрок номер один
r отпасли ппогпаммипования — чувствовала важность того, чтобы каждый
Введение
Именно поэтому корпорация приложила огромные усилия к созданию самого
быстрого и удобного браузера и стала распространять его бесплатно.
Корпорация Microsoft выиграла борьбу браузеров, a Netscape потерпела
поражение. В последующие пять лет трудно представить, чтобы какой-либо
браузер смог превзойти популярность Internet Explorer корпорации Microsoft.
Однако на серверной стороне другая история. Война далека от
завершения. Microsoft не может протолкнуть свою серверную технологию,
Netscape не сдается. Фактически самой популярной серверной
технологией является Java. Говоря точнее, это Java 2 Enterprise Edition
(J2EE) компании Sun Microsystems. Microsoft все еще пытается привлечь
внимание к своей новой инициативе .NET, которая является заменой ее
предыдущей платформы DNA (Distributed InterNet Applications) для
разработки приложений уровня предприятия. Вышедшая в начале 2002 г.,
.NET вступает в противоборство с J2EE. Следующие несколько лет будут
по-прежнему демонстрировать J2EE и .NET как две конкурирующие
серверные технологии. В данный момент еще слишком рано
предсказывать, кто окажется победителем.
Стратегически мудрая компания Microsoft использует иной подход, по
сравнению с Sun. Microsoft предоставляет решение одного поставщика: от
операционной системы до сервера базы данных. С другой стороны, J2EE
поддерживается всей отраслью. (Список поставщиков, которые предлагают
серверы, поддерживающие J2EE, можно найти в приложении G).
Аналитическое сравнение J2EE и .NETдается во множестве документов,
опубликованных в Интернете. К сожалению, выводы существенно
различаются. Чтобы больше узнать о сравнении J2EE и .NET, можно
обратиться к статьям, доступным в сети:
Сравнение Microsoft .NET и J2EE: Как они соответствуют друг другу?
http://java.oreilly.com/news/farley_0800.html
Сравнение Java 2 Enterprise Edition и платформы .NET: Две версии для
е-бизнеса.
http://www.objectwatch.com/FinalJ2EEand DotNet.doc
J2EE и Microsoft.NET: Сравнение создания web-служб на основе XML.
http://www.theserverside.com/resources/article.jsp?l=J2EE-vs-DOTNET
Сравнение Microsoft.NET с технологией J2EE.
http://msdn.microsoft.com/net/compare/default.asp
Дополнительную информацию о .NET можно найти по адресу http://
msdn.microsoft.com.
Отметим, что термин web-сервер может применяться также к программному
пакету, который используется компьютером web-сервера для обработки
запросов и ответов. Фактически в этой книге термин web-сервер служит для
обозначения именно такого программного обеспечения.
Первый популярный web-сервер — NCSA HTTPd — был создан Робом
Маккулом в Национальном центре приложений суперкомпьютеров
(NCSA). Изобретение Маккула действительно было великолепным. Со
временем оно легло в основу web-сервера Apache. Это наиболее часто
используемый сегодня в Интернете web-сервер.
Введение
15
Протокол пересылки гипертекста (HTTP)
HTTP (Hypertext Transfer Protocol) является протоколом, который позволяет
web-серверам и браузерам обмениваться данными в Web. Это протокол зап-
роса и ответа. Клиент запрашивает файл, а сервер отвечает на запрос. HTTP
использует надежные соединения TCP — по умолчанию ТСР-порт 80. HTTP
версии 1.1 был определен в RFC 2068. Затем он был уточнен в RFC 2616, ко-
торый можно найти по адресу http://www.w3c.org/Protocols/.
В HTTP транзакцию всегда инициирует клиент, создавая соединение и
посылая запрос HTTP. Сервер не может контактировать с клиентом или уста-
навливать с клиентом соединение обратного вызова. И клиент, и сервер могут
преждевременно прервать соединение. Например, можно нажать кнопку Stop
в браузере, чтобы остановить процесс загрузки файла, эффективно закрыв
соединение HTTP с web-сервером.
Запросы HTTP
Транзакция HTTP начинается с запроса, посылаемого браузером клиента,
и заканчивается ответом сервера. Запрос HTTP состоит из трех компонентов:
• Метод — URI — Протокол/версия
• Заголовки запроса
• Тело запроса
Приведем пример запроса HTTP:
GET /servlet/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
Referer: http://localhost/ch8/SendDetails.htm
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
LastName=Franks&FirstName=Michael
Метод — URI — протокол/версия появляются в первой строке запроса.
GET /servlet/default.jsp HTTP/1.1
где GET является методом запроса, /servlet/default. j sp представляет собой U RI,
16
Введение
Метод запроса подробно рассматривается в следующем разделе.
URI полностью определяет ресурс Интернета. UR1 интерпретируется
обычно относительно корневого каталога сервера, поэтому он должен всегда
начинаться с прямого слэша (/). URL является в действительности типом UR1
(см. http://www.ietf.org/rfc/rfc2396.txt). Версия протокола представляет ис-
пользуемую версию протокола HTTP.
Заголовок запроса содержит полезную информацию о рабочей среде кли-
ентаи о теле запроса. Например, он может описывать язык, используемый
браузером, длину тела и т. д. Каждый заголовок отделяется последовательностью
символов возврат каретки/перевод строки (CRLF).
Формат запроса HTTP требует, чтобы между заголовками и телом запроса
стояла пустая строка (CRLF). CRLF сообщает серверу HTTP, где начинается
тело запроса. В некоторых книгах по программированию Интернета символы
CRLF считаются четвертым компонентом запроса HTTP.
В предыдущем запросе HTTP телом является строка:
LastName=Franks&FirstName=Michael
Тело типичного запроса HTTP может быть значительно длиннее.
Методы запроса HTTP
Каждый запрос HTTP может использовать один из методов, определенных
в стандартах HTTP. Методы запроса HTTP 1.1 представлены в таблице 1.1.
Таблица 1.1. Методы запроса HTTP 1.1
Метод GET Описание Самый простой и, возможно, наиболее часто используемый метод HTTP. GET извлекает данные, идентифицированные URL. Если URL указывает на сценарий (CGI, сервлет и т. д.), GET возвращает данные, созданные сценарием.
•HEAD Метод HEAD предоставляет ту же функциональность, что и GET, но возвращает только заголовки HTTP без тела документа.
POST POST также широко используется, как GET. Обычно POST применяется в формах HTML. Он служит для пересылки блокаданных серверу в теле запроса.
OPTIONS Метод OPTIONS испол ьзуется для запроса предоставляемых сервером возможностей. Запросы могут быть общими или специальными к определенному ресурсу.
Введение
17
PUT Метод PUT является дополнением к запросу GET PUT
сохраняет тело запроса в месте, определенном URI.
По своим функциям он аналогичен PUT в FTP.
DELETE Метод DELETE используется для удаления документа
с сервера. Удаляемый документ указывается в разделе URI
запроса.
TRACE Метод TRACE прокладывает путь доступа запроса через
брандмауэр и прокси-серверы. TRACE полезен при отладке
сложных сетевых проблем и аналогичен утилите traceroute.
Предупреждение! HTTP 1.0 имеет только три метода запроса: GET, HEAD и POST.
В приложениях Интернета из семи методов обычно используются только
GET и POST.
Ответы HTTP
Аналогично запросу, ответ HTTP состоит из трех частей:
• Протокол — код статуса — описание
• Заголовки ответа
• Тело ответа
Приведем пример ответа HTTP:
НТТР/1.1 200 ОК
Server: Microsoft-IIS/4.0
Date: Mon, 3 Jan 1998 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 11 Jan 1998 13:23:42 GMT
Content-Length: 112
<HTML>
<HEAD>
<TITLE>HTTP Response Example</TITLE></HEAD><BOOY>
Welcome to Brainy Software
</BODY>
</HTML>
Первая строка заголовка ответа аналогична первой строке заголовка запро-
са. Она сообщает, что используется протокол HTTP версии 1.1, запрос выпол-
нен успешно (код 200), и все идет нормально.
18
Введение
Заголовки ответа несут полезную информацию, подобно заголовкам запроса.
Тело ответа содержит HTML. Заголовки и тело ответа разделяются символами
CRLF.
Системная архитектура
В этом разделе дается общее представление о прикладной программной системе,
использующей Java или другие технологии. В качестве введения в архитектуру
программной системы рассмотрим пути ее развития.
Хорошо спроектированное программное приложение делится на отдельные
логические части, называемые уровнями. Каждый уровень выполняет опреде-
ленные функции в приложении. Уровни являются чистой абстракцией и не
соответствуют физическому распределению.
Типичными уровнями программной системы являются:
• Уровень представления. Здесь находятся части, которые обрабатывают
пользовательский интерфейс и взаимодействие с пользователем.
• Уровень бизнес-логики. Содержит компоненты, которые обрабатывают
программную логику в приложении.
• Уровень данных. Используется бизнес-логикой для длительного хранения
состояния. Обычно включает в себя одну или несколько баз данных, в ко-
торых хранятся данные. Могут также применяться другие типы хранилищ
данных. Например, сейчас для хранения данных все шире используются
документы XML.
Двухзвенная архитектура
Двухзвенное приложение является простым клиент-серверным приложе-
нием, в котором нагрузка обработки ложится на плечи компьютера клиента, а
сервер действует как контроллер трафика между клиентом и данными. Термин
«толстый клиент» для этого типа архитектуры связан с большим объемом
требований по обработке на клиентской стороне. В двухзвенной архитектуре
уровень представления и уровень бизнес-логики объединены в один уровень,
и другим уровнем является уровень данных (см. рис. 1.1).
Рис. 1.1. Двухзвенное приложение
Недостаток архитектуры этого типа состоит в том, что по мере роста числа
клиентов начинают возникать проблемы. Первая проблема связана с тем фак-
Введение
19
тем, что вся обработка происходит на клиентской стороне. Сетевой трафик
возрастает, так как каждый клиент должен делать несколько запросов данных
на сервере — еще до предоставления чего-либо пользователю.
Другой проблемой является стоимость, потому что каждому клиенту требу-
ется машина с достаточной процессорной мощностью. По мере роста числа
клиентов стоимость обеспечения клиентскими машинами может стать астро-
номической.
Однако наиболее суровой проблемой двухзвенной архитектуры является,
вероятно, проблема сопровождения. Даже незначительное изменение в логике
обработки может потребовать полной перестройки организации. Этот процесс
может быть автоматизирован, но в крупных организациях возникают проблемы
из-за того, что некоторые пользователи оказываются не готовы к модерниза-
ции, вто время как другие настаивают на ее немедленном выполнении.
Трехзвенная архитектура
Для решения проблемы клиентских двухзвенных приложений приложение
разбивается на три отдельных уровня. Первый — уровень представления,
второй, или промежуточный, уровень содержит бизнес-логику, и третий уро-
вень — это уровень данных (см. рис. 1.2).
Приложение
Клиент
База данных
Сервер
Рис. 1.2 . Трехзвенное приложение
N-звенная архитектура
Чтобы добиться большей гибкости, три уровня приложения можно раздро-
бить еще больше. Приложение с такой архитектурой называется п-звенным
приложением. В этой архитектуре уровень бизнес-логики делится функцио-
нально, а не физически. Он разбивается следующим образом:
• Интерфейс пользователя. Обрабатывает взаимодействие пользователя с
приложением. В Интернет-приложениях для отображения тегов HTML
обычно применяется web-браузер.
• Логика представления. Определяет, что интерфейс пользователя выво-
дит на экран и как обрабатываются пользовательские запросы.
• Бизнес-логика. Здесь моделируется бизнес-логика приложения.
• Службы инфраструктуры. Предоставляют дополнительные функции,
необходимые компонентам приложения.
• Уровень данных. Содержит данные приложения.
20
Введение
Java 2 Enterprise Edition (J2EE)
J2EE не является продуктом. Это скорее спецификация, которая определяет
контракт между приложениями и контейнером. Контейнером здесь называется
стандартизованная среда времени выполнения, которая предоставляет спе-
циальные службы для развернутых в ней компонентов. Более подробно J2EE
описывается в части 111.
Разработка web-приложений на Java
При разработке web-приложений на языке Java обычно применяются две
основные архитектуры. Для обслуживания клиентов и обработки бизнес-ло-
гики первая архитектура использует сервлеты и JSP на среднем уровне. Эта
архитектура представлена на рис. 1.3.
Рис. 1.3. Архитектура приложений сервлеты/JSP
Эту модель используют небольшие приложения и приложения среднего
размера.
Вторая архитектура использует сервер J2EE и Enterprise JavaBeans (EJ В), что
особенно полезно при создании больших приложений уровня предприятия,
которым требуется масштабирование. Эта архитектура показана на рис. 1.4. EJ В
обсуждаются в части 111 книги.
Рис. 1.4. Архитектура приложений J2EE
ОБЗОР СОДЕРЖАНИЯ
Эта книга состоит из четырех частей, включая приложения.
Часть I. Создание Java-приложений для Web
Эта часть состоит из 20 глав, в которых рассказывается о сервлетах и серверных страни-
цах Java (JSP).
Глава 1, «Технология сервлетов», знакомит с технологией сервлетов и сравнивает ее с
другими существующими технологиями Web. Здесь рассказывается о том, как писать серв-
леты. Даются пошаговые инструкции по быстрому конфигурированию контейнера сервлетов,
по компиляции сервлета и по развертыванию его в контейнере. При правильном следовании
инструкциям вы сможете увидеть ваш сервлет в действии, используя web-браузер.
Глава 2, «Как устроены сервлеты», рассматривает особенности последней редакции ин-
терфейса прикладного программирования (API) спецификации сервлетов Java версии 2.3.
Здесь описывается первый из двух пакетов, доступных для программистов сервлетов:
javax.servlet. Этот пакет содержит базовые классы и интерфейсы, которые можно использо-
вать при написании сервлетов. В этой главе обсуждаются такие важные концепции, как
жизненный цикл сервлета, контекст сервлета, запросы, ответы, и как писать сервлеты, под-
держивающие потоки выполнения.
Глава 3, «Создание сервлетных приложений», рассматривает классы и интерфейсы в пакете
javax.servlet.http. По сравнению с пакетом javax.servlet, пакет javax.servlet.http предлагает более
развитые классы и интерфейсы, которые расширяют классы и интерфейсы javax.servlet. Рас-
сказывается об использовании таких методов, как получение значений, посланных пользо-
вателем, методы HTTP, буферизация ответа, диспетчеризация запросов и включение других
ресурсов в сервлет.
Глава 4, «Доступ к базам данных с помощью JDBC», показывает, как можно получать
доступ и манипулировать данными в базе данных с помощью JDBC. Начинается глава с
рассмотрения объектной модели пакета java.sql. Подробно объясняется, как устанавливать
соединение с базой данных. Рассматривается многоцелевая утилита, которая позволяет
вводить оператор SQL в браузере, выполнять его на сервере и получать результат в браузере.
Глава 5, «Управление сеансом», показывает, как важно иметь возможность управлять сеан-
сом пользователя и сохранять состояние предыдущих запросов. Вводятся такие методы, как
перезапись URL, скрытые поля и cookie. Однако контейнер сервлетов предлагает свое
собственное средство автоматического управления сеансом, которое упрощает управление
сеансом пользователя.
Глава 6, «События приложения и сеанса», рассказывает о новых возможностях специ-
фикации сервлетов 2.3. Рассматриваются также события, которые были доступны в пре-
дыдущих версиях спецификации. Вы узнаете, как прослушивать и перехватывать события
приложения и сеанса и конфигурировать дескриптор развертывания.
Глава 7, «Фильтрация сервлетов», описывает еще одно новое средство спецификации
сервлетов 2.3. Здесь показано, как можно использовать фильтрацию сервлетов для выпол-
нения таких важных задач, как предварительная обработка запроса HTTP.
Глава 8, «Основы JSP», знакомит со второй web-технологией Java, которая должна
использоваться совместно с сервлетами. Рассматриваются ситуации, где желательно исполь-
зовать JSP, и обсуждаются отношения между сервлетами и JSP.
Глава 9, «Синтаксис JSP», представляет синтаксис серверных страниц Java. В частности,
обсуждаются директивы, элементы сценариев и элементы действия. Даются примеры
использования этих элементов.
22
Обзор содержания
Глава 10, «Разработка JSP Веап», вводит ориентированный на компоненты подход для со-
здания приложений JSP с помощью JavaBean. Этот подход обеспечивает разделение труда.
Программист Java пишет и компилирует JavaBean, который реализует всю функциональ-
ность, необходимую в приложении, а разработчик страниц трудится вто же самое время над
дизайном страниц. Когда JavaBean будет готов, дизайнер страниц использует теги для вызова
методов и свойств Bean на странице JSP.
Глава 11, «Применение пользовательских тегов JSP», объясняет, что такое пользователь-
ские теги и как применять их для выполнения пользовательских действий на странице JSP.
Описываются замечательные свойства пользовательских тегов, и начинается глава с созда-
ния страницы JSP, применяющей пользовательские теги. Кроме того, рассматриваются классы
и интерфейсы пакетаjavax.servlet.jsp.tagext.
[лава 12, «Программируемая загрузка файлов», описываеттехнологию, которая позволяет
программным путем посылать файлы браузеру. Эта техника предоставляет программисту пол-
ный контроль над загружаемым файлом. Рассматривается пример программной загрузки
файла со страницы JSP.
Глава 13, «Пересылка файлов», объясняет все, что нужно знать о пересылке файлов, вклю-
чая базовую теорию запросов HTTP. Знание запроса HTTP является критически важным,
потому что при пересылке файла приходится иметь дело с необработанными данными. В
последнем разделе главы рассказывается о File Upload Bean от Brainysoftware.com, который
можно найти на сайте издательства «Лори» www.lory-press.ru.
Глава 14, «Конфигурирование системы безопасности», представляет технологию защиты
web-приложения, использующую конфигурирование дескриптора развертывания для
указания web-контейнеру на ограничение доступа к некоторым или всем ресурсам. Конфи-
гурирование означает, что необходимо только модифицировать файл дескриптора развер-
тывания — при этом не требуется никакого кодирования.
Глава 15, «Кэширование», предлагает два метода кэширования для повышения произ-
водительности приложения: кэширование данных в текстовом файле и кэширование дан-
ных в оперативной памяти. Первое решение записывает часто используемые, но редко
изменяющиеся данные в текстовые файлы. Когда приложению требуются данные из базы
данных, вместо обращения к серверу базы данных приложение может использовать тек-
стовый файл. Второй метод кэширует данные в памяти, что может существенно улучшить
производител ьность.
Глава 16, «Развертывание приложений», описывает процесс развертывания сервлета и
приложения JSP. Чтобы правильно развернуть web-приложение, необходимо сначала изучить
структуру каталогов приложения. Еще одной темой является дескриптор развертывания, где
можно сконфигурировать каждое приложение.
Глава 17, «Проектирование web-приложений на Java», представляет две модели при-
ложений сервлеты/JSP. Вы узнаете, как выбрать для приложения правильную архитектуру.
Глава 18, «Разработка приложений е-коммерции», представляет приложение сетевого ма-
газина, которое использует архитектуру Model 2, обсуждаемую в главе 17. Это законченное
приложение охватывает большинство свойств приложения е-коммерции.
Глава 19, «E-Books на основе XML», представляет проект для использования в качестве
сетевой справочной системы, содержимое которой основывается на документе XML.
Глава 20, «Управление документами на основе Web», предлагает законченную утилиту
управления документами. Интерфейс пользователя выглядит, как Проводник Windows внутри
web-браузера. Можно исследовать структуру базы данных, которая управляет всеми объек-
тами, представляющими документы, и расширить функциональность для удовлетворения
потребностей.
Обзор содержания
23
Часть II. Программирование клиента с помощью
JavaScript
Эта часть содержит семь глав, рассказывающих о том, как использовать JavaScript в ка-
честве языка программирования клиента в web-приложениях.
Глава 21, «Основы JavaScript», является введением в JavaScript и в программирование
клиентской стороны web-приложения.
Глава 22, «Основы программирования на стороне клиента», рассматривает преимуще-
ства программирования на клиентской стороне, которые не только способствуют повыше-
нию уровня масштабируемости web-приложения, но также делают более удобной работу
пользователя. Вы узнаете о проблемах, встречающихся при программировании на клиентской
стороне.
Глава 23, «Переадресация», обсуждает различные методы переадресации пользователей
на другие ресурсы. Переадресация широко используется в web-приложениях, и важно выб-
рать правильный метод переадресации.
[лава 24, «Проверка ввода на.стороне клиента», предоставляет методы для выполнения
проверки ввода на клиентской стороне. Проверка на клиентской стороне гарантирует, что
значения элементов формы являются действительными, до отправки формы. С точки зре-
ния сервера это означает сокращение нагрузки, так как не требуется возвращать пользова-
телю форму для исправления значения. Пользователи же получают более быстрый ответ,
так как они немедленно предупреждаются о неверном вводе в форму. Рассматриваются два
типа проверки ввода: на уровне формы и на уровне элемента формы.
Глава 25, «Работа с cookie на клиентской стороне», подробно рассматривает cookie, в
частности, как обращаться с cookie на клиентской стороне (например, в браузере). Даются
рекомендации по работе с cookie, включая создание, удаление и редактирование cookie как
на серверной, так и на клиентской стороне.
Глава 26, «Работа с деревьями объектов», предлагает метод для работы с объектами в иерар-
хии. Этот метод используется в проектах по созданию утилиты управления документами и
оперативной справки на основе XML, представленных в главах 19 и 20.
Глава 27, «Управление апплетами», не рассматривает вопросы создания апплетов. Здесь
обсуждаются различные аспекты работы с апплетами: как можно управлять апплетами со
страницы HTML с помощью JavaScript. Управление апплетом включает в себя выполнение
методов апплета, считывание его свойств и передачу ему значения для дальнейшей обработки.
Часть III. Разработка масштабируемых
приложений с помощью EJB
Эта часть содержит шесть глав, рассказывающих о Enterprise JavaBeans (EJB), которые
помогают в разработке масштабируемых приложений.
Глава 28, «Enterprise JavaBeans», служит в качестве введения в Enterprise JavaBeans (EJ В).
Она начинается с определения EJB и представляет некоторые преимущества использова-
ния EJB — большинство из них недоступны для сервлетов/JSP. Затем обсуждаются архитек-
тура и отдельные рол и в приложении EJB и в жизненном цикле развертывания. Приводится
пример приложения, и даются некоторые технические рекомендации при рассмотрении
пакета javax.ejb. В конце представлены два клиентских приложения для тестирования
приложения-примера.
24
Обзор содержания
Глава 29, «EJ В сеанса», представляет первый тип EJB: Bean сеанса. Начинается глава
с обзора Bean сеанса, и описываются два типа Bean сеанса: с поддержкой состояния и без
поддержки состояния. После обсуждения API рассматривается пример использования Bean
сеанса. Вы узнаете, как писать клиентские приложения, которые применяют EJB.
Глава 30, «EJB сущности», описывает два типа Bean сущности: Bean сущности, сохране-
нием которых управляет EJВ (BMP), и Bean сущности, сохранением которых управляет кон-
тейнер (СМР).
Глава 31, «Язык запросов EJВ», представляет язык, добавленный в спецификацию EJB
2.0: язык запросов Enterprise Java Beans Query Language (EJB QL). EJB QL похож на SQL (язык
структурированных запросов) и используется разработчиками EJB для выборки данных в
методах поиска EJ В сущности типа СМ Р.
Глава 32, «Служба сообщений Java», предлагает введение в обмен сообщениями и API
JMS. Приводится несколько примеров, которые используют J MS для отправки и получения
сообщений. Вы приобретете базовые знания для работы с третьим типом EJB — Bean,
управляемыми сообщениями (MDB).
Глава 33, «EJB, управляемые сообщениями», начинается с обзора управляемых сооб-
щениями Bean и с подробного рассмотрения объектной модели. Приводится пример EJB,
управляемого сообщениями, и показывается, как развернуть его в J Boss.
Приложения
Последняя часть книги состоит из семи приложений.
Приложение А. Установка и конфигурирование Tomcat
Приложение В. Справочник по пакету javax.servlet
Приложение С. Справочник по пакету javax.servlet.http
Приложение D. Справочник по пакету javax.servlet.jsp
Приложение Е. Справочник по пакету javax.servlet.jsp.tagext
Приложение Е Установка и конфигурирование J Boss
Приложение G. Дополнительные ресурсы
I
Создание
Java-приложений
для Web
1 Технология сервлетов
2 Как устроены сервлеты
3 Создание сервлетных приложений
4 Доступ к базам данных с помощью JDBC
5 Управление сеансом
6 События приложения и сеанса
7 Фильтрация сервлетов
8 Основы JSP
9 Синтаксис JSP
10 Разработка J S Р Bean
11 Применение пользовательских тегов JSP
12 Программируемая загрузка файлов
13 Пересылка файлов
14 Конфигурирование системы безопасности
15 Кэширование
16 Развертывание приложений
17 Проектирование web-приложений на Java
18 Разработка приложений е-коммерции
19 E-Books на основе ХМ L
20 Управление документами на основе Web
1
Технология сервлетов
1 ехнология сервлетов является фундаментом разработки web-приложений
с помощью языка программирования Java. Это одна из наиболее важных тех-
нологий Java, и она лежит в основе другой популярной технологии Java для
разработки web-приложений: серверных страниц Java (JSP, JavaServer Pages).
Как разработчик сервлетов вы должны понимать технологию сервлетов и их
архитектуру. Даже в случае разработки Java-приложения для Web только с по-
мощью страниц JSP понимание технологии сервлетов поможет создать более
эффективные и надежные приложения JSP.
Цель этой главы — познакомить вас с технологией сервлетов. Предоставля-
ются пошаговые инструкции, которые позволят вам создать и выполнить сер-
влетное приложение.
Обсуждаются следующие вопросы:
• Преимущества сервлетов
• Архитектура сервлетных приложений
• Как работают сервлеты
• Как написать и выполнить сервлетное приложение
В этой книге в качестве контейнера сервлетов и контейнера JSP использу-
ется Tomcat 4.0. В данной главе будет показано, как быстро сконфигурировать
Tomcat, чтобы можно было выполнить простое сервлетное приложение.
28
Глава 1
(^Примечанием Полное описание конфигурирования приложения дается в
главе 16. Дополнительные сведения об установке Tomcat
можно найти в приложении А.
Достоинства сервлетов
В начале своего развития Интернет состояла только из статического содержи-
мого, создаваемого с помощью языка разметки гипертекста (Hypertext Markup
Language, HTML). В то время любой, кто мог создать страницу HTML, считался
специалистом по Интернету. Это, однако, продолжалось недолго.
Динамическое содержимое стало доступно в Web благодаря технологии ин-
терфейса общего шлюза (Common Gateway Interface, CGI). CGI позволяет web-
серверу вызывать внешнюю программу и передавать ей данные запроса HTTP
для его обработки. Ответ внешней программы передается затем web-серверу,
который пересылает его клиентскому браузеру. Программы CGI могут быть
написаны на любом языке, который способен вызывать web-сервер. Со време-
нем самым популярным языком для написания программ CGI стал Perl.
Однако по мере роста популярности Интернета число пользователей, посе-
щающих популярный web-сайт, росло экспоненциально, и стало очевидно, что
CGI не способен обеспечить масштабируемость приложений Интернета. Не-
достаток CGI состоит в том, что каждый клиентский запрос заставляет web-
сервер порождать новый процесс запрошенной программы CGL Как известно,
создание процесса является дорогой операцией, которой требуется много
процессорных циклов и оперативной памяти.
Постепенно новые технологии заменяют технологию CGI в разработке web-
приложений. Мир был свидетелем появления следующих технологий:
• ColdFusion. Предоставляет подобные HTML пользовательские теги, кото-
рые можно применять для выполнения ряда операций, главным образом
для запроса базы данных. В свое время ColdFusion была основной техно-
логией программирования web-приложений.
• Серверный JavaScript (SSJS). Является расширением языка JavaScript —
языка сценариев, который все еще широко используется в клиентском
web-программировании. Применяя технологию LiveWire компании
Netscape, SSJS может обращаться к классам Java, размещенным на
сервере.
• РНР. Интересная технология с открытым исходным кодом, которая
развилась за последние годы. Облегчает разработку web-приложений,
предоставляя собственное управление сеансами. Содержит встроенные
функции, такие как пересылка файлов. Число программистов, исполь-
зующих РНР, резко выросло в последние годы.
7зхнология сервлетов
29
• Сервлеты. Технология сервлетов была предложена компанией Sun
Microsystems в 1996 г. Это основная тема книги.
• Серверные страницы Java (JSP). Являются расширением технологии сер-
влетов.
• Активные серверные страницы (ASP). ASP компании Microsoft использу-
ют технологии сценариев, которые работают на платформах Windows.
Предпринимались попытки переноса этой технологии на другие опера-
ционные системы. Windows ASP работают с web-сервером IIS (Internet
Information Server). Эта технология будет заменена активными сервер-
ными страницами .NET.
• Активные серверные страницы .NET (ASP.NET). Эта технология является
частью инициативы .NET компании Microsoft. Интересно то, что плат-
форма .NET использует среду Common Language Runtime (единая систе-
ма выполнения программ), которая похожа на виртуальную машину Java
и предоставляет обширную библиотеку классов, доступную всем язы-
кам .NET и страницам ASP.NET. ASP.NET вводит несколько новых тех-
нологий, включая управление состоянием, которое не зависит от фай-
лов cookie и перезаписи URL.
В прошлом ASP и сервлеты/JSP были основными технологиями, исполь-
зуемыми при разработке web-приложений. С выпуском ASP.NET нетрудно
предсказать, что эта технология станет основным конкурентом сервлетов/
JSP. Каждая из технологий, ASP (и ASP.NET) и сервлеты/JSP, имеет своих
приверженцев, и пока непонятно, какая из них станет победителем. Скорее
всего, ни одна из них не возьмет верх и не монополизирует рынок; вероятно,
в ближайшие годы они будут равноценны.
Сервлеты и JSP имеют следующие преимущества:
• Производительность. Сервлеты превосходят CGI по производительнос-
ти, поскольку отсутствует создание процесса для каждого клиентского
запроса. Закончив обработку запроса, сервлет остается резидентным в
памяти, ожидая другой запрос.
• Переносимость. Аналогично другим технологиям .1ауасервлетные при-
ложения являются переносимыми. Можно перемещать их в другие
операционные системы без особых проблем.
• Быстрый цикл разработки. Будучи технологией Java, сервлеты имеют
доступ к богатой библиотеке Java, которая помогает ускорить процесс
разработки.
• Надежность. Сервлеты управляются виртуальной машиной Java. Поэтому
не приходится беспокоиться об утечке памяти или о сборке мусора, что
помогает писать надежные приложения.
30
Глава 1
• Широкая доступность. Java — широко распространенная технология. Мно-
гочисленные поставщики разрабатывают технологии на основе Java.
Одним из достоинств этого является то, что можно легко найти и купить
компоненты, удовлетворяющие конкретным потребностям, что сохра-
няет драгоценное время разработки.
Архитектура сервлетных приложений
Сервлет является классом Java, который может быть динамически загружен и
выполнен специальным web-сервером. Этот поддерживающий сервлеты web-
сервер называется контейнером сервлетов (servlet container). В начальный пе-
риод развития технологии сервлетов он назывался процессором сервлетов
(servlet engine).
Сервлеты взаимодействуют с клиентами по модели запрос-ответ на базе HTTP.
Поскольку технология сервлетов работает поверх HTTP, контейнер сервлетов
должен поддерживать HTTP как протокол для передачи запросов клиента и
ответов сервера. Однако контейнер сервлетов может поддерживать и другие про-
токолы, такие как HTTPS (HTTP поверх SSL) для защищенных транзакций.
На рис. 1.1 показана архитектура сервлетного приложения.
Запрос HTTP
Браузер
Ответ HTTP
Контейнер
сервлетов
Сервлет
Статическое
содержимое
Рис. 1.1. Архитектура сервлетного приложения
В приложении JSP контейнер сервлетов заменяется контейнером JSP. Оба
контейнера, сервлетов и JSP, часто называют web-контейнером или контейне-
ром сервлетов/JSP, особенно в тех случаях, когда web-приложение состоит из
сервлетов и страниц JSP.
Более подробно о контейнерах сервлетов и JSP
рассказывается в главе 8.
Как показано на рис. 1.1, сервлетное приложение может также включать в
себя статическое содержимое, такое как страницы HTML и файлы изображе-
ний. Не желательно, чтобы статическое содержимое обслуживалось контей-
нером сервлетов, поскольку это содержимое будет быстрее обрабатываться
более подходящим для этого сервером HTTP, например web-сервером Apache
или сервером 1IS компании Microsoft. Поэтому обычная практика состоит в
размещении web-сервера для обработки всех клиентских запросов. Web-cep-
Технология сервлетов
31
вер обслуживает статическое содержимое и передает контейнеру сервлетов все
клиентские запросы сервлетов. На рис. 1.2 представлена более распространенная
архитектура сервлетного приложения.
Архитектура web-приложения на Java, использующая
сервер J2EE, отличается от архитектур, показанных на
рис. 1.1 и 1.2 (см. главу 28).
Рис. 1.2. Архитектура сервлетного приложения, использующего
сервер HTTP
Как работает сервлет
сервлет загружается контейнером сервлетов при первом запросе сервлета. Затем
сервлету передается запрос пользователя, сервлет обрабатывает его и возвращает
ответ контейнеру сервлетов, который в свою очередь посылает ответ пользо-
вателю. После этого сервлет остается в памяти, ожидая другие запросы, — он
не будет выгружаться из памяти, если только контейнер сервлетов не почув-
ствует недостаток в памяти. Однако каждый раз, когда запрашивается сервлет,
контейнер сервлетов сравнивает метку времени загруженного сервлета с файлом
класса сервлета. Если метка времени файла класса оказывается более поздней,
сервлет перезагружается в памяти. Таким образом, не требуется перезапускать
контейнер сервлетов всякий раз при обновлении сервлетов.
Алгоритм работы сервлета внутри контейнера представлен на рис. 1.3.
Контейнер сервлетов Tomcat
Сегодня доступно несколько контейнеров сервлетов. Наиболее популярным и
признаваемым как официальный контейнер сервлетов/JSP является Tomcat.
Разработанный первоначально компанией Sun Microsystems, исходный код
Tomcat был передан Apache Software Foundation в октябре 1999 г. В своем но-
вом доме Tomcat был включен в проект Jakarta. Apache, Sun и другие компании
с помощью добровольцев со всего мира превратили Tomcat в эталонную реа-
лизацию контейнера сервлетов мирового уровня. Через два месяца после
передачи была выпущена версия Tomcat 3.0. Затем было несколько промежу-
точных версий Tomcat 3.x, пока не появилась версия 3.3.
Наследником версии З.Зявляется текущая версия 4.0. Контейнер сервлетов 4.0
(Catalina) основывается на совершенно новой архитектуре. Он был разработан
32
Глава 1
заново с целью достижения максимальной гибкости и производительности.
Версия 4.0 реализует спецификации Servlet 3.2 и JSP 1.2, именно эта версия
используется в данной книге.
Рис. 1.3. Как работает сервлет
Другим популярным контейнером сервлетов является JRun корпорации
Allaire. J Run доступен в трех редакциях: Developer, Professional и Enterprise.
Редакция Developer является бесплатной, но не лицензирована для развер-
тывания. Редакции Professinal и Enterprise предоставляютлицензиюддя раз-
вертывания с отчислением. Можно загрузить JRun по адресу http://
commerce.allaire.com/download.
Tomcat сам по себе является web-сервером. Это означает, что Tomcat можно
использовать для обслуживания HTTP-запросов сервлетов, а также статических
файлов (HTML, файлов изображений и т. д.). На практике, однако, Tomcat обычно
применяется как модуль с другим более мощным web-сервером, таким как web-
сервер Apache или сервер I IS Microsoft, поскольку это ускоряет обработку запро-
сов без сервлетов и без JSP. Только запросы сервлетов или JSP передаются Tomcat.
Чтобы написать сервлет, требуется как минимум версия 1.2 набора разра-
ботчика Java (JDK, Java Development Kit). JDK 1.2 можно загрузить по адресу
http://java.sun.com/j2se. Эталонные реализации сервлетов и JSP не включены
в J2SE, но они имеются в Tomcat. Tomcat написан полностью на Java.
Технология сервлетов
33
Если вы еще не установили и не сконфигурировали Tomcat, то сейчас самое
время сделать это. Необходимую помощь по выполнению этих задач можно
найти в приложении А.
Шесть шагов создания сервлета
После установки и конфигурирования Tomcat следует привести его в рабочее
состояние. По сути, чтобы написать и запустить сервлет, необходимо выпол-
нить следующие шаги:
1. Создать в Tomcat структуру каталогов для приложения.
2. Написать исходный код сервлета. Необходимо импортировать в свой файл
исходного кода пакеты javax.servlet и javax.servlet.http.
3. Откомпилировать исходный код.
4. Создать дескриптор развертывания.
5. Запустить Tomcat.
6. Вызвать сервлет в web-браузере.
Рассмотрим эти действия более подробно.
Шаг 1. Создание структуры каталогов в Tomcat
СПримечаниеМ Каталог, где устанавливается Tomcat, обычно называется
.. %CATALINA_HOME%. В предыдущих версиях Tomcat это был
каталог %ТОМСАТ_НОМЕ%.
При установке Tomcat в домашнем каталоге Tomcat (%CATALINA_HOM Е%)
автоматически создаются несколько подкаталогов. Одним из них является
webapps. В каталоге webapps хранятся web-приложения. Web-приложение пред-
ставляет собой совокупность сервлетов и другого содержимого, установлен-
ных в определенном подмножестве пространства имен URL сервера. Для каж-
дого сервлетного приложения выделяется отдельный каталог. Поэтому при
создании сервлетного приложения необходимо прежде всего создать каталог
приложения. Чтобы создать структуру каталогов для приложения с именем
туАрр, выполните следующие действия:
1. Создайте каталоге именем туАрр в каталоге webapps. Имя каталога важ-
но, так как оно будет присутствовать в URL сервлета.
2. Создайте каталог WEB-INF в туАрр и каталог с именем classes в WEB-
IN Е Структура каталогов показана нарис. 1.4. Каталог classes в WEBINF
34
Глава 1
предназначен для классов Java. Если имеются файлы HTML, поместите
их непосредственно в каталог туАрр. Можно также создать каталог images
в туАрр для всех файлов изображений.
Отметим, что каталоги examples, manager, ROOT, tomcat-doc и webdav пред-
назначены для приложений, которые создаются автоматически при установке
Tomcat.
Рис. 1.4. Структура каталогов приложения Tomcat
' Шаг 2. Написание исходного кода сервлета
На этом шаге готовится исходный код. Вы можете написать исходный код
самостоятельно с помощью текстового редактора или скопировать его с сайта
www.lory-press.ru.
ЕТ^СоветТ! Исходный код для всех примеров, содержащихся в этой
книге, доступен также на web-сайте, посвященном книге.
Посетите www.newriders.com, чтобы загрузить необходимые
файлы.
Код листинга 1.1 содержит простой сервлет с именем TestingServlet. Файл
называется TestingServlet.java. Сервлет посылает браузеру несколько тегов
HTML и некоторый текст.
Л исти н г 1.1 TestingServlet.java
import javax.servlet.*;
import javax.servlet.http.*;
Технология сервлетов
35
import java.io.*;
import java.util.*;
public class TestingServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out. println(’’<HTML>”);
out. println(’’<HEAD>”);
out. println( ’’<TITLE>Servlet Testing</TITLE>’’);
out.println(”</HEAD>”);
out. println(''<BODY>");
out. println(’’Welcome to the Servlet Testing Center’’);
out. println( ’’</BODY>”);
out. println( ’’</HTML>");
Сохраните файл TestingServlet.java в каталоге WEB-INF/classes в myApp.
Размещение исходного кода в этом каталоге сделает его недоступным в web-
браузере. Статичные файлы, такие как файлы HTML и файлы изображений,
должны размещаться в каталоге myApp или во вложенном в него каталоге.
Предупреждение! Размещение файлов исходного кода вне каталога WEB-
1 INF сделает их видимыми в браузере.
Шаг 3. Компиляция исходного кода
Чтобы скомпилировать исходный код сервлета, необходимо включить путь
доступа к файлу servlet.jar в переменную окружения CLASSPATH. Файл
servlet.jar расположен в подкаталоге common\lib\ в %CATALINA_HOME%.
Например, если Tomcat установлен на диске С:\ в Windows и каталог уста-
новки назван tomcat, перейдите в каталог, где находится TestingServlet.java, и
введите следующую команду:
javac classpath C:\tomcat\common\lib\servlet.jar TestingServlet.java
Чтобы избавиться от необходимости вводить путь доступа к классу каждый
раз при компиляции исходного кода, можно добавить полный путь файла
servlet.jar в переменную окружения CLASSPATH. Если установить Tomcat на
диске С:\ и назвать каталог установки tomcat, то нужно будет добавить
36
Глава 1
C:\tomcat\common\lib\servlet.jar в переменную окружения CLASSPATH. После
этого можно будет компилировать исходный коде помощью простой команды:
javac TestingServlet.java
Если вы забыли, как редактировать переменную окружения
CLASSPATH, обратитесь к приложению А.
Шаг 4. Создание дескриптора развертывания
Дескриптор развертывания является необязательным компонентом серв-
летного приложения. Дескриптор записывается в форме документа XML с
именем web.xml и должен находиться в каталоге WEB-INFсервлетного прило-
жения. Дескриптор развертывания содержит конфигурационные настройки,
специфические для приложения (см. главу 16).
Чтобы создать дескриптор развертывания, необходимо создать файл web.xml
и поместить его в каталог WEB-INF каталога туАрр.
В нашем примере приложения файл web.xml должен иметь следующее
содержимое:
<?xml version="1.О" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN’’
’’http: //j ava. sun. com/dtd/web-app_2_3. dtd ”>
<web-app>
<servlet>
<servlet-name>Testing</servlet-name>
<servlet-class>TestingServlet</servlet-class>
</servlet>
</web-app>
Файл web.xml имеет один элемент — web-app. Вы должны описать все свои
сервлеты внутри <web-app>. Для каждого сервлета имеется элемент <servlet> и
требуются элементы <servlet-name> и <servlet-class>. <servlet-name> является
именем сервлета, под которым он известен Tomcat. <servlet-class> — компили-
рованный файл сервлета без расширения .class.
Приложение может содержать более одного сервлета. Для каждого сервлета
в файле web.xml требуется элемент <servlet>. В следующем примере показано,
как будет выглядеть web.xml, если добавить еще один сервлет с именем Login:
<?xml version=”1.0” encoding="IS0-8859-1’’?>
Технология сервлетов
37
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Testing</servlet-name>
<servlet-class>TestingServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>LoginServlet</servlet-class>
</servlet>
</web-app>
Шаг 5. Запуск Tomcat
Если Tomcat еще не работает, необходимо его запустить (см. приложение А).
Шаг 6. Вызов сервлета из web-браузера
Теперь можно вызвать сервлет из web-браузера. По умолчанию Tomcat выпол-
няется на порту 8080 в виртуальном каталоге туАрр в подкаталоге servlet. Мы со-
здали сервлет с именем Testing. U RL этого сервлета имеет следующий формат:
http://domain-name/virtual-directory/servlet/servlet-name
К любому статическому файлу можно обратиться с помощью U RL:
http://domain-name/virtual-directory/staticFile.html
Например, к файлу Logo.gif в каталоге туАрр/images/ можно обратиться с
помощью URL:
http://domain-name/virtual-diredtory/images/Logo.gif
Если web-браузер выполняется на том же компьютере, что и Tomcat, то можно
заменить часть domain-name на «localhost». В этом случае U RL сервлета будет:
http://localhost:8080/myApp/servlet/Testing
В дескрипторе развертывания (см. шаг 4) устанавливается соответствие между
файлом класса сервлета, называемым TestingServlet, и именем «Testing». Бла-
годаря этому можно вызвать сервлет, указав его файл класса (TestingServlet) или
его имя (Testing). При отсутствии дескриптора развертывания для вызова сер-
влета необходимо указать его имя класса, т. е. TestingServlet. Это означает, что
если дескриптор развертывания не написан, то для вызова сервлета придется
использовать следующий URL:
38
Глава 1
http://localhost:8080/myApp/servlet/TestingServlet
После ввода URL в поле web-браузера Address или Location появится строка
«Welcome to the Servlet Testing Center», как показано на рис. 1.5.
Welcome to the Servlet Testing Center
Рис. 1.5. Сервлет Testing
Вот и все. Первый сервлет написан и протестирован.
Чтобы не вводить номер порта всякий раз, можно изменить используемый
Tomcat по умолчанию порт 8080 на порт 80 — это порт по умолчанию web-сер-
вера. (Изменение номера порта описывается в приложении А.) В этой книге
применяется порт 8080.
С Примечание^ встретите в книге код различных сервлетов. Для запуска
любого из них необходимо выполнить описанные шесть
шагов. Во избежание повторения эти шаги не
рассматриваются для каждого сервлета, представленного в
книге. О них не стоит беспокоиться при использовании таких
инструментов разработки Java, как Borland JBuilder и IBM
VisualAge, поскольку выполнением этих шагов занимается
программа разработки (RAD).
Заключение
В этой главе представлена общая картина того, как создается сервлетное при-
ложение. В частности, были описаны преимущества сервлетов, исследована
архитектура сервлетного приложения и показано, как сервлет работает внутри
контейнера сервлетов. Также было показано, как конфигурировать Tomcat и
как создавать свои собственные сервлеты. В следующей главе рассматривается
спецификация Java Servlet из интерфейса прикладного программирования
(API) версии 2.3.
Как устроены
сервлеты
'от, кто хочет стать хорошим специалистом, должен изучить все особен-
ности спецификации Java Servlet интерфейса прикладного программирования
(API). В этой книге используется самая последняя версия спецификации сер-
влетов API — версия 2.3. Для программистов сервлетов доступны два пакета:
javax.servlet и javax.servlet.http. Первый из них содержит базовые классы и ин-
терфейсы, которые можно использовать для написания сервлетов с нуля.Вто-
рой пакет, javax.servlet.http, предлагает более развитые классы и интерфейсы,
которые расширяют классы и интерфейсы первого пакета. Удобнее програм-
мировать с помощью второго пакета.
При изучении чего-либо следует начинать с основ. Например, понимание
интерфейса javax.servlet.Servlet является очень важным, потому что он инкап-
сулирует методы жизненного цикла сервлета и является интерфейсом, кото-
рый должны реализовывать все сервлеты. Необходимо также знать контекст
сервлета, который представляет окружение сервлета, и конфигурацию сервле-
та. В связи с важностью этих вопросов вам следует познакомиться с членами
пакета javax.servlet. Будет также показано, что обычно существует несколько
способов выполнения одного и того же.
После нескольких примеров будет рассмотрен класс GenericServlet, член
пакета javax.servlet, действующий как оболочка для интерфейса
javax.servlet.Servlet. Применение этого класса упрощает код, так как требуется
предоставить реализации только для используемых методов.
40
Глава 2
Для выполнения каждого примера нужно скомпилировать исходный код и ско-
пировать получаемый файл класса в каталог classes каталога WEB-1NF приложе-
ния. В главе 1 описаны шесть шагов, которые необходимы для запуска сервлета.
Пакет javax.servlet
Пакет javax.servlet содержит семь интерфейсов, три класса и два исключения.
Рассмотрим их основные функции и приведем примеры использования.
Для знакомства со всеми членами этого пакета обратитесь к приложению
В, где представлена полная справочная информация.
Пакет содержит семь интерфейсов:
• Request Dispatcher
• Servlet
• ServletConfig
• ServletContext
• Servlet Request
• ServletResponse
• SingleThreadModel
Три класса:
• GenericServlet
• ServletlnputStream
• ServletOutputStream
И классы исключений:
• ServletException
• UnavailableException
Объектная модель пакета javax.servlet показана на рис. 2.1.
Жизненный цикл сервлета
Интерфейс Servlet в пакете javax.servlet является основой программирования
сервлетов. Servlet — центральная абстракция технологии сервлетов Java. Каж-
дый создаваемый сервлет должен явно или неявно реализовать интерфейс
javax.servlet.Servlet. Жизненный цикл сервлета определяется тремя его метода-
ми: init, service и destroy.
Как устроены сервлеты
41
Рис. 2.1. Объектная модель пакета javax.servlet
Метод init()
Метод init вызывается контейнером сервлетов после создания экземпляра
класса сервлета. Контейнер сервлетов вызывает этот метод только один раз для
указания сервлету, что сервлет включается в работу. Метод init должен быть ус-
пешно выполнен, прежде чем сервлет сможет получить какие-либо запросы.
Можно переопределить этот метод для записи кода инициализации, кото-
рый необходимо выполнить только один раз, такой как загрузка драйвера базы
данных, инициализация значений и т.д. В других случаях этот метод обычно
оставляют пустым.
Метод имеет следующую сигнатуру:
public void init(ServletConfig config) throws ServletException
Метод init является важным также потому, что контейнер сервлетов передает
объект ServletConfig, который содержит конфигурационные значения, объяв-
ленные в файле web.xml для этого приложения (см. ниже).
Этот метод может порождать исключение ServletException. Контейнер
сервлетов не может запустить сервлет в работу, если метод init порождает
исключение ServletException или метод не возвращает управление в тече-
ние периода времени, определенного web-сервером.
ГПримечаниеМ ServerException является самым важным исключением при
1 1 программировании сервлетов. Многие методы в пакетах
javax.servlet и javax.servlet.http порождают это исключение
при возникновении в сервлете проблем.
42
Глава 2
Метод service()
Метод service вызывается контейнером сервлетов после метода init, чтобы
позволить сервлету ответить на запрос.
Сервлеты обычно выполняются внутри многопоточных контейнеров сер-
влетов, которые могут обрабатывать множество запросов одновременно.
Поэтому необходимо помнить о синхронизации доступа к любым общим ре-
сурсам, таким как файлы, сетевые соединения и переменные класса и экзем-
пляра сервлета. Например, если открыть файл и выполнить запись в этот файл
в сервлете, то нужно учитывать, что другой поток того же сервлета может открыть
тот же файл (см. ниже).
Метод имеет следующую сигнатуру:
public void service(ServletRequest request, ServletResponse response)
throws ServletException, java.io.lOException
Контейнер сервлетов передает объекты Servlet Request и ServletResponse.
Объект ServletRequest содержит запрос клиента, а объект ServletResponse
содержит ответ сервлета. Эти два объекта позволяют писать код, который
определяет, как сервлет обслуживает клиентский запрос.
Метод service генерирует исключение ServletException, если возникает
исключение, которое влияет на нормальную работу сервлета. Метод service
может также порождать исключение java.io.IOException, если во время выпол-
нения метода возникает исключительная ситуация при вводе или выводе.
Метод service предназначен для того, чтобы можно было написать код,
который заставляет сервлет функционировать требуемым образом.
Метод destroyO
Контейнер сервлетов вызывает метод destroy перед удалением экземпляра
сервлета из службы. Это обычно происходит, когда контейнер сервлетов вык-
лючается или контейнеру сервлетов требуется дополнительная свободная память.
Этот метод вызывается только после того, как все потоки выполнения в
методе service сервлета заканчиваются, или после того как завершится период
ожидания. После вызова метода destroy контейнер сервлетов не будет снова
вызывать метод service этого сервлета.
Метод destroy дает сервлету возможность очистить все использовавшиеся
ресурсы (например, память, дескрипторы файлов, потоки выполнения), и га-
рантирует, что любое устойчивое состояние синхронизировано с текущим со-
стоянием сервлета в памяти.
Сигнатура этого метода:
public void destroyO
Как устроены сервлеты
43
Демонстрация жизненного цикла сервлета
Листинг 2.1 содержит код сервлета с именем PrimitiveServlet. Это простой
сервлет, демонстрирующий жизненный цикл сервлета. Класс PrimitiveServlet
реализуетз^ах.зеМе^БеМе! (как должны делать все сервлеты) и предоставляет
реализации для пяти методов сервлета. Каждый раз, когда вызывается метод
init, service или destroy, сервлет выводит имя метода на консоль.
Л исти н г 2.1 PrimitiveServlet.java
import javax.servlet.*;
import java.io.lOException;
public class PrimitiveServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
System, out. println("init");
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
System.out.println("service");
}
public void destroyO {
System.out.println("destroy");
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
}_______________________________________________________________________
После компиляции исходного кода в каталоге myApp\WEB-INF\classes до-
бавьте сервлет в файл web.xml, назвав его Primitive, как показано в листинге 2.2.
Л истинг 2.2 Файл web.xml для PrimitiveServlet
<?xml version="1.О" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
44
Глава 2
Л исти нг 2.2 Продолжение
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Primitive</servlet-name>
<servlet-class>PrimitiveServlet</servlet-class>
</servlet>
</web-app>
Теперь можно вызвать этот сервлет в браузере, введя URL:
http://localhost:8080/myApp/servlet/Primitive
При первом вызове сервлета на консоль выводятся следующие две строки:
init
service
Это говорит, что был вызван метод init, а затем метод service. Однако при
последующих запросах вызывается только метод service. Сервлет выводит на
консоль строку:
service
Это доказывает, что метод init вызывается только один раз.
Что делают в листинге 2.1 getServletlnfo и getServletConfig? Ничего. В классе
PrimitiveServlet они присутствуют л ишь с целью удовлетворения спецификации,
согласно которой класс должен предоставить реализации для всех методов в
интерфейсе, который он реализует.
В методе getServletlnfo можно вернуть любую строку, например название
компании, имя автора или другую необходимую информацию. Кто-то из про-
граммистов может расширить ваш класс сервлета, и он захочет узнать, какую
полезную информацию предоставляет создатель сервлета.
Метод getServletConfig является более важным. Посмотрим, как его можно
использовать.
Получение данных конфигурации
Спецификация сервлета позволяет конфигурировать приложение. Подробная
информация по этому вопросу представлена в главе 16. Вэтой главе приводит-
Как устроены сервлеты
45
ся пример, демонстрирующий, как можно извлечь данные конфигурации из
файла web.xml приложения.
Для каждого сервлета, зарегистрированного в файле web.xml, можно опре-
делить множество пар имя_параметра/начальное_значение, которые могут
быть извлечены из сервлета. Следующий файл web.xml содержит сервлет с име-
нем ConfigDemo, класс которого называется ConfigDemoServlet.class. Сервлет
имеет две пары имя/значение. Первый параметр называется adminEmail, и его
значение равно admin@brainysoftware.com. Второй параметр называется
adminContactNumber, и значение этого параметра равно 04298371237.
<?xml version=’’1.0’’ encoding=’’IS0-8859-1,'?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN”
’’ http: //j ava. sun. xom/j 2ee/dtds/web-app_2.2. dtd ’’>
<web-app>
<servlet>
<servlet-name>ConfigDemo</servlet-name>
<servlet-class>ConfigDemoServlet</servlet-class>
<init-param>
<param-name>adminEmail</param-name>
<param-value>admin@brainysoftware.com</param-value>
</init-param>
<init-param>
<param-name>adminContactNumber</param-name>
<param-value>04298371237</param-value>
</init-param>
</servlet>
</web-app>
Зачем нужны начальные параметры? По практическим соображениям. Если
информация жестко закодирована в коде сервлета, то придется перекомпили-
ровать сервлет в случае изменения данных. Файл web.xml является обычным
текстом. Его содержи мое легко редактируется с помощью текстового редактора.
Код, который извлекает имя и начальное значение параметра, приведен в
листинге 2.3.
Для извлечения начальных параметров необходимо, чтобы объект
ServletConfig был передан сервлету контейнером сервлетов. После получения
объекта ServletConfig можно использовать его методы getlnitParameterNames и
getlnitParameter. Метод getlnitParameterNames не имеет аргументов и возвра-
щает Enumeration, содержащее все имена параметров в объекте ServletConfig.
Метод getlnitParameter получает String, содержащую имя параметра, и возвращает
String, содержащую значение параметра.
46
Глава 2
Поскольку контейнер сервлетов передает объект ServletConfig методу init,
то проще всего написать код в методе init. Код листинга 2.3 просматривает в
цикле объект Enumeration, вызывая параметры, которые возвращаются мето-
дом getInitParameterNames. Для каждого параметра выводятся имя параметра
и значение. Значение параметра извлекается с помощью методаgetlnitParameter.
Листинг 2.3 Извлечение начальных параметров
import javax.servlet.*;
import java.util.Enumeration;
import java.io.lOException;
public class ConfigDemoServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
Enumeration parameters = config.getlnitParameterNamesO;
while (parameters.hasMoreElementsO) {
String parameter = (String) parameters.nextElementO;
System.out.println("Parameter name : " + parameter);
System.out.println("Parameter value : " +
config.getlnitParameter(parameter));
}
}
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
}_______________________________________________________________________
Код выводит на консоль следующее:
Parameter name: adminContactNumber
Parameter value: 04298371237
Как устроены сервлеты
47
Parameter name: adminEmail
Parameter value: admin@brainysoftware.com
Сохранение объекта ServletConfig
Код листинга 2.3 показывает, как можно использовать объект ServletConfig в
методе init. Однако иногда требуется большая гибкость. Например, при обслу-
живании пользователя необходимо получить доступ к объекту ServletConfig из
метода service. В этом случае нужно сохранить объект ServletConfig в перемен-
ной уровня класса. Это несложная задача. Необходимо создать переменную
объекта ServletConfig и присвоить ей объект ServletConfig, возвращаемый кон-
тейнером сервлетов в методе init.
Листинг 2.4 представляет код, который сохраняет объект ServletConfig для
последующего использования. Прежде всего необходима переменная для
объекта ServletConfig.
ServletConfig ServletConfig;
В методе init записываем код:
ServletConfig = config;
Теперь переменная ServletConfig ссылается на объект ServletConfig, возвра-
щаемый контейнером сервлетов. Метод getServletConfig предназначен именно
для возврата объекта ServletConfig.
public ServletConfig getServletConfigO {
return ServletConfig;
>
При расширении класса ReserveConfigServlet все равно можно будет извле-
кать объект ServletConfig путем вызова метод getServletConfig.
Листинг 2.4 Сохранение объекта ServletConfig
import javax.servlet.*;
import java.io.lOException;
public class ReserveConfigServlet implements Servlet {
ServletConfig ServletConfig;
public void init(ServletConfig config) throws ServletException {
ServletConfig = config;
}
48
Глава 2
Л исти нг 2.4 Продолжение
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return ServletConfig;
i?
}
}
Контекст сервлетов
В программировании контекст сервлетов — это среда, где выполняется сервлет.
Контейнер сервлетов создает объект ServletContext, который можно исполь-
зовать для доступа к информации о среде выполнения сервлета.
Сервлет может также связать атрибут объекта с контекстом по имени. Л юбой
объект, связанный с контекстом, доступен любому другому сервлету, являюще-
муся частью того же web-приложения.
Как получить объект ServletContext? Косвенно, с помощью объекта
ServletConfig, передаваемого контейнером сервлетов в метод init сервлета.
Интерфейс ServletConfig имеет метод с именем getServletContext, который
возвращает объект ServletContext. Затем можно использовать различные
методы интерфейса ServletContext для получения необходимой информа-
ции. Это методы:
• getMajorVersion. Возвращает целое число, представляющее старшую вер-
сию API сервлетов, которую поддерживает контейнер сервлетов. Если
контейнер сервлетов поддерживает API сервлетов версии 2.3, то этот
метод вернет 2.
• getMinorVersion. Возвращает целое число, представляющее младшую вер-
сию API сервлетов, которую поддерживает контейнер сервлетов. Для API
сервлетов версии 2.3 этот метод вернет 3.
• getAttributeNames. Возвращает перечисление строк, которые содержат
имена атрибутов, хранящихся в настоящее время в ServletContext.
Как устроены сервлеты
49
• get Attribute. Получает String, содержащую имя атрибута, и возвращает
объект, связанный с этим именем.
• setAttribute. Сохраняет объект в ServletContext и связывает объект с
заданным именем. Если имя уже существует в ServletContext, старый свя-
занный объект будет заменен объектом, переданным в этот метод.
• removeAttribute. Удаляет из ServletContext объект, связанный с именем. Этот
метод получает один аргумент: имя атрибута, который будет удален.
Код листинга 2.5 показывает сервлет с именем Context DemoServlet, кото-
рый извлекает некоторую информацию из контекста сервлета, включая имена
атрибутов и значения, младшую и старшую версии контейнера сервлетов и
информацию о сервере.
Листинг 2.5 Извлечение информации о контексте сервлета
import javax.servlet.*;
import java.util.Enumeration;
import java.io.IOException;
public class ContextDemoServlet implements Servlet {
ServletConfig ServletConfig;
public void init(ServletConfig config) throws ServletException {
ServletConfig = config;
}
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
ServletContext ServletContext = ServletConfig.getServletContextO;
Enumeration attributes = ServletContext.getAttributeNamesO;
while (attributes.hasMoreElementsO) {
String attribute = (String) attributes.nextElementO;
System.out.println("Attribute name : " + attribute);
System.out.println("Attribute value : " +
ServletContext.getAtt ribute(attribute));
}
System.out.println("Major version : ” +
ServletContext.getMajorVersion());
50
Глава 2
Л исти нг 2.5 Продолжение
System.out.println("Minor version : ” +
servletContext.getMinorVersion());
System.out.println(”Server info : ” +
servletContext.getServerlnfo());
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
}
Этот код выдает следующие результаты. Они могут отличаться на разных
компьютерах в зависимости от используемой версии Tomcat, операционной
системы и т. д.
Attribute name : javax.servlet.context.tempdirAttribute value :
..\work\localhost\myApp
Attribute name : org.apache.catalina.resources
Attribute value : org.apache.naming.resources.ProxyDirContext@24e2e3
Attribute name : org.apache.catalina.WELCOME_FILES
Attribute value : [Ljava.lang.String;@2bb7e0
Attribute name : org.apache.catalina.jsp.classpath
Attribute value : C:\tomcat4\webapps\myApp\WEB-INF\classes;
. Major version : 2Minor version : 3
Server info : Apache Tomcat/4.0-b5
Совместное использование информации
сервлетами
В некоторых приложениях нужно, чтобы информация определенного типа,
например строка соединения с базой данных или счетчик страниц, была
доступна всем сервлетам. Это делается с помощью атрибутов объекта
ServletContext.
Следующий пример использует два сервлета: AttributeSetterServlet и
DisplayAttributesServlet. Сервлет AttributeSetterServlet (см. листинг 2.6) связы-
Как устроены сервлеты
51
вает имя password с объектом String, содержащим слово «dingdong». Чтобы сде-
лать это, сервлет получает объект ServletContext из объекта ServletConfig, пере-
данного контейнером сервлетов в метод init. Затем сервлет использует метод
setAttribute для связывания «password» с «dingdong».
Л исти нг 2.6 AttributeSetterServlet
import javax.servlet.*;
import java.io.lOException;
public class AttributeSetterServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
// связываем объект, который будет доступен другим сервлетам
ServletContext ServletContext = config.getServletContext();
ServletContext.setAttribute("password", "dingdong");
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
}
public void destroyO {
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
}
Код листинга 2.7 является сервлетом, который извлекает все пары имя/зна-
чение атрибутов в объекте ServletContext. Метод init этого сервлета сохраняет
объект ServletConfig в ServletConfig. Метод service использует метод
getServletContext интерфейса ServletConfig для получения объекта ServletContext.
После этого можно использовать метод getAttributeNames объекта ServletContext
для получения Enumeration со всеми именами атрибутов и для выполнения
цикла с целью получения значений всех атрибутов, которые выводятся на кон-
соль вместе с именами атрибутов.
Л исти н г 2.7 DisplayAttributesServlet
import javax.servlet.*;
import java.io.lOException;
52
Глава 2
Листинг 2.7 Продолжение
import java.util.Enumeration;
public class ResponseDemoServlet implements Servlet {
ServletConfig ServletConfig;
public void init(ServletConfig config) throws ServletException {
ServletConfig = config;
}
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
ServletContext ServletContext = ServletConfig.getServletContext();
Enumeration attributes = ServletContext.getAttributeNamesO;
while (attributes.hasMoreElementsO) {
String attribute = (String) attributes. nextElementO;
System.out.println(’’Attribute name : ” + attribute);
System.out.println("Attribute value : ” +
ServletContext.getAtt ribute(att ribute));
}
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
}
Enumeration attributes = ServletContext.getAttributeNamesO;
while (attributes.hasMoreElementsO) {
String attribute = (String) attributes.nextElementO;
System.out.println("Attribute name : ’’ + attribute);
System.out.println("Attribute value : ’’ +
ServletContext.getAtt ribute(attribute));
}
Чтобы посмотреть, как работают сервлеты, прежде всего необходимо выз-
вать сервлет AttributeSetterServlet и задать атрибут «password». Затем вызывается
DisplayAttributesServlet для получения Enumeration имен всех атрибутов и
вывода значений.
Как устроены сервлеты
53
Результат представлен ниже:
Attribute name : javax.servlet.context.tempdir
Attribute value :
C:\123data\JavaProjects\JavaWebBook\work\localhost_8080
Attribute name : password
Attribute value : dingdong
Attribute name : sun.servlet.workdir
Attribute value :
C:\123data\JavaProjects\JavaWebBook\work\localhost_8080
Запросы и ответы
Запросы и ответы составляют суть web-приложения. Всервлетном приложении
пользователь с помощью web-браузера посылает запрос контейнеру сервлетов, а
контейнер сервлетов передает запрос сервлету.
В сервлетной парадигме запрос пользователя представляется объектом
Servlet Request, пересылаемым контейнером сервлетов в качестве первого ар-
гумента метода service. Вторым аргументом метода service является объект
ServletResponse, который представляет ответ пользователю.
Интерфейс ServletRequest
Интерфейс ServletRequest определяет объект, используемый для инкап-
суляции информации о запросе пользователя, включая пары имя/значение
параметров, атрибуты и входной поток.
Интерфейс ServletRequest предоставляет важные методы, которые обес-
печивают доступ к информации о пользователе. Например, метод
getParameterNames возвращает Enumeration, содержащее имена параметров
текущего запроса. Для получения значения каждого параметра служит метод
getParameter интерфейса ServletRequest.
Методы get Remote Address и getRemoteHost можно применять для извлечения
идентификационных данных компьютера пользователя. Первый метод воз-
вращает строку, представляющую IP-адрес компьютера, который используется
клиентом. Второй метод возвращает строку, содержащую квалифицированное
имя хоста компьютера.
Листинги 2.8 и 2.9 демонстрируют объект ServletRequest в действии. Пример
состоит из сервлета с именем Request DemoServlet и формы НТМ L, содержащейся
в файлет4ех.Ьйп1, который необходимо поместить в каталог приложения, т. е. в
myApp.
54
Глава 2
Листинг 2.8 index.html
<HTML>
<HEAD>
<TITLE>Sending a request</TITLE>
</HEAD>
<BODY>
<FORM ACTION=servlet/RequestDemoServlet METHOD="POST">
<BRXBR>
Author: <INPUT TYPE=’’TEXT" NAME="Author’’>
<INPUT TYPE=’’SUBMIT" NAME="Submit">
<INPUT TYPE=’’RESET" VALUE="Reset’’>
</F0RM>
</B0DY>
</HTML>
Листинг 2.9 RequestDemoServlet
import javax.servlet.*;
import java.util.Enumeration;
import java.io.lOException;
public class RequestDemoServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
}
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
System.out.println("Server Port: ’’ + request.getServerPortO);
System.out.println("Server Name: " + request. getServerNameO);
System.out.println("Protocol: ’’ + request.getProtocolO);
System.out.println(”Character Encoding: " +
request.getCharacterEncoding());
System.out.println("Content Type: ” + request.getContentTypeO);
System.out.println(’’Content Length: ’’ + request.getContentLengthO);
System.out.println("Remote Address: ’’ + request.getRemoteAddrO);
System.out.println(’’Remote Host: ’’ + request.getRemoteHostO);
System.out.println("Scheme: " + request.getSchemeO);
Как устроены сервлеты
55
Enumeration parameters = request.getParameterNamesO;
while (parameters.hasMoreElementsO) {
String parameterName = (String) parameters.nextElementO;
System.out.println("Parameter Name: ” + parameterName);
System.out.println(’’Parameter Value: ” +
request.getParameter(parameterName));
}
Enumeration attributes = request.getAttributeNamesO;
while (attributes.hasMoreElementsO) {
String attribute = (String) attributes.nextElementO;
System.out.println(”Attribute name: " + attribute);
System.out.println("Attribute value: " +
request.getAttribute(attribute));
}
}
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
Чтобы выполнить пример, запросите сначала файл index.html, используя
следующий URL:
http://localhost:8080/myApp/index.html
На рис. 2.2 показан файл index.html, в котором «haywood» введено в каче-
стве значения author.
После отправки формы на экране должен появиться список имен атрибу-
тов и значений.
Интерфейс Servlet Response
Интерфейс ServletResponse представляет ответ пользователю. Наиболее важ-
ным методом этого интерфейса является getWriter. Он позволяет получить
объект java.io. Print Writer, который можно применять для записи тегов HTML
и другого текста для пользователя.
Листинги 2.10 и 2.11 представляют файл HTMLc именем index2.html и серв-
лет, метод service которого переопределяется кодом, который выводит некоторые
56
Глава 2
теги HTML пользователю. Этот сервлет является модификацией примера в
листингах 2.8 и 2.9, извлекающего различную информацию о пользователе.
Вместо вывода информации на консоль, метод sefvice посылает ее назад пользо-
вателю.
Рис. 2.2. Файл index.html
Отметим, что код в листинге 2.10 аналогичен коду листинга 2.8 за исключе-
нием того, что в листинге 2.10 значением атрибута ACTION формы является
servlet/Response DemoServlet.
Листинг 2.10 index2.html
<HTML>
<HEAD>
<TITLE>Sending a request</TITLE>
</HEAD>
<BODV>
<FORM ACTION=servlet/ResponseDemoServlet METHOD="POST">
<BR><BR>
Author: dNPUT TYPE="TEXT" NAME="Author">
<INPUT TYPE="SUBMIT" NAME="Submit">
<INPUT TYPE="RESET" VALUE="Reset">
</FORM>
</BODY>
</HTML>
Как устроены сервлеты
57
Л исти н г 2.11 ResponseDemoServlet
import javax.servlet.*;
import java.io.Printwriter;
import java.io.IOException;
import java.util.Enumeration;
public class ResponseDemoServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
}
public void destroyO {
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out.println(”<HTML>") ;
out. println( ”<HEAD>’’);
out. println( ’’<Т1Т1Е>’’);
out. println( ’’ServletResponse”);
out. println(”</TITLE>”);
out.println(”</HEAD>");
out. println("<BODY>”);
out.println(”<B>Demonstrating the ServletResponse object</B>”);
out. println("<BR>’’);
out.println(”<BR>Server Port: ” + request.getServerPortO);
out.println(”<BR>Server Name: ” + request.getServerNameO);
out.println("<BR>Protocol: ” + request.getProtocolO);
out.println(’’<BR>Character Encoding: ’’ +
request.getCharacterEncoding());
out.println(’’<BR>Content Type: ’’ + request. getContentTypeO);
out.println(”<BR>Content Length: " + request.getContentLengthO);
out.println(’’<BR>Remote Address: " + request.getRemoteAddrO);
out.println(”<BR>Remote Host: ” + request.getRemoteHostO);
out.println(”<BR>Scheme: ” + request.getScheme());
Enumeration parameters = request.getParameterNamesO;
while (parameters.hasMoreElementsO) {
String parameterName = (String) parameters.nextElementO;
out.println(”<br>Parameter Name: ” + parameterName);
out.println(’’<br>Parameter Value: ” +
request. getParameter( parameterName));
}
Enumeration attributes = request.getAttributeNamesO;
while (attributes.hasMoreElementsO) {
String attribute = (String) attributes.nextElementO;
58
Гпава 2
Листинг 2.11 Продолжение
out.println("<BR>Attribute name: " + attribute);
out.println("<BR>Attribute value: " +
request.getAtt ribute(att ribute));
}
out.println("</BODY>");
out.println("</HTML>");
)
public String getServletlnfoO {
return null;
}
public ServletConfig getServletConfigO {
return null;
}
>
Чтобы выполнить пример, запросите сначала файл index2.html, используя
следующий URL:
httр://localhost:8080/myApp/index2. html
На рис. 2.3 показан файл index2.html, где «haywood» введено в качестве зна-
чения author.
После отправки формы вызывается Response DemoServlet, и браузер должен
вывести сведения, показанные на рис. 2.4.
Рис. 2.3. Файл index2.html
59
Как устроены сервлеты
--* — - - - -
Server Port 8080
Server Name: locabort
Protocol ИТГР/1 1
Character Eaco^nf nuD
CoattntType: apphcabon/h-w«w*form-wieacoded
Coateat Length: 28
Remote Advert: 127.0 0.1
Remote Host bdbul
Scheme: http
Parameter Name. Author
Parameter Value haywood
Parameter Name: Submt
Parameter Value Submit
Рис. 2.4. Использование объекта ServletResponse
Класс оболочки GenericServlet
Итак, нами созданы классы сервлетов, которые реализуют интерфейс
javax.servlet.Servlet. Все работает отлично, но имеются два недостатка:
1. Необходимо предоставить реализации всех пяти методов интерфейса
Servlet, хотя чаще всего требуется тол ько один. Эго чрезмерно усложняет код.
2. Объект ServletConfig передается в метод init. Необходимо сохранять этот
объект, чтобы использовать его в других методах. Это нетрудно, но при-
ходится производить дополнительную работу.
Пакет javax.servlet предоставляет класс-оболочку с именем GenericServlet,
который реализует два важных интерфейса из пакета javax.servlet: Servlet и
ServletConfig, а также интерфейс java.io.Serializable. Класс GenericServlet пред-
лагает реализации всех методов, большинство из которых являются пустыми.
Можно расширять GenericServlet и переопределять только те методы, которые
необходимы. Очевидно, это более приемлемое решение.
Код листинга 2.12 является сервлетом с именем SimpleServlet, который рас-
ширяет GenericServlet. Код предоставляет реализацию метода service, посыла-
ющую некоторые выходные данные браузеру. Так как метод service является
единственным нужным в данной ситуации методом, то только этот методдол-
жен появиться в классе. По сравнению со всеми классами сервлетов, которые
реализуют интерфейс javax.servlet.Servlet непосредственно, SimpleServlet выг-
лядит значительно проще и понятнее.
60
Глава 2
Л и ст и н г 2.12 Расширение GenericServlet
import javax.servlet.*;
import java.io.lOException;
import java.io.Printwriter;
public class SimpleServlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out. println( ’’<HTML>’');
out. println( "<HEAD>");
out. println("<TITLE>");
out. println("Extending GenericServlet.");
out.println("</TITLE>");
out.println("</HEAD>");
out.println("<B0DY>");
out.println("Extending GenericServlet makes your code simpler.");
out.println("</BODY>");
out.printin("</HTML>");
Выходные данные сервлета SimpleServlet показаны на рис. 2.5.
Рис. 2.5. Расширение GenericServlet
Как устроены сервлеты
61
Создание сервлетов, поддерживающих
потоки выполнения
Контейнер сервлетов способен поддерживать несколько запросов к одному и
тому же сервлету, создавая различные потоки выполнения для обслуживания
каждого запроса. Во многих случаях каждый поток выполнения имеет дело со
своими собственными объектами ServletRequest и ServletResponse, которые
изолированы от других потоков выполнения. Проблемы начинают возникать,
однако, когда сервлету необходимо обращаться к внешнему ресурсу. Чтобы
понять проблему, которую создают многопоточные сервлеты, рассмотрим при-
мер с «играющей собакой».
Пусть наш сервлет, обращающийся к внешнему ресурсу, — это собака, ко-
торая перетаскивает теннисные мячи из одной корзины в другую. Каждая
корзина может вмещать десять мячей.
Корзины и мячи являются внешним ресурсом для собаки. Для игры собаке
нужны две корзины и десять мячей. В начале эти десять мячей находятся в
первой корзине. Собака перетаскивает все мячи из первой корзины во вторую,
по одному мячу за раз. Собака разумна и может сосчитать до десяти. Поэтому
она знает, когда закончить.
В качестве второго потока выполнения того же сервлета рассмотрим вто-
рую собаку, которая играет в ту же самую игру. Так как для обеих собак есть
только две корзины и десять мячей, то собаки совместно используют один и
тот же «внешний ресурс». Игра происходит следующим образом:
1. Первая собака начинает первой (сервлет получает вызов от пользователя).
2. После того как первая собака перетащит три мяча, начнет играть вторая
собака (сервлет вызывается вторым пользователем). Что произойдет?
Две собаки, совместно использующие одни и те же мячи, показаны на рис. 2.6.
Рис. 2.6. Понятие многопоточного кода
62
Глава 2
Первая и вторая собаки не найдут достаточного количества мячей, чтобы
закончить игру, и окажутся в тупике.
Однако если каким-то образом поставить вторую собаку в очередь, чтобы она
подождала, пока не закончит первая собака, то обе собаки будут удовлетворены.
Именно это происходит, когда двум потокам выполнения одного сервлета
необходим доступ к внешнему ресурсу, скажем, им нужно открыть файл и за-
писать в него данные.' Рассмотрим пример, отражающий реальную ситуацию.
Код листинга 2.13 представляет сервлет счетчика страниц. Он делает очень
простую вещь — переопределяет метод service, чтобы:
1 . Открыть файл counter.txt с помощью BufferedReader, считать число в счет-
чик и закрыть файл.
2. Увеличить счетчик.
3. Записать счетчик обратно в файл counter.txt.
4. Вывести значение счетчика в web-браузере.
Представим, что два пользователя, Boni и Bulbul, обращаются к сервлету.
Сначала к нему обращается Boni, а затем, через пару наносекунд, Bulbul. Сце-
нарий выглядит, вероятно, следующим образом:
1. Метод service выполняет шаги 1 и 2, азатем получает другой запрос.
2. Метод делает шаг 1 для Bulbul, прежде чем продолжить выполнение ша-
гов 3 и 4 для Boni.
И что же? Boni и Bulbul получат одно и то же число, т. е. сервлет выдаст
неправильный результат. Как можно видеть в листинге 2.13, сервлет не под-
держивает многопоточность.
Л исти н г 2.13 Сервлет, не поддерживающий многопоточность
import javax.servlet.*;
import java.io.*;
public class SingleThreadedServlet extends GenericServlet
{
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
int counter = 0;
// получить сохраненное значение
try {
BufferedReader reader = new BufferedReader(
new FileReaderC’counter.txt"));
counter = Integer.parselnt( reader. readLineO );
reader.close();
}
Как устроены сервлеты
63
catch (Exception е) {
}
// увеличить счетчик
counter++;
// сохранить новое значение
try {
BufferedWriter writer = new BufferedWriter(
new FileWriter("counter,txt”));
writer.write(Integer.toString(counter));
writer.close();
}
catch (Exception e) {
}
try {
Printwriter out = response.getWriter();
out.println("You are visitor number " + counter);
}
catch (Exception e) {
}
}
}
Чтобы решить проблему, вспомните об играющих собаках. Если вторая со-
бака будет ждать, пока первая собака не закончит играть, обе собаки смогут
завершить игру успешно.
Следовательно, для обслуживания двух пользователей в одно время сервлет
должен заставить второго пользователя ждать, пока сервлет не закончит
обслуживать первого пользователя. При таком решении сервлет становится
однопоточным.
Это нетрудно сделать с помощью маркера интерфейса SingleThreadedServlet.
Не требуется изменять код, необходимо только реализовать интерфейс.
Код листинга 2.14 является модификацией листинга 2.13. Ничего не изме-
няется за исключением того, что класс SingleThreadedServlet реализует теперь
SingleThreadModel, обеспечивая поддержку потоков.
Л исти н г 2.14 Сервлет, поддерживающий многопоточность
impo rt j avax. se rvlet. *;
import java.io. *;
64
Глава 2
Листинг 2.14 Продолжение
public class SingleThreadedServlet extends GenericServlet
implements SingleThreadModel {
I
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
int counter = 0;
// получить сохраненное значение
try {
BufferedReader reader = new BufferedReader(
new FileReader("counter.txt"));
counter = Integer.parselnt( reader.readLine() );
reader.close();
}
catch (Exception e) {
}
// увеличить счетчик
counter++;
// сохранить новое значение
try {
BufferedWriter writer = new BufferedWriter(
new FileWriter("counter.txt"));
writer.write(Integer.toString(counter));
writer.close();
}
catch (Exception e) {
}
try {
Printwriter out = response.getWriter();
out.println("You are visitor number " + counter);
}
catch (Exception e) {
}
}
}
Теперь, если пользователь обратится к сервлету в тот момент, когда сервлет
обслуживает другого пользователя, то пользователь, обратившийся позже, дол-
жен будет ждать.
Если вы хотите поэкспериментировать с неправильной многопоточной
обработкой, то код листинга 2.15 предоставляет SingleThreadedServlet с за-
держкой в 6 секунд. Откройте два браузера и запросите быстро один и тот же
сервлет. Вы получите одинаковые значения в браузерах.
Как устроены сервлеты
65
Л истинг 2.15 Демонстрация сервлета, не поддерживающего
многопоточность
import javax.servlet.*;
import java.io.*;
public class SingleThreadedServlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws ServletException, lOException {
int counter = 0;
// получить сохраненное значение
try {
BufferedReader reader = new BufferedReader(
new FileReader("counter.txt"));
counter = Integer. parselnt( reader. readLineO );
reader.closeO;
}
catch (Exception e) {
}
// увеличить счетчик
counter++;
// задержка в 6 секунд
try {
Thread thread = new ThreadO;
thread.sleep(6000);
}
catch (InterruptedException e) {
}
// сохранить новое значение
try {
BufferedWriter writer = new BufferedWriter(
new FileWriter("counter.txt"));
writer.write(Integer.toString(counter));
writer.close();
}
catch (Exception e) {
}
try {
Printwriter out = response.getWriter();
out.println("You are visitor number " + counter);
}
catch (Exception e) {
}
}
}
66
Глава 2
Этот код открывает файл counter.txt, считывает значение, увеличивает зна-
чение и записывает новое значение в файл.
Однако между строкой кода, которая увеличивает значение, и строкой кода, ко-
торая записывает полученное значение для пользователя, вставлен следующий код:
try {
Thread.thread = new ThreadO;
thread.sleep(6000);
}
catch (InterruptedException e) {
Теперь имеется время для запроса этого же сервлета из второго браузера.
Значение, показываемое в браузерах, будет одним и тем же, если второй зап-
рос придет прежде, чем первый поток выполнения сервлета обновит значение
в файле counter.txt.
Неопытный программист мог бы предположить, что хорошим решением
является реализация каждым сервлетом интерфейса SingleThreadModel. Но это
неправильно. Если сервлет никогда не обращается к внешнему ресурсу, то по-
становка в очередь второго запроса будет создавать ненужную задержку для
следующих за первым запросом пользователей. Аналогично, если доступ к
внешнему ресурсу происходит, но обновлять его значение не требуется, нет
необходимости реализовывать интерфейс SingleThreadModel. Например, если
метод service сервлета только читает статическое значение из файла, то можно
разрешить нескольким потокам выполнения сервлета открывать и читать файл
одновременно.
Заключение
В этой главе описаны интерфейсы и классы пакета javax.servlet — одного из
двух пакетов, служащих для программирования сервлетов. Этот пакет содер-
жит базовые классы и интерфейсы, которые расширяются членами второго
пакета: javax.servlet.http. Важно знать базовые классы и интерфейсы javax.servlet,
хотя в реальных приложениях они используются реже, чем члены пакета
javax.servlet.http. Было показано также, как реализовать интерфейс
SingleThreadModel для решения проблемы, возникающей при обращении мно-
гопоточных сервлетов к одному внешнему ресурсу.
В следующей главе рассказывается о создании сервлетов, которые исполь-
зуют пакет javax.servlet.http.
Создание
сервлетных
приложений
предыдущих главах говорилось о том, как создавать сервлеты, выпол-
нять их в контейнере сервлетов и вызывать из web-браузера. Также были изу-
чены различные классы и интерфейсы пакета javax.servlet и показано, как ре-
шать проблему многопоточных сервлетов.
Однако при программировании сервлетов обычно работают с пакетом
javax.servlet.http. Классы и интерфейсы этого пакета являются производными
от классов и интерфейсов пакета javax.servlet. Члены javax.servlet.http богаче и
удобнее в использовании. В этом пакете класс HttpServlet, представляющий
сервлет, расширяет javax.servlet.GenericServlet и вносит большое число соб-
ственных методов. Пакетjavax.servlet.http имеет интерфейсы HttpServletRequest
и HttpServletResponse, которые эквивалентны интерфейсам javax.servlet.Request
и javax.servlet.Response соответственно. HttpServletResponse расширяет интер-
фейс javax.servlet.ServletResponse, a HttpServletRequest является производным
от интерфейса javax.servlet.ServletResponse.
Имеются дополнительные классы, которые недоступны в пакете javax.servlet.
Например, можно использовать класс с именем Cookie для работы с файлами
cookie. Кроме того, класс HttpServlet предоставляет методы для работы с сеан-
сами пользователей (см. главу 5).
Теперь рассмотрим класс HttpServlet, который обычно расширяется при
разработке сервлетов.
68
Глава 3
Класс HttpServlet
Класс HttpServlet расширяет класс javax.servlet.GenericServlet. Класс HttpServlet
добавляет ряд интересных методов. Наиболее важными являются шесть мето-
дов doxxx. Это методы doPost, doPut, doGet, doDelete, doOptions и doTrace. Каж-
дый из них вызывается при использовании соответствующего метода запроса
HTTP. Например, метод doGet вызывается, когда сервлет получает запрос
HTTP, который был послан с помощью метода GET.
ГПримечаниеЛ Можно заметить, что метод HEAD протокола HTTP 1.1 не
- - имеет соответствующего do-метода в сервлете. В
действительности метод doHead существует в классе
HttpServlet, но является частным (закрытым) методом.
Из шести методов doxxx наиболее часто используются методы doPost и doGet.
Метод doPost вызывается, когда браузер посылает запрос HTTP с йомощью
метода POST. Метод POST может использоваться формой HTML. Рассмотрим
следующую форму HTML на клиентской стороне:
<FORM ACTION="Register" METH0D="P08T">
<INPUT TYPE=TEXT Name=”firstNanie,,>
<INPUT TYPE=TEXT Name=’TastName”>
<INPUT TYPE=SUBMIT>
</FORM>
Когда пользователь нажимает кнопку Submit, чтобы отправить форму, бра-
узер посылает серверу запрос HTTP с помощью метода POST. Web-сервер пе-
редает этот запрос сервлету Register, и вызывается метод сервлета doPost. Пара
имя/значение параметра формы посылается в теле запроса. Например, если в
приведенной выше форме ввести Ann в качестве значения firstName и Go в
качестве значения lastName, то в теле запроса будет содержаться:
firstName=Ann
lastName=Go
Форма HTML может также использовать метод GET; однако POST приме-
няется с формами HTML значительно чаще.
Метод doGet вызывается, когда запрос HTTP посылается с помощью мето-
да GET. GET является методом, используемым в HTTP по умолчанию. При
вводе URL, например www.yahoo.com, запрос посылается на Yahoo! с помо-
щью метода GET. Если метод GET применяется в форме, пара имя/значение
параметра добавляется к URL. Поэтому, если в форме имеются два параметра
с именами firstName и lastName и пользователь вводит Ann и Go соответствен-
но, то U RL сервлета будет таким:
Создание сервлетных приложений
69
http://yourdomain/myApp/Register?firstName=Ann&lastName=Go
При получении метода GET сервлет вызовет свой метод doGet.
(Примечание^ Может возникнуть вопрос, как сервлет узнает, какой метод
doxxx вызывать. Ответ можно найти в исходном коде класса
HttpServlet. Этот класс наследует метод service из интерфейса
javax.servlet.Servlet, который вызывается контейнером
сервлетов. Напомним, что его сигнатура имеет вид:
public void service(ServletRequest request,
ServletResponse response)
throws ServletException, lOException
Этот метод использует HttpRequest для запроса и
HttpResponse для ответа и передает оба как аргументы
второму методу service, который имеет сигнатуру:
protected void servicef HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException
Интерфейс HttpServletRequest имеет метод с именем
getMethod, который возвращает String, содержащую метод
HTTP, используемый клиентским запросом. Зная метод HTTP,
метод service вызывает соответствующий метод doxxx.
Сервлет в листинге 3.1 применяет методы doGet и doPost.
Г Примечание^) Если форма HTTP не имеет атрибута ACTION, то значением
- по умолчанию для этого атрибута будет текущая страница.
Листинг 3.1 Методы doGet и doPost
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response. setContentType(’’text/html’’);
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>The GET method</TITLE>");
70
Глава 3
Листинг 3.1 Продолжение
out.println(”</Н EAD>”);
out. println (”<BODY>*');
out.println("The servlet has received a GET. ” +
"Now, click the button below.");
out.println("<BR>");
out.println("<FORM METHOD=POST>");
out.println("<INPUT TYPE=SUBMIT VALUE=Submit>");
out.println("</FORM>");
out.println("</BODY>");
out.println("</HTML>");
}
public void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>The POST method</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("The servlet has received a POST. Thank you.");
out.println("</BODY>");
out.println("</HTML>");
}
}
Когда сервлет вызывается в первый раз из web-браузера путем ввода U RL сер-
влета в поле Address или Location, в качестве метода запроса используется GET. На
серверной стороне вызывается метод doGet. Сервлет посылает строку «The servlet
has received a GET. Now, click the button below». («Сервлет получил GET Теперь
нажмите кнопку, расположенную ниже».) плюс форму HTML (см. рис. 3.1).
Посылаемая браузеру форма применяет метод POST. Когда пользователь
нажимает кнопку, чтобы отправить форму, серверу передается запрос POST.
Сервлет вызывает метод doPost, посылая браузеру String, содержащую «The
servlet has received a POST. Thank you» («Сервлет получил POST. Спасибо»).
Выходные данные doPost показаны на рис. 3.2.
Создание сервлетных приложений
71
The servlet has received a GET. Now, dick the button below.
S;
4:
V
Рис. 3.1. Выходные данные метода doGet
The servlet has received a POST. Thank you. &
-»*
if.
К :
* •
i
& '
4 :
s
ife
*
&
5
&
Рис. 3.2. Выходные данные метода doPost
72
Глава 3
Интерфейс HttpServletRequest
Помимо нескольких специфических для протокола методов, содержащихся в
классе HttpServlet, пакет javax.servlet.http предоставляет также развитые интер-
фейсы запроса и ответа: HttpServletRequest и HttpServletResponse.
Получение заголовков запроса HTTP из
HttpServletRequest
Запрос HTTP, который браузер клиента посылает серверу, включает в себя
заголовок запроса HTTP с важной информацией, такой как cookie и referer.
Доступ к заголовкам можно получить с помощью объекта HttpServletRequest,
передаваемого методу doxxx.
(/Примечание^ Список всех заголовков запроса HTTP приводится в главе 13.
Следующий пример демонстрирует, как можно использовать интерфейс
HttpServletRequest д ля получения всех имен заголовков и для отправки пар имя/
значение заголовков в браузер (см. листинг 3.2).
Листинг 3.2 Получение заголовков запроса HTTP
import javax.servlet.*;
import javax.servlet.http.*,
import java.io.*;
import java.util.*;
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response. setContentType(’’text/html”);
Printwriter out = response.getWriter();
Enumeration enumeration = request.getHeaderNamesO;
while (enumeration.hasMoreElementsO) {
String header = (String) enumeration.nextElement();
out.println(header + ": " + request.getHeader(header) + "<BR>”);
}
}
}
RegisterServlet в листинге 3.2 использует методы getHeaderNames и getHeader.
Сначала вызывается getHeaderNames для получения Enumeration, содержаще-
Создание сервлетных приложений
73
го все имена заголовков, находящиеся в клиентском запросе. Значение каждо-
го заголовка извлекается с помощью метода getHeader, которому передается
имя заголовка.
Выходные данные кода зависят от клиентского окружения, в частности от ис-
пользуемого браузера и операционной системы клиентской машины. Например,
некоторые браузеры могут посылать серверу файлы cookie. На наличие заголовка
referer запроса HTTP влияетто, как пол ьзователь запрашивает сервлет: вводит U RL
в поле Address/Location или щелкает мышью на гиперссылке.
Выходные данные кода листинга 3.2 показаны на рис. 3.3.
Cookie u»enume=budu p*ssword=3010562 < ‘
Cache-ConttoL no-cache
Host localhost8080 А:
Accept Mnige/g£ «nagehi-xbitmap, mage/jpeg, tmage/pjpeg, appEcabon/vndmi-excel, appkcabon/msword. S.
appkcahon/vnd.mf-powerpomt, appkcahon/pd£
User-Agent MoalaM.O (compatible; MSIE 6.0b; Windows NT 5.0; NET CLR 1.0.2914)
Content-Length: 0
Accept-Language: en-us
Accept-Encodng gap, deflate
Content-Type: apphcaiion/x-www-fonn-urlencoded
Connection: Keep-Alive
Referer. bttp//localhost808Q/servfet/Reg^terServiet
Рис. 3.3. Получение заголовков запроса HTTP
Некоторые методы интерфейса HttpServletRequest предоставляют инфор-
мацию о путях доступа. Метод getPathlnfo возвращает String, содержащую до-
полнительную информацию о пути, или null, если информации о пути нет.
Метод get Path Translated возвращает ту же информацию, что и метод getPathlnfo,
но транслирует путь в имя физического пути доступа или возвращает null, если
нет дополнительной информации о пути. Дополнительная информация сле-
дует после имени сервлета и перед строкой запроса. Имя сервлета и дополни-
тельная информация разделяются символом наклонной черты (/).
Например, рассмотрим запрос к сервлету с именем Path Info DemoServlet,
сделанный с помощью URL:
http://localhost:8080/myApp/servlet/PathInfoDemoServlet/AddInfo?id=2
Этот URL содержит дополнительную информацию «/Addinfo» после име-
ни сервлета. Метод getPathlnfo вернет строку «/Addinfo», метод getQueryString
74
Гпава 3
вернет «id=2», а метод getRathTranslated вернет «C:\App\Java\AddInfo». Значение,
возвращаемое getPathTranslated, зависит от местоположения файла класса
сервлета.
Метод getRequestURI вернет первую строку универсального идентифика-
тора ресурса (URI) запроса. Это будет часть URI, которая находится слева от
строки запроса. Метод getServletPath возвращает часть URI, которая ссылает-
ся на вызываемый сервлет. На рис. 3.4 приведена информация о пути доступа
сервлета с именем Http Request DemoServlet.
Path fafo: oul
Pidi Trvuiitedt вы!
Request URL AnyAppAervietdfapRcquestDaDO
Soviet Pith: /serrict/HnpRsquestDesao
Рис. 3.4. Информация о пути доступа HttpRequestDemoServlet
Получение строки запроса из
HttpServletRequest
Следующим важным методом является getQueryString, который использу-
ется для извлечения строки запроса HTTP. Строка запроса размещается в URL
справа от пути доступа к сервлету.
Если в форме HTML используется метод GET, то пара имя/значение пара-
метра добавляется к URL. Код в листинге 3.3 является сервлетом с именем
HttpRequestDemoServlet, который выводит на экран значение строки запроса
и форму.
Л истин г 3.3 Получение строки запроса
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
Создание сервлетных приложений
75
public class HttpRequestDemoServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.printIn(”<HTML>”);
out.println(”<HEAD>");
out.println(”<TITLE>Obtaining the Query String</TITLE>");
out.println("</HEAD>”);
out.println(”<BODY>”);
out.println(”Query String: ” + request.getQueryString() + ”<BR>”);
out.println(”<FORM METHOD=GET>”);
out. println(”<BR>First Name: <INPUT TYPE=TEXT NAME=FirstName>’’);
out.println(”<BR>Last Name: <INPUT TYPE=TEXT NAME=LastName>");
out.println("<BR><INPUT TYPE=SUBMIT VALUE=Submit>”);
out.printIn("</FORM>");
out.println("</B0DY>");
out.println(”</HTML>”);
Когда пользователь вводит URL сервлета в браузере Web и сервлет вызыва-
ется в первый раз, строка запроса содержит null, как показано на рис. 3.5.
После ввода каких-то значений в форму HTML и отправки формы стра-
ница выводится заново (см. рис. 3.6). Обратите внимание, что теперь име-
ется добавленная к URL строка. Строка запроса содержит пары имя/зна-
чение параметров, разделенные знаком амперсанда (&).
Получение параметров из HttpServletRequest
Выше было показано, как получить строку запроса, содержащую значение.
Следовательно, можно получить пары имя/значение параметров формы или
другие значения предыдущей страницы. Однако применение метода
getQueryString для получения пар имя/значение параметров формы нежела-
тельно, так как в этом случае придется самостоятельно выполнять анализ стро-
ки. Можно использовать другие методы HttpServlet Request для получения имен
и значений параметров: методы getParameterNames и getParameter.
Метод getParameterNames возвращает Enumeration, содержащее имена па-
раметров. Однако во многих случаях имена параметров уже известны, и этот
метод использовать не нужно. Для получения значения параметра служит ме-
тод getParameter, принимающий имя параметра в качестве аргумента.
76
Глава 3
Следующий пример демонстрирует, как можно использовать методы
getParameterNames и get Parameter для вывода всех имен и значений парамет-
ров формы HTML с предыдущей страницы. Код приводится в листинге 3.4.
Рис. 3.5. Строка запроса равна null
Query Stnog НгйЫаам"1м&Ьм1№|ве*Пмг
Fnt Nene: |
Last Nine: |
Рис. 3.6. Строка запроса co значением, отличным от null
Листинг 3.4 Получение пар имя/значение параметров
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
Создание сервлетных приложений
77
public class HttpRequestDemoServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
response.setContentType(”text/html");
Printwriter out = response.getWriter();
out. println(’’<HTML>");
out.println(”<HEAD>”);
out.println(”<TITLE>Obtaining the Parameter</TITLE>”);
out.println("</HEAD>”);
out.println(”<BODY>");
out.println("The request’s parameters are:<BR>’’);
Enumeration enumeration = request.getParameterNamesO;
while (enumeration.hasMoreElements()){
String parameterName = (String) enumeration.nextElement();
out.println(parameterName + ”: ” +
request.getParameter(parameterName) + ”<BR>" );
}
out.println("<FORM METHOD=GET>");
out.println(”<BR>First Name: <INPUT TYPE=TEXT NAME=FirstName>");
out.println(”<BR>Last Name: <INPUT TYPE=TEXT NAME=LastName>");
out.println(”<BR><INPUT TYPE=SUBMIT VALUE=Submit>”);
out.println("</FORM>”);
out. println(’’</BODY>”);
out.println("</HTML>”);
}
}
При первом вызове сервлет не имеет никаких параметров предыдущего зап-
роса, поэтому не выводится никаких пар имя/значение (см. рис. 3.7).
При последующих запросах пользователь должен ввести значения обоих
параметров: firstName и lastName. Это показано на странице рис. 3.8.
Код листинга 3.4 будет также работать, если форма использует метод POST.
Однако во многих случаях необходимо передавать в URL значения, получае-
мые не из формы. Эта техника рассматривается в главе 5.
78
Глава 3
Рис. 3.7. Первый запрос не имеет параметра
The request’s parameters are:
FntName: Andy
LastName: Walsh
First Name: |
Last Name: |
9
Рис. 3.8. Пары имя/значение параметров
Обработка многозначных параметров
Нередко требуется использовать в форме параметры с одинаковыми име-
нами, например, в случае применения элементов управления флажком, которые
могут принимать несколько значений, или при наличии элемента управления
HTML с множественным выбором. В подобных ситуациях нельзя использо-
Создание сервлетных приложений
79
вать метод get Parameter, так как он будет выдавать только первое значение.
Вместо этого применяется метод getParameterValues.
Метод getParameterValues имеет один аргумент: имя параметра. Он возвра-
щает массив строк, содержащий все значения этого параметра. Если параметр
с аданным именем не найден, метод getParameterValues вернет null.
В следующем примере используется метод getParameterValues для получения
всех любимых музыкальных жанров, выбранных пользователем. Код этого сер-
влета показан в листинге 3.5.
Листинг3.5 Получение нескольких значений параметра
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class HttpRequestDemoServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response. setContentType( ’’text/html");
Printwriter out = response.getWriter();
out. println(’’<HTML>”);
out.println(”<HEAD>”);
out.println(”<TITLE>Obtaining Multi-Value Parameters</TITLE>”);
out.println(”</HEAD>”);
out.println("<BODY>");
out. println(’’<BR>");
out.println("<BR>Select your favorite music:”);
out. println( ”<BRXF0RM METHOD=POST>”);
out.println("<BR><INPUT TYPE=CHECKBOX ” +
"NAME=favoriteMusic VALUE=Rock>Rock”);
out.println(”<BRXINPUT TYPE=CHECKBOX ” +
"NAME=favoriteMusic VALUE=Jazz>Jazz”);
out.println(”<BR><INPUT TYPE=CHECKBOX ” +
"NAME=favoriteMusic VALUE=Classical>Classical”);
out.println(”<BRX!NPUT TYPE=CHECKBOX ” +
"NAME=favoriteMusic VALUE=Country>Country”);
out.println(”<BR><INPUT TYPE=SUBMIT VALUE=Submit>”);
out.println(”</FORM>”);
out.println(”</BODY>");
out.println(”</HTML>”);
}
80
Глава 3
Листинг 3.5 Продолжение
public void doPost(HttpServletftequest request,
HttpServletResponse response)
throws ServletException, lOException {
String[] values = request.getParameterValuesC’favoriteMusic");
response.setContentType("text/html");
Printwriter out = response.getWriter();
if (values != null ) {
int length = values.length;
out.printlnf’You have selected: ");
for (int i=0; Klength; i++) {
out.println('<BR>” + values[i]);
}
}
}
При первом вызове сервлета вызывается метод doGet, который посылает
форму. Форма имеет четыре элемента управления флажком с одним именем:
favoriteMusic. Однако значения флажков различны (см. рис. 3.9).
Рис. 3.9. Форма с несколькими флажками
Когда пользователь устанавливает флажки, браузер посылает все выбран-
ные значения. На серверной стороне используется метод getParameterValues для
извлечения значений, посланных в запросе (см. рис. 3.10).
Создание сервлетных приложений
81
Рис. 3.10. Вывод выбранных значений
Отметим, что для формы используется метод POST, поэтому пары имя/зна-
чение параметров извлекаются в методе doPost.
HttpServletResponse
Интерфейс HttpServletResponse предоставляет несколько зависящих от прото-
к ла методов, недоступных в интерфейсе javax.servlet.ServletResponse. Интер-
фейс HttpServletResponse расширяет интерфейс javax.servlet.ServletResponse. В
примерах, приведенных выше, использовались два метода HttpServletResponse
при отправке выходных данных в браузер: setContentType и getWriter.
response.setContentType("text/html");
Printwriter out = response.getWriter();
Имеются и другие методы. Метод addCookie посылает браузеру cookie. Для
обработки URL, посылаемых браузеру, также используются специальные ме-
тоды (см. главу 5).
Еще одним интересным методом интерфейса HttpServletResponse является
метод set Header. Он позволяет добавить поле имя/значение в заголовок ответа.
Можно также использовать метод sendRedirect для перенаправления
пользователя на другую страницу. Когда вызывается этот метод, web-сервер
посылает специальное сообщение браузеру для запроса другой страницы.
Поэтому всегда осуществляется взаимодействие с клиентской стороной,
прежде чем извлекается другая страница. Этот метод применяется в следующем
82
Глава 3
примере. Листинг 3.6 показывает страницу Login, которая предлагает пользо-
вателю ввести имя пользователя и пароль. Если оба значения правильны,
пользователь перенаправляется на страницу Welcome. В противном случае
пользователь увидит ту же самую страницу Login.
При первом вызове сервлета вызывается метод сервлета doGet. Метод doGet
выводит форму. Пользователь может ввести имя пользователя и пароль и от-
править форму. Отметим, что форма применяет метод POST, что означает, что
на серверной стороне вызывается метод doPost и имя пользователя и пароль
сравниваются с предопределенными значениями. Если имя и пароль совпада-
ют, пользователь перенаправляется на страницу Welcome. В противном случае
метод doPost снова выводит форму Login вместе с сообщением об ошибке.
Листинг 3.6 Страница Login
import javax.servlet.*;
import javax.servlet.http. *;
import java.io.*;
import java.util.*;
public class LoginServlet extends HttpServlet {
private void sendLoginForm(HttpServletResponse response,
boolean withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriter();
out.println(”<HTML>”);
out.println("<HEAD>");
out. println(”<TITLE>Login</TITLE>”);
out. println(”</HEAD>’’);
out.println(”<BODY>");
if (withErrorMessage)
out.println(”Login failed. Please try again.<BR>");
out.println(”<BR>”);
out.println("<BR>Please enter your user name and password.”);
out.println("<BR><FORM METHOD=POST>”);
out.println(”<BR>User Name: <INPUT TYPE=TEXT NAME=userName> );
out.println("<BR>Password: <INPUT TYPE=PASSWORD NAME=password> );
out.println(”<BR><INPUT TYPE=SUBMIT VALUE=Submit>”);
out.println(”</FORM>");
out.println(”</BODY>");
out.println(”</HTML>");
Создание сервлетных приложений
83
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendl_oginForm(response, false);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter(’’userName’’);
String password = request.getParameter(’’password”);
if (userName!=null && password!=null &&
userName. equals(’’jamesb") && password.equals(”007’’)) {
response.sendRedi rect("http://domain/app/WelcomePage");
}
else {
sendLoginForm(response, true);
}
}
}
("Примечание^ При перенаправлении на ресурс, находящийся в том же
-- J приложении, не требуется определять полный URL, т. е. в
предыдущем примере можно написать
response.sendRedirect(«/app/WelcomePage»). Однако по
соображениям эффективности метод sendRedirect обычно не
используется для перенаправления пользователя на другой
ресурс в том же приложении. В этих целях применяется
другой метод (см. ниже).
В коде листинга 3.6 присутствует закрытый (private) метод с именем
sendLoginForm, который получает объект HttpServletResponse и логическое
значение, указывающее, будет ли вместе с формой посылаться сообщение об
ошибке. Метод sendLoginForm вызывается из методов doGet и doPost. Когда
он вызывается из метода doGet, сообщение об ошибке не задается, так как это
первый запрос страницы пользователем. Поэтому флаг with Error Messge уста-
новлен в false. При вызове из метода doPost флаг принимает значение true, по-
скольку метод sendLoginForm вызывается из метода doPost только в том слу-
чае, если имя пользователя и пароль не соответствуют заданным.
Страница Login при первом запросе показана на рис. 3.11. Страница Login
после неудачной попытки входа приведена на рис. 3.12.
84
Глава 3
Please eater your uier name and paatword.
U»er Name: | '
Pasaword:| '
Рис. 3.11. Страница Login при первом запросе
По поводу этого примера у вас может возникнуть вопрос: «Если для того,
чтобы попасть на страницу Welcome, достаточно ввести U RL в браузере Web,
зачем тогда регистрироваться?»
Это правильный вопрос, ведь пользователь может обойти страницу Login.
Эта проблема имеет отношение к управлению сеансом и рассматривается в
главе 5.
Login faded. Pfeaac toy again.
Pleue etter your шег name and pattword
User Name : [
Pairword [
id
Рис. 3.12. Страница Login после неудачной попытки входа
Создание сервлетных приложений
85
Отправка кода ошибки
HttpServletResponse позволяет также посылать предопределенные сообщения об
ошибках. Интерфейс определяет ряд public static final целых чисел, которые
начинаются с SC_. Например, SC_FORBIDDEN будет транслироваться
в НТТР-ошибку 403.
Вместе с кодом ошибки можно послать индивидуальное сообщение об
ошибке. При неудачной регистрации, вместо повторного вывода на экран стра-
ницы Login, можно послать НТТР-ошибку 403 и сообщение. Чтобы сделать
это, замените вызов sendLoginForm в методе doPost следующим:
response.sendError(response.SC_FORBIDDEN, "Login failed.");
На рис. 3.13 показано то, что увидит пользователь при неудачной регистрации.
(^Примечанием Полный список статусных кодов можно найти в приложении С.
Error: 403
Location: /servlet/LoginServlet
Lo0c fated.
Рис. 3.13 Отправка HTTP-ошибки 403
Отправка специальных символов
Несколько символов имеют в HTML специальное назначение. Например, сим-
вол «меньше» (<) используется как открывающий символ тега HTML, а символ
«больше» (>) является закрывающим символом тега HTML.
При отправке этих символов для отображения в браузере необходимо зако-
дировать их, чтобы они выводились правильно. Например, рассмотрим код
листинга 3.7.
86
Глава 3
Метод doGet из SpecialCharacterServlet предназначен для отправки строки,
которая будет выводиться браузером как следующий текст:
In HTML, you use <BR> to change line.
Листинг 3.7 Неправильное отображение специальных символов
import javax.servlet.*;
import javax.servlet.http.*; >
import java.io.*;
import java.util.*;
public class SpecialCharacterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriter();
out. println(”<HTML>”);
out.printin("<HEAD>”);
out.println("<TITLE>HTML Tutorial - Changing Line</TITLE>”);
out.println(”</HEAD>");
out.println("<BODY>”);
out.println("In HTML, you use <BR> to change line.”);
out.println(”</BODY>");
out.println(”</HTML>”);
Однако этот код создает проблему.
Выходные данные метода doGet, получаемые в браузере, показаны нарис. 3.14.
Поскольку <BR> означает в HTML перевод строки, текст выводится не-
правильно. <BR> интерпретируется как команда разбиения исходной строки
на две, и выходные данные выводятся в двух строках.
Если требуется вывести на экран специальный символ, его необходимо за-
кодировать. Символ «меньше» (<) кодируется как «<», а символ «больше»
(>) как «>». Другими специальными символами являются амперсанд (&) и
двойная кавычка ("). Амперсанд (&) заменяется кодом «&атр;», а двойная ка-
вычка (") — «"». Кроме того, два и более пробелов всегда выводятся как
один пробел, если не преобразовать каждый отдельный пробел в « ».
Преобразование каждого вхождения специального символа является скучной
работой. Имеется функция, которая делает это автоматически. Она называется
encodeHtmlTag (см. листинг 3.8). Если строка String, посылаемая браузеру, со-
Создание сервлетных приложений
87
держит специальный символ, передайте ее на преобразование функции
encode HtmlTag.
Мф|/|Ьа1юМ0вЦ
Poh Irfo: aufl
Pith Translated: nuO
Request URL /myApp/serriet/HttpRequestDemo
Soviet Path: /seraiet/HttpRequestDemo
Рис. 3.14. Специальные символы выводятся неправильно
Листинг 3.8 Функция encodeHtmlTag
public static String encodeHtmlTag(String tag) {
if (tag==null)
return null;
int length = tag.length();
StringBuffer encodedTag = new StringBuffer(2 * length);
for (int i=0; i<length; i++) {
char c = tag.charAt(i);
encodedTag.append("<”);
else if (c==’>’)
encodedTag. append( "> ’’);
else if (c==’&')
encodedTag.append(”& ”);
else if (c==”")
encodedTag.append(""");
else if (c==’ ’)
encodedTag.append(" ");
else
encodedTag.append(c);
}
return encodedTag.toStringO;
88
Глава 3
Листинг 3.9. демонстрирует сервлет, который использует метод
encodeHtmlTag для кодирования любой строки String со специальными сим-
волами.
Листинг 3.9 Использование метода encodeHtmlTag в сервлете
import javax.servlet.* *;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class SpecialCharacterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.printin("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>HTML Tutorial - Changing Line</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println(encodeHtmlTag("In HTML, you use <BR> to change line."));
out.println("</B0DY>");
out.println("</HTML>”);
}
I it ★
* Кодирует тег HTML, чтобы он выводился
* в браузере как есть.
* В частности, этот метод ищет
* в String и заменяет каждое вхождение
* следующих символов:
* ’<’ на "<"
* ’>’ на ">:"
* '&’ на "&атр;"
★ на """
* ’ ' на ” "
* /
public static String encodeHtmlTag(String tag) {
if (tag==null)
return null;
inx length = tag.length();
StringBuffer encodedTag = new StringBuffer(2 * length);
Создание сервлетных приложений
89
for (int i=0; Klength; i++) {
char c = tag.charAt(i);
if (c==’<’)
encodedTag.append("<”);
else if (c=='>')
encodedTag.append(”>");
else if (c==’&’)
encodedTag.append(”&”);
else if (c=="”)
encodedTag.append(”"");
else if (c==’ ’)
encodedTag.append(" ”);
else
encodedTag.append(c);
return encodedTag.toStringO;
На рис, 3.15 показаны выходные данные при отправке строки «In HTML,
you use <BR> to change line». Если посмотреть на исходный код HTML, то можно
заметить, что символ < был преобразован в <, а символ > в >.
Рис. 3.15. Кодирование специальных символов
90
Глава 3
Буферизация ответа
Если инициирована буферизация ответа, то выходные данные не посылаются
в браузер, пока не будет закончена обработка сервлета или пока не заполнится
буфер. Буферизация улучшает производительность сервлета, так как сервлету
необходимо посылать строковые данные только один раз, а не всякий раз, когда
вызывается метод print или println объекта Printwriter.
По умолчанию буферизация включена и размер буфера,равен 8192 символам.
Можно изменить это значение с помощью метода setBufferSize интерфейса
HttpServletResponse. Этот метод может вызываться только перед отправкой
каких-либо данных.
Заполнение элементов HTML
Одной из часто выполняемых задач является заполнение значений элементов
HTML. Это простая задача, если следовать двум правилам:
1. Всегда заключайте значение в двойные кавычки (”). В этом случае пробе-
лы будут отображаться правильно.
2. Если значение содержит двойную кавычку, ее необходимо закодировать.
Сервлет в листинге 3.10 содержит форму HTML с двумя элементами: User
Name и Password. Для элемента User Name задано значение Duncan «The Great»
Young, а паролем является lo&&lita.
Л исти н г 3.10 Заполнение элементов HTML
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class PopulateValueServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
String userName = "Duncan \"The Great\" Young";
String password = "lo&&lita”;
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>”);
out.println("<HEAD>");
Создание сервлетных приложений
91
out.println("<TITLE>Populate HTML elements</TITLE>”);
out.println(”</HEAD>”);
out.println(”<H3>Your user name and password.</H3>");
out.println(”<FORM METH0D=P08T>");
out.pnntln("<BR>User name: dNPUT TYPE=TEXT NAME=userName VALUE=\"" +
userName +
out.println("<BR>Password:
<INPUT TYPE=PASSWORD NAME=password VALUE=\"" +
password +
out.println("<BODY>”);
out.println("</BODY>");
out.println("</HTML>”);
Однако, как можно видеть на рис. 3.16, значение User Name обрезается,
поскольку браузер воспринимает первый символ двойной кавычки в «The
Greate» как конец значения. Для исключения этой проблемы используйте ме-
тод encodeHtmlTag.
Рис. 3.16. Обрезанное значение
92
Глава 3
Пересылка запроса
В некоторых ситуациях требуется вставить содержимое HTML-страницы или
выходные данные другого сервлета. Кроме того, может понадобиться передать
обработку запроса НТМ L из одного сервлета в другой. Текущая спецификация
сервлетов предлагает для этого интерфейс Request Dispatcher, который нахо-
дится в пакете javax.servlet. Этот интерфейс имеет два метода: include и forward,
которые позволяют делегировать обработку запроса-ответа другому ресурсу.
Оба метода получают в качестве аргументов объект javax.servlet.Servlet Request
и объект javax.servlet.ServletResponse.
Метод include используется для вставки содержимого другого ресурса, та-
кого как сервлет, страница JSP или страница HTML. Метод имеет сигнатуру:
public void include(javax.servlet.ServletRequest request,
javac.servlet.ServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Метод forward служит для пересылки запроса от одного сервлета другому.
Исходный сервлет может выполнить некоторые начальные задачи примени-
тельно к объекту ServletRequest, прежде чем переслать его. Сигнатура метода
forward имеет вид:
public void forward(javax.servlet.ServletRequest request,
javac.servlet.ServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Различие между sendRedirect и forward
Оба метода, sendRedirect и forward, перемещают пользователя к новому
ресурсу. Однако между ними существует фундаментальное различие.
Метод sendRedirect работает, посылая код статуса, который заставляет
браузер запросить другой URL. Это означает, что всегда существует
обмен с клиентской стороной. Кроме того, предыдущий объект
HttpServletRequest теряется. Чтобы передать информацию между
исходным сервлетом и следующим запросом, информация обычно
передается как строка запроса, добавляемая к URL места назначения.
Метод forward перенаправляет запрос без помощи клиентского браузера.
Оба объекта, HttpServletRequest и HttpServletResponse, также передаются
новому ресурсу.
Чтобы выполнить метод include или forward, сначала необходимо получить
объект RequestDispatcher. Объект Request Dispatcher можно получить тремя спо-
собами:
Создание сервлетных приложений
93
• Использовать MeroagetRequestDispatcherMHTep^flcajavax.servlet.SefvletCbntext,
передав String, содержащую путь доступа к другому ресурсу. Путь задает-
ся относительно корня ServletContext.
• Использовать метод getRequestDispatcher интерфейсаjavax.servlet.ServletRequest,
передав String, содержащую путь доступа к другому ресурсу. Путь задает-
ся относительно текущего запроса HTTP.
• Использовать MeroagetNamedDispatcher интерфейсаjavax.servlet.ServletContext,
передав String, содержащую имя другого ресурса.
При написании кода для пересылки запроса программисты-новички часто де-
п ют общую ошибку, передавая методу getRequestDispatcher неправильный путь
доступа. Существует большое различие между методом getRequestDispatcher
интерфейса ServletContext и методом, принадлежащим интерфейсу
ServletRequest. Выбор метода зависит от расположения ресурса, который будет
включен или которому будет передан запрос. Если применяется метод
getRequestDispatcher интерфейсаjavax.servlet.ServletContext, то передается путь
доступа, задаваемый относительно корня ServletContext. При использовании
ла, года getRequestDispatcher интерфейса javax.servlet.ServletRequest передается
путь, который задается относительно текущего запроса HTTR
При создании объекта Request Dispatcher в сервлете с именем FirstServlet для
включения или пересылки запроса другому сервлету с именем SecondServlet
п още всего поместить файлы классов обоих сервлетов в один каталог. Таким
эоразом, FirstServlet можно вызвать с помощью URLhttp://domain/VirtualDir/
servlet/FirstServlet, a SecondServlet — с помощью U RL http://domain/VirtualDir/
servlet/SecondServlet. Затем можно использовать getRequestDispatcher интер-
фейса ServletRequest, передав имя второго сервлета. В FirstServlet можно напи-
сать следующий код:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
, RequestDispatcher rd =
request.getRequestDispatcher("SecondServlet");
rd.include(request, response);
}
или
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd =
request.getRequestDispatcher("SecondServlet");
rd.forward(request, response);
}
94
Глава 3
Так как FirstServlet и SecondServlet находятся в одном каталоге, нет необходи-
мости вставлять слэш (/) перед SecondServlet. В этом случае не требуется беспо-
коиться о путях доступа сервлетов. Рассмотрим другой, более сложный способ.
Передадим следующую строку в getRequestDispatcher объекта ServletRequest:
"/servlet/SecondServlet”
Если использовать getRequestDispatcher объекта ServletContext, то нужно
передать в качестве аргумента пути «/Virtual Dir/servlet/SecondServlet»:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd =
getServletContext().getRequestDispatcher(’’/servlet/SecondServlet”);
rd.include(request, response);
}
или
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd =
getServletContext().getRequestDispatcher("/servlet/SecondServlet”);
rd.forward(request, response);
}
Можно использовать метод getNamed Dispatcher:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd = *
getServletContext().getNamedDispatcher("SecondServlet”);
rd.include(request, response);
}
или
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd =
getServletContext (). getNamedDispatcher( ’’SecondServlet”);
rd.forward(request, response);
}
Создание сервлетных приложений
95
Конечно, при использовании метода getNamedDispatcher необходимо ре-
гистрировать второй сервлет в дескрипторе развертывания. Например:
<?xml version="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>FirstServlet<servlet-class>
</servlet>
<servlet>
<servlet-name>SecondServlet</servlet-name>
<servlet-class>SecondServlet<servlet-class>
</servlet>
</web-app>
Если включение осуществляется в методе doPost, будет вызываться метод
doPost второго сервлета. Если включение выполняется в методе doGet, будет
вызываться метод doGet второго сервлета.
Предупреждение! Если включенный в сервлет ресурс изменяется,
.... 1 ” .......... необходимо перезапустить Tomcat, чтобы изменение
вступило в силу. Это требуется сделать, потому что
включенный сервлет никогда не вызывается напрямую.
После загрузки включенного сервлета его отметка
времени не сравнивается.
Использование интерфейса Request Dispatcher более подробно рассматри-
вает в следующем разделе.
('Примечание'} Раньше для выполнения функций RequestDispatcher
' - использовалось сцепление сервлетов. Однако сцепление
сервлетов не является частью спецификации J2EE, и его
применение зависит от конкретных контейнеров сервлетов.
Этот термин все еще можно встретить в старой литературе
по сервлетам.
96
Глава 3
Включение других ресурсов
Во многих ситуациях требуется включить в сервлет другие ресурсы. Напри-
мер, может существовать совокупность функций JavaScript, которую желательно
включить в ответ пользователю. Выделение несервлетного содержимого гаран-
тирует обеспечение модульности. В этом случае программист JavaScript может
работать независимо от программиста сервлетов. Страница, содержащая фун-
кции JavaScript, может быть включена с помощью метода include объекта
RequestDispatcher.
Другой пример: необходимо вставить выходные данные сервлета, которые
являются ссылкой на случайно выбранный рекламный баннер. Выделив в от-
дельный сервлет код, который выбирает баннер, этот код можно включать в
различные сервлеты, и форматирование можно выполнять только для вклю-
чаемого сервлета.
Метод include интерфейса RequestDispatcher может быть вызван в любое
время.
Целевой сервлет имеет полный доступ к объекту запроса, но может записы-
вать информацию только в ServletOutputStream или в объект Writer объекта от-
вета. Целевой сервлет может также завершить ответ, либо дописав содержимое
после конца буфера ответа, либо явно вызвав метод flush интерфейса
ServletResponse. Включаемый сервлет не может задавать заголовки или вызы-
вать какой-либо метод, который влияет на заголовки ответа.
Когда сервлет вызывается в методе include, этому сервлету может понадо-
биться узнать путь, по которому он был вызван. Во включаемом сервлете можно
получить доступ и задать следующие атрибуты запроса с помощью метода
getAttribute объекта запроса:
• javax.servlet.include.request_uri
• javax.servlet.include.context_path
• javax.servlet.include.servlet_path
• javax.servlet.include.path_info
• javax.servlet.include.query_string
Эти атрибуты не устанавливаются, если включаемый сервлет получен с по-
мощью метода getNamed Dispatcher.
Включение статического содержимого
Иногда необходимо вставлять статическое содержимое, например страницы
HTML или файлы изображений, подготовленные дизайнером web-графики.
Это можно сделать с помощью той же техники, которая применяется для вклю-
чения динамических ресурсов.
Создание сервлетных приложений
97
Следующий пример показывает сервлете именем FirstServlet, который вклю-
чает файл HTML с именем AdBanner.html. Файл класса сервлета расположен в
каталоге WEB-INF\classes, а файл AdBanner.html, подобно другим файлам
HTML, находится в каталоге приложения. Другими словами, если имеется
приложение myApp, то файл AdBanner.html располагается в каталоге myApp, а
файл класса сервлета — в каталоге myApp/WEB-IN F/classes. Сервлет представ-
лен в листинге 3.11, а файл HTML — в листинге 3.12.
Л исти н г 3.11 Включение статического содержимого
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class FirstServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
RequestDispatcher rd = request.getRequestDispatcher("/AdBanner.html");
rd.include(request, response);
}
}
Л истин г 3.12 Файл AdBanner.html
<HTML>
<HEAD>
<TITLE>Banner</TITLE>
</HEAD>
<BODY>
<IMG SRC=images/banner.jpg>
</BODY>
</HTML>
Включение другого сервлета
Вторым примером является сервлет (FirstServlet), который включает в себя
другой сервлет (SecondServlet). Второй сервлет посылает параметр включен-
ного запроса пользователю. FirstServlet представлен в листинге 3.13, а
SecondServlet — в листинге 3.14. Выходные данные показаны на рис. 3.17.
Глава 3
Листинг 3.13 FirstServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FirstServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>”);
out.println("<HEAD>");
out.println("<TITLE>Included Request Parameters</TITLE>");
out.println("</HEAD>");
out.println(”<BODY>”);
out.println("<B>Included Request Parameters</B><BR>”);
RequestDispatcher rd =
request.getRequestDispatcher("/servlet/SecondServlet?name=budi");
rd.include(request, response);
out.println("</BODY>");
out.println(r,</HTML>4’); ' 5
Листинг 3.14 SecondServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class SecondServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
Enumeration enum = request.getAttributeNamesO;
while (enum.hasMoreElementsO) {
String attributeName = (String) enum.nextElementO;
out.println(attributeName + ": +
request.getAttribute(attributeName) + "<BR>");
Создание сервлетных приложений
99
http://te4*K«:eoe(yavAp(>/Mrvht^RrftSvvtot
Included Ra<usst Parameters
javixsenrietindude.seTviet_path: /lerviet/SecondServiet
javax.fervlet include.contexi_paih: ftnyApp
javas.*erwlctinchdc.query_striag; name»budi
javax.ierviet indude. requert_uri /lervlet/SecondServiet
Рис. 3.17. Параметры включенного запроса
Передача управления обработкой
В отличие от метода include метод forward интерфейса Request Dispatcher
может быть вызван сервлетом, только если никакие выходные данные небыли
переданы клиенту. Если в буфере ответа содержатся неотправленные данные,
буфер должен быть очищен прежде, чем будет вызван метод service целевого
сервлета. Если ответ отправляется до вызова метода forward, порождается ис-
ключение HlegalStateException.
Например, сервлет в листинге 3.15 будет выдавать ошибку, так как метод
flush Buffer вызывается до метода forward.
Листинг3.15 Передача управления после очистки буфера
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FirstServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
100
Глава 3
Листинг 3.15 Продолжение
out.println("<HEAD>");
out.println("<TITLE>Included Request Parameters</TITLE>”);
out.println("</HEAD>”);
out.println(”<B0DY>");
out.println("<B>Included Request Parameters</B><BR>");
response.flushBuffer();
RequestDispatcher rd =
request. getRequestDispatcher( ’’/servlet/SecondServlet”);
rd.forward(request, response);
out.println(”asdfaf</BODY>”);
out.println(”</HTML>");
}
}
Метод forward может также использоваться для пересылки запроса стати-
ческому содержимому. И вэтом случае метод flush Buffer не должен вызываться
заранее.
Метод forward является хорошей заменой методу sendRedirect интерфейса
HttpServletResponse. Напомним, что при использовании sendRedirect осуществ-
ляется взаимодействие с клиентом. Если пользователь перенаправляется на
сервлет или на страницу текущего приложения, можно применять метод
forward; в этом случае не происходит предварительное взаимодействие с брау-
зером, поэтому пользователь получает ответ быстрее.
Изменим сервлет Login, приведенный в листинге 3.6. Вместо метода
sendRedirect, сервлет использует RequestDispatcher для пересылки запроса сер-
влету WelcomeServlet после успешной регистрации. Код модифицированного
сервлета Login показан в листинге 3.16, а код сервлета WelcomeServlet — в лис-
тинге 3.17.
Листинг 3.16 Сервлет LoginServlet
import javax.servlet.*;
import javax.servlet.http.*:
import java.io.*;
import java.util.*;
public class LoginServlet extends HttpServlet {
private void sendLoginForm(HttpServletResponse response,
boolean withErrorMessage)
throws ServletException, lOException {
response.setContentType(”text/html”);
Создание сервлетных приложений
101
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out. println( 4TITLE>Login</TITLE>");
out.println(”</HEAD>”);
out. println( ’’<B0DY>”);
if (withErrorMessage)
out.println("Login failed. Please try again.<BR>”);
out.println(’’<BR>'’);
out.println("<BR>Please enter your user name and password.’’);
out. println( XBRXFORM METH0D=P0ST>");
out.println(’’<BR>User Name: <INPUT TYPE=TEXT NAME=userName>");
out.println(’'<BR>Password: <INPUT TYPE=PASSWORD NAME=password>");
out.println(”<BR><INPUT TYPE=SUBMIT VALUE=Submit>’’);
out.println("</F0RM>");
out. println( ’’</B0DY>”);
out.println("</HTML>”);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
sendLoginForm(response, false);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter("userName”);
String password = request.getParameter(”password");
if (userName!=null && password!=null &&
userName.equals(”jamesb") && password.equals(”007”)) {
RequestDispatcher rd =
request. getRequestDispatcher( ’’WelcomeServlet");
rd.forward(request, response);
}
else {
sendLoginForm(response, true);
102
Глава 3
Листинг 3.17 СервлетWelcomeServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class WelcomeServlet extends HttpServlet {
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException
response.setContentType("text/html");
Printwriter out = response.getWriter();
out. println( "<HTM.L>");
out. printing ’’<HEAD>’’);
out.printin("<TITLE>Welcome</TITLE>");
out..println(”</HEAD>"j;
out.println("<BODY>");
out.println("<P>Welcome to the Bulbul’s and Boni’s Web Site.</P>’’);
out.println("</BODY>");
out.printIn("</HTML>");
}
}
Заключение
В этой главе говорилось о применении некоторых классов и интерфейсов па-
кета javax.servlet.http. Были приведены примеры использования HttpServlet,
HttpServletRequest и HttpServletResponse.
Кроме того, мы рассмотрели интерфейс Request Dispatcher, служащий для
включения других ресурсов и для пересылки запроса другому ресурсу.
В следующей главе обсуждается доступ к базе данных с помощью технологии
JDBC (Java Database Connectivity). Это важная тема, так как почти все нетри-
виальные web-приложения используют доступ к базам данных.
Доступ
к базам данных
с помощью JDBC
-t) ольшинство web-приложений используют базу данных. Программирова-
ние доступа к базам данных играет существенную роль в web-разработке. В этой
главе рассказывается о том, как можно сохранять, извлекать и манипулиро-
вать данными. В Java технология, которая обеспечивает доступ и работу с базами
данных, называется JDBC (Java Database Connectivity).
JDBC имеет две части: базовый интерфейс прикладного программирова-
ния (JDBC Core API) и дополнительный пакет API JDBC (JDBC Optional
Package API). JDBC Core API является основной частью JDBC, и когда речь
4 ет о JDBC, как правило, имеется в виду эта часть.
JDBC Core API является частью Java 2 Standard Edition (J2SE) и представляется
в форме классов и интерфейсов пакета java.sql. Во время создания этой книги
самой последней была версия 3.0. Члены пакетаjava.sqt используются прежде
всего для основного программирования баз данных.
JDBC Optional Package API определяется в пакете javax.sql, текущей версией
является 2.0. Пакетjavax.sql поддерживает пулы соединений, распределенные
транзакции, наборы строк и т.д. Пулы соединений являются наиболее важным
при программировании сервлетов. Этот пакет можно загрузить по адресу http:/
/java.sun.com/products/jdbc/download.html.
Полное обсуждение объектной модели JDBC требует отдельной книги. Эта
г гва начинается с обзора объектной модели пакета java.sql, но не рассмат-
ривает JDBC подробно. Некоторые из свойств JDBC даже не требуются для
104
Глава 4
web-программирования. Например, JDBC поддерживает курсоры, которые
могут откатываться назад, но использование курсора этого типа в сервлете не-
приемлемо по соображениям эффективности. Дополнительную информацию
о JDBC можно найти на web-сайте JDBC: http://java.sun.com/products/jdbc/
indec.html.
В большинстве примеров этой главы используются интерфейсы и классы
пакета java.sql. Изучения этого пакета достаточно для создания поддерживаю-
щих базы данных сервлетов. Предполагается, что читатель знаком с языком
SQL. В завершение главы предлагается небольшой пример программирова-
ния базы данных в сервлетах. (Специалист в области JDBC может пропустить
этот раздел.)
Пакет java.sql
Пакет java.sql предоставляет API для доступа и обработки данных в источнике
данных. Наиболее важными членами пакета java.sql являются:
• Класс DriverManager
• Интерфейс Driver
• Интерфейс Connection
• Интерфейс Statement
• Интерфейс ResultSet
• Интерфейс PreparedStatement
• Интерфейс ResultSetMetaData
Каждый из этих объектов обсуждается ниже.
Класс DriverManager
Класс DriverManager предоставляет статические методы для управления драй-
верами JDBC. Каждый драйвер JDBC, который будет применяться, необходимо
зарегистрировать в DriverManager. Драйвер JDBC базы данных, с которой будет
устанавливаться соединение, поставляется либо производителем базы данных,
либо независимым поставщиком. Для разных серверов баз данных используют-
ся различные драйверы JDBC. Например, драйвер JDBC для Microsoft SQL Server
отличается от драйвера, применяемого для доступа к базе данных Oracle.
Чтобы загрузить драйвер JDBC с сервлета или страницы JSP, нужно скопи-
ровать файл драйвера JDBC для определенного сервера базы данных (обычно
файл .jar) в подкаталог WEB-INF\lib каталога приложения. Необходимо со-
здать каталог lib, если он не существует, а затем использовать следующий код:
Доступ к базам данных с помощью JDBC
105
try {
Class.forName("JDBC.driver");
}
catch (ClassNotFoundException e) {
// драйвер не найден
}
В этом случае JDBC.driver является полностью квалифицированным именем
класса драйвера JDBC. Это имя можно найти в документации, сопровождаю-
щей драйвер JDBC.
Самым важным методом класса DriverManager является getConnection, ко-
торый возвращает объект java.sql.Connection. Этот метод имеет три перегружае-
мых версии, которые имеют следующие сигнатуры:
public static Connection
public static Connection
public static Connection
password)
getConnection(String
getConnection(St ring
getConhection(St ring
url)
url, Properties info)
url, String user, String
Интерфейс Driver
Интерфейс Driver реализуется каждым классом драйвера JDBC. Сам класс
драйвера загружается и регистрируется в DriverManager, a DriverManager может
управлять несколькими драйверами для любого заданного запроса соединения.
В случае регистрации нескольких драйверов DriverManager будет по очереди зап-
рашивать каждый драйвер при попытке соединиться с целевым URL.
Интерфейс Connection
Интерфейс Connection представляет соединение с базой данных. Экземп-
ляр интерфейса Connection получают с помощью метода getConnection класса
DriverManager.
close
Метод close немедленно закрывает и освобождает объект Connection. В
результате не приходится ждать, когда он будет освобожден автоматически.
Сигнатура метода имеет следующий вид:
public void close() throws SQLException
isClosed
Этот метод используется для проверки того, что объект Connection закрыт.
Сигнатура этого метода:
public boolean isClosedO throws SQLException
Т06
Глава 4
createStatement
Метод createStatement служит для создания объекта Statement с целью от-
правки базе данных операторов SQL. Если один и тот же оператор SQL выпол-
няется несколько раз, лучше использовать объект PreparedStatement.
Этот метод имеет две перегружаемые версии с сигнатурами:
public Statement createStatement() throws SQLException
public Statement createStatement
(int resultSetType,
int resultSetConcurrency) throws SQLException
prepareStatement
Метод prepareStatement используется для создания PreparedStatement.
Его сигнатура имеет вид:
public PreparedStatement prepareStatement() throws SQLException
getAutoCommit
Метод getAutoCommit возвращает булево значение, определяющее текущее
состояние автозавершения. Сигнатура этого метода имеет вид:
public boolean getAutoCommitO throws SQLException
Этот метод возвращает true, если автозавершение включено, и false в про-
тивном случае. По умолчанию автозавершение включено.
setAutoCommit
Метод setAutoCommit задает состояние автозавершения объекта Connection.
Его сигнатура имеет вид:
public void setAutoCommit(boolean autocommit) throws SQLException
commit
Метод commit используется для завершения транзакции. Сигнатура этого
метода:
public void commitO throws SQLException
rollback
Метод rollback используется для отката транзакции. Его сигнатура имеет вид:
public void rollback() throws SQLException
Доступ к базам данных с помощью JDBC
107
Интерфейс Statement
Методы интерфейса Statement используются для выполнения оператора SQL
и получения результатов, которые он генерирует. Двумя наиболее важными
методами этого интерфейса являются executeQuery и executeUpdate.
executeQuery
Метод executeQuery выполняет оператор SQL, который возвращает один
объект ResultSet. Его сигнатура имеет вид:
public ResultSet executeQuery(String sql) throws SQLException
executeUpdate
Метод executeUpdate выполняет SQL-операторы insert, update и delete. Ме-
тод возвращает ряд записей, измененных при выполнении оператора SQL, и
имеет следующую сигнатуру:
public int executellpdate(String sql)
Интерфейс ResultSet
Интерфейс ResultSet представляет в виде таблицы множество результатов,
получаемых из базы данных. Объект ResultSet поддерживает курсор, указыва-
ющий на его текущую строку данных. Изначально курсор позиционируется
перед первой строкой. Рассмотрим некоторые методы интерфейса ResultSet.
isFirst
Метод isFirst сообщает, указывает ли курсор на первую запись в ResultSet.
Он имеет сигнатуру:
public boolean isFirstO throws SQLException
isLast
Метод isLast сообщает, указывает ли курсор на последнюю запись в ResultSet.
Он имеет сигнатуру:
public boolean isLastO throws SQLException
next
Метод next перемещает курсор на следующую запись, возвращая true, если
текущая строка является действительной, и false, если больше нет записей в
объекте ResultSet. Метод имеет сигнатуру:
public boolean next() throws SQLException
108
Глава 4
getMetaData
Метод getMetaData возвращает объект ResultSetMetaData, представляющий
метаданные объекта ResultSet. Сигнатура метода:
public ResultSetMetaData getMetaData() throws SQLException
Дополнительно можно использовать методы getXXX для получения значе-
ния определенного столбца в строке, указанной курсором. В этом случае XXX
представляет тип данных, возвращаемый методом по определенному индексу,
и каждый метод getXXX принимает значение индекса столбца в ResultSet. Ин-
декс столбца 1 указывает на первый столбец. Сигнатура метода:
public XXX getXXX(int columnindex) throws SQLException
Например, метод getString имеет следующую сигнатуру и возвращает ука-
занную ячейку как String:
public String getString(int columnindex) throws SQLException
Интерфейс PreparedStatement
Доступ к базам данных с помощью JDBC
109
Четыре шага для получения доступа
к базе данных
Выясним, что требуется для получения доступа к базе данных и для работы с
данными.
Прежде чем можно будет манипулировать данными в базе данных, необхо-
димо установить соединение с сервером этой базы данных. После создания
соединения можно будет общаться с сервером базы данных: посылать запросы
SQL для извлечения данных из таблицы, для обновления записей, вставки но-
вых записей или удаления данных, которые больше не нужны. Кроме того,
можно вызывать хранимые процедуры, создавать таблицы и т. п.
Доступ к базе данных с помощью JDBC можно представить в виде четырех
шагов:
1. Загрузка драйвера базы данных JDBC.
2. Создание соединения.
3. Создание оператора.
4. Создание результирующего набора, если ожидается, что сервер базы дан-
ных должен прислать некоторые данные.
Обсудим каждый из этих шагов.
Шаг 1. Загрузка драйвера базы данных JDBC
Серверы баз данных имеют свои собственные «языки» для коммуникации.
Это означает, что для общения с сервером базы данных необходимо понимать
его язык. К счастью, существуют «трансляторы», которые могут обеспечить
взаимодействие кода Java с серверами баз данных. Эти трансляторы имеют
форму драйверов JDBC. Другими словами, если требуется получить доступ к
определенной базе данных, необходим драйвер JDBC для этой базы данных.
Драйверы JDBC доступны сегодня для большинства популярных баз дан-
ных, в частности для Oracle, Sybase, DB2, Microsoft SQL Server, MySQL. Дос-
тупны также драйверы ODBC (Open Database Connectivity), т. e. при отсутствии
драйвера JDBC для определенной базы данных с ней можно соединиться с
помощью ODBC.
Драйверы JDBC имеют разную цену и качество. Есть бесплатные драйве-
ры, и есть дорогие. Одни драйверы медленные, другие очень быстрые. Список
драйверов JDBC можно найти на http://industry.java.sun.com/products/jdbc/
drivers.
110
Глава 4
Типы драйверов
В зависимости от архитектуры драйверы JDBC делятся на четыре типа: Туре 1,
Туре 2, Туре 3, Туре 4.
Туре 1 самый медленный и используется, только если нет другой альтерна-
тивы. Драйверы Туре 1 предоставляют доступ к драйверам ODBC и называют-
ся также драйверами JDBC-ODBC Bridge (мост JDBC-ODBC). Этот тип драй-
вера включен в дистрибутив J2SE и J2EE. Первоначально он играл важную роль.
Почему мост? В момент зарождения JDBC миром соединения с базами дан-
ных уже правил ODBC. Компания Sun выбрала хорошую стратегию, позволив
программистам Java получать доступ к любому серверу базы данных, который
доступен через ODBC.
Драйверы Туре 1 не используются в производственных системах, кроме тех
случаев, когда для базы данных не существует других драйверов. Однако для
быстрого прототипирования эти драйверы вполне приемлемы. Например, если
необходимо проверить концепцию и в наличии имеется только база данных
Microsoft Access, JDBC-ODBC Bridge может сразу предоставить к ней доступ.
Отметим, что на компьютере, который используется для соединения с ба-
зой данных, необходимо задать имя источника данных ODBC (DSN, Data
Source Name). Некоторый собственный код ODBC и во многих случаях соб-
ственный код клиента базы данных должны быть загружены на каждой машине,
которая использует этот тип драйвера. Следовательно, подобные драйверы
обычно применяются, когда автоматическая установка и загрузка Java-прило-
жения не являются важными.
I Настройка имени источника данных ODBC
Чтобы настроить имя источника данных ODBC:
1. Откройте в Панели управления диалоговое окно ODBC Data Source
Administration (Управления источником данных ODBC).
2. Перейдите на вкладку System DSN.
3. Нажмите кнопку Add.
4. Выберите имя драйвера в списке драйверов ODBC.
5. В появившемся диалоговом окне Setup введите необходимую информацию.
6. Нажмите ОК, чтобы закрыть диалоговое окно.
Драйверы Туре 2 написаны частично на Java и частично на собственном API,
чтобы преобразовывать вызовы JDBC в вызовы клиентского API для Oracle,
Sybase, Informix, DB2 или другой СУБД. Отметим, что подобно драйверу-мосту
этот стиль драйвера требует, чтобы некий двоичный код был загружен на каждой
клиентской машине.
Доступ к базам данных с помощью JDBC
111
Драйверы Туре 3 используют сетевой протокол для преобразования вызовов
API JDBC в независимый от СУБД сетевой протокол, который затем трансли-
руется сервером в протокол СУБД. Это промежуточное между сетью и сервером
программное обеспечение способно соединить всех своих клиентов, основанных
на технологии Java, с множеством различных баз данных. Конкретный используе-
мый протокол зависит от поставщика. Обычно это наиболее гибкая альтернатива
API JDBC, и, скорее всего, поставщики этого решения будут предоставлять про-
дукты, предназначенные для работы в интранет. Чтобы эти продукты обеспечивали
также доступ в Интернет, они должны поддерживать дополнительныетребования
по безопасности, доступ через брандмауэры и т. д. Некоторые поставщики
добавляют драйверы, основанные на технологии JDBC, к своим продуктам про-
межуточного программного обеспечения баз данных.
Драйверы Туре4 написаны только наязыке Java. Они применяют собственный
протокол для преобразования вызовов JDBC в сетевой протокол, непосредственно
используемый СУБД. Это позволяет выполнять прямые обращения с клиентской
машины к серверу СУБД и является практичным решением для доступа в интра-
нет. Поскольку многие из этих протоколов являются частными, сами поставщики
баз данных будут основным источником драйверов данного типа.
^Примечанием Список драйверов JDBC для различных баз данных
44 1 содержится на http://industry.java.sun.com/products/jdbc/
drivers.
Установка драйверов JDBC
Так как драйверы JDBC обычно поставляются в файле .jar, то прежде всего
необходимо сообщить Tomcat, где найти драйвер. Скопируйте файл .jar в каталог
WEB-INF\lib каталога приложения. Создайте каталог lib, если он не существует.
СПримечаниеМ 1^РИ использовании моста JDBC-ODBC нет необходимости
устанавливать драйвер, так как он уже включен в пакет
разработчика Java (JDK).
Теперьдрайвер доступен, и можно создатьего экземпляр. Для этого исполь-
зуется статический метод forName класса Class, ему передается полностью ква-
лифицированное имя класса драйвера:
Class.forName(driverName)
Например, для базы данных MySQL наиболее популярным является драйвер
Туре 4, разработанный Марком Мэтьюзом вУниверситете Purdue и доступный
по адресу http://www.worldserver.com/mm.mysql/. Этот драйвер предоставляется
собщей публичной лицензией GNU. Предположим, что драйвер доступен коду
Java, тогда можно загрузить его с помощью кода:
112
Глава 4
Class.forName("org.gjt.mm.mysql.Driver");
Конечно, аргумент driverName будет иным, если используется другой драй-
вер для MySQL.
Для базы данных ODBC код, загружающий драйвер, будет следующим:
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver");
Если для соединения с Microsoft SQL Server используется JDBC-драйвер
FreeTds Type 4, то для загрузки драйвера служит код:
Class.forName("com.internetcds.jdbc.tds.Driver");
Драйвер JDBC должен поставляться с документацией, в которой указыва-
ется используемое имя класса драйвера.
Г Примечание^ Microsoft SQL Server 2000 имеет свой собственный драйвер
4,1 JDBC Туре 4, загружаемый по адресу http://
www.microsoft.com/sql/downloads/2000/jdbc.asp.
Если планируется устанавливать соединение с другими серверами баз дан-
ных, необходимо загрузить драйверы JDBC для каждой базы данных. Напри-
мер, для обеспечения соединения с базой данных ODBC и с базой данных
MySQL загружают оба драйвера с помощью кода:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Class.forName("org.gjt.mm.mysql.Driver");
Шаг 2. Создание соединения
После регистрации драйвера JDBC в DriverManager можно использовать его
для создания соединения с базой данных. В JDBC соединение с базой данных
представляется интерфейсом java.sql.Connection. Для получения объекта
Connection применяется метод getConnection класса DriverManager.
Как упоминалось выше, метод getConnection имеет три перегружаемые версии.
Первая версия получает три аргумента: url, userName и password. Ее сигнатура
выглядит следующим образом:
public static Connection getConnection(String url,
String user, String password)
throws SQLException
Доступ к базам данных с помощью JDBC
113
Аргумент url является самым сложным элементом в этом методе. Его синтаксис:
j dbc:subprotocol:subname
Части subprotocol и subname зависят от используемого сервера базы данных.
Их значения указываются в документации драйвера JDBC.
Если применяется драйвер моста JDBC-ODBC, то subprotocol будет иметь
значение ”odbc”, a subname — DSN (Data Source Name) базы данных. Напри-
мер, если DSN задано как MarketingData, то URL будет:
jdbc:odbc:MarketingData
В случае соединения с базой данных MySQL значением subprotocol будет
«mysql», а для subname должно быть задано имя машины и базы данных. На-
пример, для базы данных с именем Fred следует использовать:
jdbc:mysql///Fred
Если один из загруженных драйверов распознает JDBC URL, переданный в
метод getConnection, этот драйвер попробует установить соединение с базой
данных, определенной в URL. Например, следующий код показывает, как по-
лучить объект Connection для базы данных MySQL с именем Fred на сервере с
именем robusta. Нужно также передать в метод getConnection имя пользовате-
ля «admin» и пароль «secret».
Connection connection =
DriverManager.getConnection("jdbc:mysql///robusta/Fred","admin", "secret”);
Следующий код используется для получения доступа к базе данных ODBC,
DSN которой является MarketingData, с помощью имени пользователя «sa» и
пароля «1945»:
Connection connection =
DriverManager.getConnection("jdbc:odbc:MarketingData", "sa", "1945");
Если соединение не требует регистрации, можно передать null в качестве
аргументов user и password.
Другая версия метода getConnection позволяет передать информацию о
пользователе и пароле в URL. Сигнатура этой версии:
public static Connection getConnection(String url)
throws SQLException
114
Глава 4
Например, можно использовать следующий URL для соединения с базой
данных MySQL с именем Fred на машине robusta, которая требует имя пользо-
вателя «те» и пароль «pwd»:
DriverManager.getConnection (" jdbc:mysql://robusta/Fred?user=me&password=pwd");
А для соединения с базой данных ODBC с именем Marketing Data, которая
доступна без указания имени регистрации и пароля, используется следующее:
Connection connection =
DriverManager.getConnection("jdbc:odbcMarketingData");
Если один из загруженных драй веров распознает JDBC URL, переданный в
метод DriverManager.getConnection, этот драйвер установит соединение с СУБД,
определенной в JDBC URL.
Метод getConnection класса DriverManager необходим для написания про-
грамм Java, которые обращаются к базам данных. Другие методы требуются
только в том случае, когда пишется сам драйвер JDBC.
Объект Connection, получаемый из метода getConnection класса
DriverManager, является открытым соединением, которое можно использовать
для передачи операторов SQL серверу базы данных. Поэтому специальный
метод для открытия Connection не требуется.
Шаг 3. Создание оператора
4
После создания объекта Connection можно передавать любые операторы
SQL, которые понимает сервер базы данных. Уровень понимания различен для
разных серверов. Например, база данных Oracle допускает подзапросы, а сер-
вер MySQL — нет. Успех выполнения оператора SQL зависит также от уровня
полномочий пользователя, который передает запрос базе данных. Если пользо-
ватель имеет полномочие на просмотр всех таблиц базы данных, то он может
передать базе данных оператор SQL SELECT и ожидать возвращения некото-
рых результатов. Если пользователь имеет полномочие на обновление запи-
сей, но не на их удаление, то он сможет только обновить, но не удалить записи.
Необходимо учитывать эти факторы при программировании кода JDBC.
Для передачи оператора SQL необходимо создать объект JDBC, называемый
Statement. Это можно сделать с помощью метода createStatement интерфейса
Connection. Существуют две перезагружаемые версии этого метода, но чаще
всего используется версия без аргументов. Она имеет сигнатуру:
public Statement createStatementO throws SQLException
Доступ к базам данных с помощью JDBC
115
Для создания объекта Statement с помощью открытого объекта Connection
служит код:
// connection - это открытый объект Connection
Statement statement = connection.createStatement();
Затем для обработки данных или структур данных используются методы
интерфейса класса Statement: execute Update и executeQuery. Сигнатуры этих
методов:
public int executeUpdate(String sql) throws SQLException
public ResultSet executeQuery(String sql) throws SQLexception
Методы executeUpdate и executeQuery
Оба метода получают String, содержащую оператор SQL. Оператор SQL
не завершается терминатором оператора СУБД, который может быть
различным в разных СУБД. Например, для указания конца оператора
Oracle использует точку с запятой (;), a Sybase — слово до. Применяемый
драйвер будет автоматически добавлять соответствующий терминатор,
поэтому нет необходимости включать его в код JDBC.
Метод executeUpdate выполняет SQL-операторы INSERT, UPDATE и
DELETE, а также операторы языка определения данных (DDL) для
создания, удаления и изменения таблиц. Этот метод возвращает число
строк для операторов INSERT, UPDATE и DELETE или 0 для операторов
SQL, которые ничего не возвращают.
Метод executeQuery выполняет оператор SQL SELECT, который
возвращает данные. Этот метод возвращает один объект ResultSet (см.
ниже). Объект ResultSet содержит данные, формируемые запросом. Этот
метод никогда не возвращает null.
Например, для создания таблицы Addresses с двумя полями можно исполь-
зовать код:
String sql = "CREATE TABLE Addresses " +
"(FirstName VARCHAR(32), LastName VARCHAR(32)";
statement.executeUpdate(sql);
Чтобы вставить запись в таблицу Addresses, используется код:
String sql = "INSERT INTO Addresses ” +
"VALUES ('Don', 'Bradman')";
statement.executeUpdate(sql);
116
Глава 4
Метод executeQuery применяется в том случае, когда должен быть получен
объект ResultSet.
Г"~Совет~7Д Метод close немедленно освобождает базу данных и
ресурсы JDBC объекта Statement, а не ожидает
автоматического закрытия объекта при сборке мусора.
Освобождение ресурсов по завершении работы с ними
является хорошей практикой — таким образом удается
избежать связывания ресурсов базы данных.
Шаг 4. Создание ResultSet
ResultSet является представлением таблицы базы данных, которое возвра-
щается объектом Statement. Объект ResultSet поддерживает курсор, указывающий
на его текущую строку. Если курсор возвращается в первый раз, он находится в
позиции перед первой строкой. Чтобы обратиться к первой строке ResultSet,
необходимо вызвать метод next() интерфейса ResultSet.
Метод next() перемещает курсор к следующей строке и может вернуть зна-
чение true или false. Он возвращает true, если новая текущая строка является
действительной, и false, если строк больше нет. Обычно этот метод используется
в цикле while для просмотра объекта ResultSet.
Для получения данных из ResultSet используется один из методовgetXXX объек-
та ResultSet: getlnt, getLong и т. д. Например, метод getlnt возвращает значение
указанного столбца в текущей строке объекта ResultSet как int, определенный в
языке программирования Java. Метод getLong извлекает данные ячейки как long,
и т. д. Чаще всего используется метод getString, который возвращает данные ячей-
ки в виде строки. Применение getString является предпочтительным во многих
случаях, так как не нужно беспокоиться о типе поля таблицы в базе данных.
Метод getString, подобно другим методам getXXX, имеет две перегружае-
мые версии, которые позволяют извлекать данные ячейки либо по индексу
столбца, либо по имени столбца. Эти два метода getString имеют сигнатуры:
public String getString(int columnindex) throws SQLException
public String getString(String columnName) throws SQLException
В следующем примере используются методы next() и getString. Этот код из-
влекает столбцы FirstName и LastName из таблицы с именем Users. Затем код
просматривает возвращаемый объект ResultSet и печатает на консоли все име-
на и фамилии в формате «first name:last пате». В данном случае предполагается,
что уже имеется объект Statement с именем statement:
String sql = ’’SELECT FirstName, LastName FROM Users”;
ResultSet resultSet = statement.executeQuery(sql);
Доступ к базам данных с помощью JDBC
117
while (resultSet.next()) {
System.out.println(resultSet.getString(1) + +
resultSet.getSt ring("LastName”));
}
Первый столбец извлекается при передаче его индекса, который равен 1.
Второй столбец получается путем передачи его имени.
Другим важным методом является close, который закрывает объект ResultSet,
если он больше не используется. Метод close освобождает ресурсы базы дан-
ных и JDBC объекта ResultSet, а не ждет, пока это произойдет при автомати-
ческом закрытии. Объект ResultSet закрывается автоматически, когда произ-
водится сборка мусора или когда соответствующий объект Statement
закрывается, выполняется заново или используется для извлечения следую-
щего результата из последовательности результатов. Всегда вызывайте метод
close, чтобы явно закрыть объект ResultSet. В предыдущем примере для заверше-
ния кода необходимо поместить следующий оператор сразу после окончания
доступа к объекту ResultSet:
resultSet.close();
Отметим, что если запрос SQL не создает ни одной строки, объект Statement
вернет объект ResultSet, не содержащий ни строки, ни null.
Обобщение пройденного
Рассмотрим пример, который использует драйвер JDBC Туре 4 РгееТёздля
доступа к таблице Users базы данных Microsoft SQL Server с именем Registration.
Сервер базы данных называется Lampoon, и для регистрации на сервере необ-
ходимо передать имя пользователя «sa» и пароль «s3mlcOnductOr». Оператор
SQLaanpauiHBaeT два столбца: FirstName и LastName. При извлечении ResultSet
оператор будет перебирать его в цикле для печати всех имен и фамилий. Код
представлен в листинге 4.1.
Листинг 4.1 Доступ к базе данных
try {
Class, fоrName("com.internetcds.jdbc.tds.Driver”);
Connection con = DriverManager.getConnection(
"jdbc:freetds:sqlserver://Lampoon/Registration",
"sa", " s3m1c0nduct0r");
System.out.println("got connection");
118
Глава 4
Л исти н г 4.1 Продолжение
Statement s = con.createStatementO;
String sql =
"INSERT INTO UserReg VALUES ('a', ’b’, *12/12/2001', *f’)";
s.executeUpdate(sql);
sql = "SELECT FirstName, LastName FROM Users";
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
System.out. printings.getString(l) + ” " + rs.getString(2));
rs.close();
s.close();
con.close();
}
catch (ClassNotFoundException e1) {
System.out.println(e1.toString());
}
catch (SQLException e2) {
System.out.println(e2.toString());
}
catch (Exception e3) {
System.out.println(e3.toString());
}
На машине автора код возвращает на консоль следующее. Выводимые ре-
зультаты зависят от того, какие данные хранятся в таблице.
Jimmy Barnes
Richard Myers
George Lucas
Сервлет Login, использующий базу данных
В качестве первого примера установки соединения с базой данных в сервлете
рассмотрим сервлет Login. В отличие от сервлета Login в главе 3, в котором
были жестко закодированы имя пользователя и пароль, этот сервлет сравни-
вает введенные пользователем имя и пароль со значениями столбцов UserName
и Password в таблице с именем Users. Эта таблица может иметь и другие столб-
цы, но это нас пока не интересует.
Когда пользователь вводит URL в поле Location или Address и впервые вызы-
вает сервлет, запускается метод сервлета doGet. Сервлет делает то, что и должен
Доступ к базам данных с помощью JDBC
119
делать сервлет регистрации, — предлагает пользователю ввести имя и пароль.
Метод doGet вызывает закрытый метод sendLoginForm, который посылает
пользователю страницу HTML для регистрации:
sendLoginForm(response, false);
Метод sendLoginForm получает два аргумента: объект HttpServletResponse,
который может быть использован методом для отправки выходных данных бра-
узеру, и булево значение. Это булево значение указывает, должно ли вместе с
формой посылаться сообщение об ошибке. Сообщение об ошибке информиру-
ет пользователя о том, что предыдущая попытка регистрации была неудачной.
Конечно, при первом вызове сервлета никаких сообщений об ошибке не посы-
лается; поэтому для второго аргумента метода sendLoginForm, вызываемого из
doGet, задано значение false. MeTOAsendLoginForm приведен влистинге4.2. Стра-
ница Login показана на рис. 4.1.
Please enter your user name and password.
User Name: |Pnnce
> *
A
Рис. 4.1. Сервлет Login, использующий базу данных
Листинг 4.2 Метод sendLoginForm сервлета LoginServlet
private void sendl_oginForm(HttpServletResponse response,
boolean withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
120
Глава 4
Листинг 4.2 Продолжение
out.println(”<HEAD>”);
out. printing"<TITLE>Login</TITLE>");
out.println("</HEAD>");
out.println(”<BODY>”);
out.println("<CENTER>");
if (withErrorMessage)
out.println(”Login failed. Please try again.<BR>");
out. println("<BR>");
out.println("<BR><H2>Login Page</H2>");
out.println("<BR>");
out.println(”<BR>Please enter your user name and password.");
out.println("<BR>");
out. println("<BRXF0RM METHOD=POST>");
out ..println ("<4TABLE>");
out.println("<TR>");
out.println("<TD>User Name:</TD>");
out. println(”<TDx!NPUT TYPE=TEXT NAME=userName></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password:</TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out.println(”<INPUT TYPE=SUBMIT VALUE=Loginx/TD>");
out.println("</TR>");
out.println("</TABLE>");
out.println("</FORM>");
out.println("</CENTER>");
out.println("</B0DY>");
out.println("</HTML>");
}
Пользователь может ввести имя пользователя и пароль для входа и нажать
кнопку Submit. После отправки вызывается метод doPost. Прежде всего он по-
лучает userName и password из объекта HttpServletRequest следующим образом:
String userName = request.getParameter("userName");
String password = request.getParameter("password");
Доступ к базам данных с помощью JDBC
121
Затем метод передает userName и password методу login. Этот метод возвра-
щает true, если имя пользователя и пароль правильные; в противном случае
возвращается false. При успешном входе запрос передается другому сервлету
следующим образом:
RequestDispatcher rd =
request.getRequestDispatcher("AnotherServlet");
rd.forward(request, response);
Если вход неверный, метод doPost снова вызывает метод send Login Form, в
этот раз с передачей сообщения об ошибке:
sendLoginForm(response, true);
Метод doPost приведен в листинге 4.3.
Листинг 4.3 Метод doPost сервлета LoginServlet
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (login(userName, password)) {
RequestDispatcher rd =
request.getRequestDispatcher("AnotherServlet");
rd.forward(request, response);
}
else {
sendLoginForm(response, true);
}
}
Здесь имеется часть, которая выполняет аутентификацию: метод login. Пы-
таясь аутентифицировать пользователя, метод ищет запись, поле UserName
которой совпадает с userName, а поле Password — с password. Если такая запись
найдена, регистрация проходит успешно, иначе регистрация не удалась.
Метод login сначала загружает драйвер JDBC следующим образом:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
В этом примере применяется драйвер JDBC-ODBC. Можно использовать
любой другой драйвер, не изменяя остальную часть кода.
722
Глава 4
С помощью драйвера JDBC можно создать объект Connection и вызвать его
метод createStatement для создания объекта Statement:
Connection con =
DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
Отметим, что здесь используется версия метода getConnection, которая по-
лучает в качестве аргумента URL.
Если объект Connection доступен, можно вызвать метод executeQuery, пере-
дав ему следующий оператор SQL:
SELECT userName FROM Users
WHERE UserName=userName
AND Password=password
Чтобы определить, существует ли в объекте ResultSet какая-либо запись,
можно вызвать его метод next(). Этот метод возвращает true, если запись есть,
иначе выдается false. При получении true метод login возвращает true, указывая
на успешную регистрацию. Значение false говорит об обратном. Метод login
представлен в листинге 4.4, и весь код LoginServlet — в листинге 4.5.
Л исти н г 4.4 Метод login сервлета LoginServlet
boolean login(String userName, String password) {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
" WHERE UserName='" + userName + ..... +
" AND Password^" + password + ....;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.close();
s.closeO;
con.close();
return true;
}
Доступ к базам данных с помощью JDBC
123
rs.close();
s.close();
con.close();
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
catch (SQLException e) {
System, out. println(e. toStringO);
}
catch (Exception e) {
System.out.println(e.toString());
}
return false;
}
Листинг 4.5 Сервлет LoginServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendLoginForm(response, false);
}
private void sendLoginForm(HttpServletResponse response,
boolean withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response. getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Login</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
if (withErrorMessage)
out.println("Login failed. Please try again.<BR>");
124
Глава 4
Листинг 4.5 Продолжение
out.println("<BR>");
out.println("<BR><H2>Login Page</H2>");
out.println("<BR>");
out.println("<BR>Please enter your user name and password.");
out.printin(”<BR>”);
out.println("<BR><FORM METHOD=POST>");
out.println("<TABLE>");
out.println("<TR>");
out. println("<TD>llser Name:</TD>");
out. println("<TDX!NPUT TYPE=TEXT NAME=userName></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password:</TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println("</TR>"); •
out.println("<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out.println("<INPUT TYPE=SUBMIT VALUE=Loginx/TD>");
out.println("</TR>");
out.println("</TABLE>");
out.println("</FORM>");
out. printlnC’</CENTER>”);
out.println("</BODY>");
out.println("</HTML>");
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (login(userName, password)) {
RequestDispatcher rd =
request.getRequestDispatcher("AnotherServlet");
rd.forward(request, response);
}
else {
sendl_oginForm( response, true);
}
}
boolean login(String userName, String password) {
Доступ к базам данных с помощью JDBC
125
try {
Class.forName("sun.j dbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatement();
String sql = "SELECT UserName FROM Users" +
WHERE UserName='" + userName + .. +
AND Password=”' + password + ..;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.closeO;
s.close();
con.close();
z return true;
}
rs.closeO;
s.close();
con.close();
}
catch (ClassNotFoundException e) {
System.out.println(e.toSt ri ng());
}
catch (SQLException e) {
System, out. println(e.toStringO);
}
catch (Exception e) {
System, out. println(e. toStringO);
}
return false;
}
}
Фактор одиночной кавычки
Сервлет LoginServlet, приведенный выше, работает, но не самым лучшим об-
разом. Сервлет передает имя пользователя и пароль методу login, и оператор
SQL создается «на лету». Например, для имени пользователя «jefF» и пароля
«java» оператор SQL будет следующим:
SELECT UserName
FROM Users
WHERE UserName=’jeff'
AND Password^'java’
126
ГЛав а 4
Значения имени и пароля заключаются водиночные кавычки: 'jeff и ’java’. Имя
пользователя может быть каким угодно, в частности оно может содержать апос-
троф, например о’соппог. Это справедливо и для паролей. Если именем пользо-
вателя является «о’соппог», а паролем — «java», то оператор SQL принимает вид:
SELECT UserName
FROM Users
WHERE UserName='o’connor'
AND Password='java’
Процессор базы данных встретит символ одиночной кавычки после о в
«о’соппог», решит, что это конецзначения UserName, и будетожидать после него
пробел. Вместо этого процессор обнаружит с, что внесет путаницу в работу сер-
вера базы данных. Будет порождена ошибка.
Означает ли это, что пользователям не разрешается вводить имя, содержащее
апостроф? К счастью, можно обойти эту проблему. Если строковое значение в
операторе SQL содержит апостроф, нужно перед этим апострофом поставить еще
один апостроф, что приведет к следующему оператору SQL:
SELECT UserName
FROM Users
WHERE UserName='o’’connor’
AND Password=’java’
Теперь все работает правильно. Сервлет LoginServlet необходимо исправить:
все значения, вводимые пользователем, должны передаваться методу с име-
нем fixSQLFieldValue, который принимает в качестве параметра String. Этот
метод заменяет каждое вхождение символа одиночной кавычки в String на два
символа одиночной кавычки. fixSQLFieldValue получает String, которая может
содержать символ одиночной кавычки, и удваивает каждое его вхождение.
Метод fixSQLFieldValue использует StringBuffer с начальной емкостью в 1.1
раза больше длины аргумента, передаваемого в объект String. Выбор этой вели-
чины основывается на оценке, что получающийся оператор SQL будет больше
максимум на 10%:
StringBuffer fixedValue = new StringBuffer((int) (length* 1,1).);
Оператор проверяет каждый символ String в цикле for. Если символ являет-
ся одиночной кавычкой, то в StringBuffer помещаются два символа одиночной
кавычки; иначе в StringBuffer помещается сам символ:
for (int i=0; Klength; i++) {
char c = value.charAt(i);
Доступ к базам данных с помощью JDBC
127
if (с==’\’’)
fixedValue.append(......);
else
fixedValue.append(c);
}
Метод fixSQLFieldValue приведен в листинге 4.6.
Листинг4.6 МетодfixSQLFieldValue
public static String fixSqlFieldValue(String value) {
if (valuer=null)
return null;
int length = value.length();
StringBuffer fixedValue =
new StringBuffer((int) (length * 1.1));
for (int i=0; Klength; i++) {
char c = value.charAt(i);
if (c==’\’’)
fixedValue.append(....);
else
fixedValue.append(c);
}
return fixedValue.toStringO;
}
Метод login в листинге 4.5 и листинге 4.6 должен передавать значения
userName и password перед созданием оператора SQL. При включении мето-
да fixSqlFieldValue в сервлет LoginServlet, код, который формирует оператор
SQL, должен быть модифицирован следующим образом:
String sql = "SELECT UserName FROM Users" +
” WHERE UserName='" + fixSqlFieldValue(userName) + .... +
" AND Password^'" + fixSqlFieldValue(password) + ....;
Очевидно, что метод fixSqlFieldValue должен вызываться всякий раз при
составлении оператора SQL, который содержит значение, вводимое пользова-
телем. Этот метод был включен в класс StringUtil, который входит в пакет
com.brainysoftware.java, содержащийся на сайте www.lory-press.ru. Это избав-
ляет от необходимости писать один и тот же код во всех классах, которые в нем
нуждаются. Метод статический, и его можно вызывать следующим образом:
import com.brainysoftware.java.StringUtil
StringUtil.fixSqlFieldValue(st ring)
128
Глава 4
Чтобы использовать этот класс, необходимо создать структуру каталогов в
каталоге /WEB-INF/classes каталога приложения; т. е. нужно создать каталог с
именем сот в каталоге classes, каталог brainysoftware в каталоге сот, каталог
java в каталоге brainysoftware и скопировать туда StringUtil.java.
Вставка данных в таблицу с помощью
RegistrationServlet
Вставка записи в таблицу является распространенной задачей базы данных. В
качестве примера приведем сервлете именем RegistrationServlet, который по-
лучает ввод пользователя для вставки записи в таблицу Users. Столбцы табли-
цы Users описаны в таблице 4.1.
Таблица 4.1. Таблица Users
Имя столбца Тип столбца
Id Numeric
FirstName String
LastName String
UserName String
Password String
Описание
Номер, который служит в качестве
первичного ключа и автоматически
увеличивается базой данных.
Имя пользователя
Фамилия пользователя
Имя пользователя, которое он
использует при входе в систему.
Дубликаты не допускаются.
Пароль пользователя для входа в систему.
Чтобы вставить запись, содержащую пользовательские данные, необходи-
мо извлечь значения, введенные пользователем, и создать оператор SQL сле-
дующего вида:
INSERT INTO Users
(FirstName, LastName, UserName, Password)
VALUES
(firstName, lastName, userName, password)
Столбец Id не включен, так как он изменяется автоматически. Новый уни-
кальный номер будет генерироваться сервером базы данных автоматически для
столбца Id при вставке новой записи в таблицу. Отметим также, что значение
столбца UserName должно быть уникальным. Никакие два пользователя не
могут иметь одно и то же имя.
Доступ к базам данных с помощью JDBC
129
При первом вызове RegistrationServlet выводит форму HTML с четырьмя
текстовыми полями, как показано на рис. 4.2.
Пользователь может ввести необходимые данные: имя, фамилию и само-
стоятельно выбранное имя пользователя и пароль. Имя пользователя должно
быть уникальным. Если ввести уже применявшееся имя пользователя, сервлет
выдаст сообщение об ошибке и снова выведет ту же форму с введенными ра-
нее значениями (см. рис. 4.3).
Если сервлет встречает неожиданную ошибку, он также выдает сообщение
об ошибке и сохраняет введенные ранее значения.
При успешной регистрации сервлет выведет сообщение «Succesfully added
one user» (Успешно добавлен один пользователь) и предоставит чистую форму.
Пользователь сможет ввести другие пользовательские данные, если захочет.
В сервлете RegistrationServlet метод для вывода формы называется
send Registration Form. Этот метод получает три аргумента: объект
HttpServletRequest, объект HttpServletResponse и логическое значение. Логи-
ческое значение указывает, будет ли выводиться на экран введенное ранее зна-
чение, если таковое имеется. Метод sendRegistrationForm представлен в лис-
тинге 4.7. Отметим, что для отображения значения в поле ввода HTML
необходимо вызвать метод encodeHtmlTag (который является частью класса
com.brainysoftware.java.StringUtil) на тот случай, если значение содержит спе-
циальные символы HTML (см. главу 3). Поэтому сервлет RegistrationServlet
импортирует класс com.brainysoftware.java.StringUtil.
Registration Page
Please eater the user details.
First Name f
LastName [*
User Name [
Password |
Рис. 4.2. RegistrationServlet
130
Глава 4
Рис. 4.3. Отказ в регистрации по причине ввода уже применявшегося
имени пользователя
Листинг 4.7 Метод sendRegistrationForm
private void sendRegistrationForm(HttpServletRequest request,
HttpServletResponse response, boolean displayPreviousValues)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out.println(”<BR><H2>Registration Page</H2>");
out.println("<BR>Please enter the user details.");
out.println("<BR>");
out. println("<BR><FORM METHOD=POST>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>First Name</TD>");
out.print("CTDXINPUT TYPE=TEXT Name=firstName");
if (displayPreviousValues)
out.print(" VALUE=\”” +
StringUtil.encodeHtmlTag(firstName) + "\"");
out.println("></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Last Name</TD>");
out.print(XTDXINPUT TYPE=TEXT Name=lastName");
Доступ к базам данных с помощью JDBC
131
if (displayPreviousValues)
out.print(" VALUE=\"" + St ringUtil.encodeHtmlTag(lastName) + "\"");
out. println("></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>User Name</TD>");
out.print("<TD><INPUT TYPE=TEXT Name=userName");
if (displayPreviousValues)
out.print(" VALUE=\"’’ + StringUtil.encodeHtmlTag(userName) + "\"");
out.println( "X/TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password</TD>");
out.print("<TDxiNPUT TYPE=PASSWORD Name=password");
if (displayPreviousValues)
out.print(" VALUE=\"" + StringUtil.encodeHtmlTag(password) + "\"");
out.println("></TD>");
out.println(”</TR>");
out.println("<TR>");
out.println("<TD><INPUT TYPE=RESET></TD>");
out. println( "<TD><INPUT TYPE=SUBMIT></TD>");
out.println("</TR>");
out. println("</TABLE>");
out.println("</FORM>");
out.println("<BR>");
out.println("<BR>");
}
Однако в отличие от формы в LoginServlet форма регистрации не содержит
тег <HEAD>, заголовочный раздел HTML, открывающий тег <BODY>, а также
закрывающие теги </BODY> и </HTML>. Заголовки страницы могут посы-
латься браузеру с помощью метода sendPageHeader, код которого представлен
в листинге 4.8.
Листинг 4.8 Метод sendPageHeader
private void sendPageHeader(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
132
Глава 4
Листинг 4.8 Продолжение
out.println(”<HTML>");
out. println("<HEAD>");
out.println("<TITLE>Registration Page</TITLE>");
out.p ri nt1n("</HEAD>");
out.printin("<BODY>”);
out.println("<CENTER>");
}
Завершение страницы может посылаться с помощью вызова метода
send Page Footer (см. листинг 4.9).
Листинг 4.9 Метод sendPageFooter
private void sendPageFooter(HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out.println("</CENTER>");
out. println("</BODY>");
out.println("</HTML>");
}
При первом вызове сервлет использует свой метод doGet для отправки формы
регистрации и всех необходимых тегов HTML браузеру. Метод doGet пред-
ставлен в листинге 4.10.
Листинг 4.10 Метод doGet
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
sendPageHeader(response);
sendRegistrationForm(request, response, false);
sendPageFooter(response);
}
Когда пользователь отправляет форму, вызывается метод doPost. Метод
doPost извлекает значения из users:
firstName = request.getParameter("firstName");
lastName = request. getParameter( "lastName’');
userName = request.getParameter("userName");
password = request.getParameter("password");
Доступ к базам данных с помощью JDBC
133
После открытия объекта Connection и создания объекта Statement методу
необходимо определить, не применялось ли уже имя пользователя. Это дела-
ется с помощью попытки извлечь запись, значение поля UserName которой
равно userName:
Connection con =
DriverManager.getConnection(”jdbc:odbc:JavaWeb");
Statement s = con.createStatement();
String sql = ’’SELECT UserName FROM Users" +
WHERE userName="' +
StringUtil.fixSQLFieldValue(userName) +
ResultSet rs = s.executeQuery(sql);
Если объект ResultSet, возвращаемый методом executeQuery интерфейса
Statement, содержит запись, его метод next передает true, указывая, что имя
пользователя уже применялось; иначе возвращается false.
Если имя пользователя применялось, строке message присваивается сообще-
ние «The user name... has been taken» и устанавливается флагошибки. Если же все
в порядке, оперативно создается оператор SQL и вызывается метод executeUpdate
объекта Statement для вставки новой записи в таблицу Users (см. рис. 4.4).
Successfully added ом user.
Registration Page
Please enter the user details. \
Рис. 4.4 Успешная регистрация
if (rs.nextO) {
rs.closeO;
message = "The user name <B>" +
134
Глава 4
StringUtil.encodeHtmlTag(userName) +
”</B> has been taken. Please select another name.";
error = true;
}
else {
rs.closeO;
sql = "INSERT INTO Users" +
(FirstName, LastName, UserName, Password" +
” VALUES" +
(’" + StringUtil.fixSQLFieldValue(firstName) + +
+ StringUtil.fixSQLFieldValue(lastName) + +
+ StringUtil.fixSQLFieldValue(userName) + +
+ StringUtil.fixSQLFieldValue(password) + "')";
int i = s.executeUpdate(sql);
if (i == D {
message = "Successfully added one user.";
}
}
Последняя часть метода doPost проверяет, не имеет ли сообщение значение
null и не был ли установлен флагошибки. Если сообщение не null, оно посыла-
ется в браузер:
if (message!=null) {
Printwriter out = response.getWriter();
out.println("<B>" + message + "</B><BR>");
out.println("<HR><BR>");
}
В случае ошибки форма регистрации посылается с последним аргументом,
равным true. Это заставляет send Registration Form выводить введенные ранее
значения. Иначе метод send Registration Form получает в последнем аргументе
значение false:
if (error==true)
sendRegistrationForm(request, response, true);
else
sendRegistrationForm(request, response, flase);
sendPageFooter(response);
}
Метод doPost представлен в листинге 4.11.
Доступ к базам данных с помощью JDBC
135
Листинг 4.11 Метод doPost
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
firstName = request.getParameter(”firstName");
lastName = request.getParameter("lastName");
userName = request.getParameter("userName");
password = request.getParameter("password");
boolean error = false;
String message = null;
try {
Connection con = riverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
" WHERE userName=’" + StringUtil.fixSQLFieldValue(userName) + ..;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.close();
message = "The user name <B>" + StringUtil.encodeHtmlTag(userName) +
”</B> has been taken. Please select another name.";
error = true;
}
else {
rs.close();
sql = "INSERT INTO Users" +
" (FirstName, LastName, UserName, Password)" +
" VALUES" +
" (”’ + StringUtil.fixSQLFieldValue(firstName) + +
.... + StringUtil.fixSQLFieldValue(lastName) + +
.... + StringUtil.fixSQLFieldValue(userName) + +
.... + StringUtil.fixSQLFieldValue(password) + "’)”;
int i = s.executeUpdate(sql);
if (i==1) {
message = "Successfully added one user.";
}
}
s.closeO;
con.close(); 4
136
Глава 4
Л истинг 4.11 Продолжение
catch (SQLException е) {
message = "Error.” + е. toStringO;
error = true;
}
catch (Exception e) {
message = "Error.” + e. toStringO;
error = true;
}
if (message!=null) {
Printwriter out = response.getWriter();
out.println("<B>" + message + "</B><BR>");
out.println("<HR><BR>");
}
if (error==true)
sendRegistrationForm(request, response, true);
else
sendRegistrationForm(request, response, false);
sendPageFooter(response);
Можно заметить, что код, который загружает драйвер JDBC, удален из ме-
тода doPost. Он был перемещен в метод init сервлета. Драйвер JDBC нужно
загружать только один раз, поэтому правильнее поместить его в метод init, ко-
торый вызывается лишь однажды в течение жизненного цикла сервлета. Ме-
тод init показан в листинге 4.12.
Л исти н г 4.12 Метод init сервлета RegistrationServlet
public void init() {
try {
Class.fо rName("sun.jdbc.odbc.JdbcOdbcDrive r");
System.out.println("JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System, out. println(e. toStringO);
}
}
Весь код сервлета RegistrationServlet представлен в листинге 4.13.
Доступ к базам данных с помощью JDBC
137
Л истин г 4.13 RegistrationServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class RegistrationServlet extends HttpServlet {
private String firstName =
private String lastName = "";
private String userName =
private String password =
public void init() {
try {
Class. forName("sun.jdbc.odbc.JdbcOdbcDriver");
System.out.println(”JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
}
/**0бработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
sendPageHeader(response);
sendRegistrationForm(request, response, false);
sendPageFooter(response);
}
/★★Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
firstName = request.getParameter("firstName");
lastName = request.getParameter("lastName");
userName = request.getParameter("userName");
password = request.getParameter("password");
138
Глава 4
Листинг 4.13 Продолжение
boolean error = false;
String message = null;
try {
Connection con =
DriverManager. getConnection( " j dbc: odbc: JavaWeb”);
System.out.println("got connection");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
" WHERE userName=”’ + StringUtil.fixSQLFieldValue(userName) + .;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.closeO;
message = "The user name <B>" +
StringUtil.encodeHtmlTag(userName) +
”</B> has been taken. Please select another name.";
error = true;
}
else {
rs.closeO;
sql = "INSERT INTO Users" +
" (FirstName, LastName, UserName, Password)" +
" VALUES" +
" ('" + StringUtil.fixSQLFieldValue(firstName) + +
.... + StringUtil.fixSQLFieldValue(lastName) + +
+ StringUtil.fixSQLFieldValue(userName) + +
....................................................... + StringUtil. fixSQLFieldValue(password) + ’”)’’;
int i = s.executeUpdate(sql);
if (i==D {
message = "Successfully added one user.";
}
}
s.closeO;
con.closeO;
}
catch (SQLException e) {
message = "Error." + e.toStringO;
error = true;
}
catch (Exception e) {
message = "Error." + e.toStringO;
error = true;
Доступ к базам данных с помощью JDBC
139
}
if (message! =null) {
Printwriter out = response. getWriterO;
out.println("<B>" + message + "</B><BR>");
out.println("<HR><BR>");
}
if (error==true)
sendRegistrationForm(request, response, true);
else
sendRegistrationForm(request, response, false);
sendPageFooter(response);
}
I ★★
* Отправка заголовка страницы HTML, включая название
* и тег <BODY>
* /
private void sendPageHeader(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response. getWriterO;
out.println(”<HTML>”);
out.println("<HEAD>”);
out.println(”<TITLE>Registration Page</TITLE>”);
out.println("</HEAD>");
out.println(”<BODY>”);
out.println("<CENTER>");
}
/* ★
* Отправка завершения страницы HTML, т.е. </BOOY>
* и </HTML>
* /
private void sendPageFooter(HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriterO;
out.println(”</CENTER>”);
out.println(”</B0DY>”);
out.println(”</HTML>");
}
/** Отправка формы, где пользователь может ввести
★ новые пользовательские данные
140
Глава 4
Листинг 4.13 Продолжение
private void sendRegistrationForm(HttpServletRequest request,
HttpServletResponse response, boolean displayPreviousValues)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out.println(”<BR><H2>Registration Page</H2>”);
out.println("<BR>Please enter the user details.”);
out.println(”<BR>”);
out. println( XBRXFORM METHOD=POST>”);
out.println(”<TABLE>”);
out.println(”<TR>");
out.println(”<TD>First Name</TD>”);
out.print(XTDXINPUT TYPE=TEXT Name=firstName”);
if (displayPreviousValues)
out.print(" VALUE=\’”’ + StringUtil.encodeHtmlTag(firstName) + ”\””);
out. println("x/TD>”);
out.println(”</TR>”);
out.println(”<TR>”);
out.println("<TD>Last Name</TD>");
out.print(XTDXINPUT TYPE=TEXT Name=lastName”);
if (displayPreviousValues)
out.print(" VALUE=\”" + StringUtil.encodeHtmlTag(lastName) + ”\’”’);
out. println(”></TD>”);
out.println(”</TR>");
out.println("<TR>”);
out.println(”<TD>User Name</TD>");
out.print("<TD><INPUT TYPE=TEXT Name=userName");
if (displayPreviousValues)
out.print(" VALUE=\"" + StringUtil.encodeHtmlTag(userName) + ”\””);
out. println("x/TD>”);
out.println(”</TR>”);
out.println(”<TR>");
out.println("<TD>Password</TD>”);
out.print("CTDXINPUT TYPE=PASSWORD Name=password”);
if (displayPreviousValues)
out.print(” VALUE=\”” + StringUtil.encodeHtmlTag(password) + ”\’’”);
Доступ к базам данных с помощью JDBC
141
out.р rin11n(”></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD><INPUT TYPE=RESETx/TD>");
out.println("<TD><INPUT TYPE=SUBMITX/TD>");
out.println("</TR>");
out.p rin11n(”</TABLE>");
out.println("</FORM>");
out.println("<BR>");
out.println("<BR>");
}
}
Вывод всех записей на экран
Вывод данных является неизбежным при программировании баз данных. Ка-
кие данные будут выводиться на экран, зависит от приложения. Рассмотрим
задачу вывода и форматирования данных в виде таблицы HTML. Сначала
попробуем вывести все записи таблицы Users, азатем реализуем страницу поиска
(Search Page), которая будет выводить данные выборочно.
Вывести все данные нетрудно. Необходимо только открыть Connection, со-
здать объект Statement и выполнить запрос, чтобы создать объект ResultSet.
Затем все записи перебираются в цикле while. Код представлен влистинге4.14.
Выходные данные этого сервлета показаны на рис. 4.5.
Листинг4.14 Вывод всех записей таблицы Users
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class DataViewerServlet extends HttpServlet {
/** Загрузка драйвера JDBC */
public void init() {
try {
Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver");
System.out.printing'JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System.out.p ri ntlh(e.toSt ring());
}
142
Глава 4
Листинг 4.14 Продолжение
}
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Display All Users</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
out.println("<BR><H2>Displaying All Users</H2>");
out.println("<BR>");
out.println("<BR>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TH>First Name</TH>”);
out.println("<TH>Last Name</TH>");
out.println("<TH>User Name</TH>");
out.println("<TH>Password</TH>");
out.println("</TR>");
String sql = "SELECT FirstName, LastName, UserName, Password" +
" FROM Users";
try {
Connection con =
DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
ResultSet rs = s.executeOuery(sql);
while (rs.nextO) {
out.println("<TR>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(1)) + ”</TD>’’);
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(2)) + "</TD>");
out.println("<TD>" + StringUtil. encodeHtmlTag(rs.getString(3)) + "<Д0>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(4)) + ’’<Д0>’’);
out.println("</TR>");
}
rs.closeO;
Доступ к базам данных с помощью JDBC
143
s.closeO;
con.closeO;
}
catch (SQLException e) {
}
catch (Exception e) {
}
out.println("</TABLE>”);
out.println("</CENTER>”);
out.println("</B0DY>");
out.println("</HTML>");
}
public void doPost(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
doGet(request, response);
}
Рис. 4.5. Вывод всех записей таблицы на экран
Страница поиска
Во многих случаях не требуется выводить все записи, а нужен лишь выбороч-
ный вывод данных. Например, вам необходимо найти сведения о человеке с
144
Глава 4
фамилией Jones, но его имени вы не знаете. Вывод всех пользователей не слиш-
ком поможет, особенно если таблица содержит большое число записей. Страница
будет более полезной, если она позволит пользователю определить ключевое
слово и выведет все записи, соответствующие ключевому слову.
Напишем сервлет Search Servlet, который разрешает пользователю вводить
ключевое слово. Ключевым словом может быть имя, фамилия или даже часть
имени или фамилии. Выходные данные этого сервлета показаны на рис. 4.6.
Hrrt Nim Last Num User Nmm Password
John Southgate jsouthgste <BR>
Samantha Jones tjonei ш
Bridget Jones bjones nodanid
Рис. 4.6. SearchServlet
Основной частью этого сервлета является метод sendSerchResult, который
устанавливает соединение с базой данных, выполняет следующий оператор SQL
и посылает результат браузеру.
SELECT FirstName, LastName, UserName, Password
FROM Users
WHERE FirstName LIKE ’%keyword%'
OR LastName LIKE '%keyword%'
Код приводится в листинге 4.15.
Л исти н г 4.15 Сервлет SerchServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
Доступ к базам данных с помощью JDBC
145
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class SearchServlet extends HttpServlet {
private String keyword =
public void init() {
try {
Class. forName(’’sun. jdbc. odbc. JdbcOdbcDriver’’);
System.out.println("JDBC driver loaded’’);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
}
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
send$earchForm(response);
sendPageFooter(response);
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
keyword = request. getParameter(’’keyword’’);
sendPageHeader(response);
sendSearchForm(response);
sendSearchResult(response);
sendPageFooter(response);
}
void sendSearchResult(HttpServletResponse response)
throws lOException {
Printwriter out = response.getWriter();
try {
Connection con =
D rive rManager. getConnection (” j dbc: odbc: JavaWeb’’);
146
Глава 4
Листинг 4.15 Продолжение
System.out.println(”got connection");
Statement s = con.createStatementO;
out.println("<TABLE>");
out.println("<TR>");
out.println("<TH>First Name</TH>");
out.println(”<TH>Last Name</TH>");
out;p rint1n("<TH>Use r Name</TH>");
out.println("<TH>Password</TH>");
out.println("</TR>");
String sql = "SELECT FirstName, LastName, UserName, Password" +
" FROM Users" +
" WHERE FirstName LIKE ’%" +
StringUtil.fixSqlFieldValue(keyword) + "%’" +
” OR LastName LIKE ’%" + StringUtil.fixSqlFieldValue(keyword) + "%'";
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
out.println("<TR>");
out. print ln("<TD>" + StringUtil.encodeHtmlTag(rs.getString(1)) + "</TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(2)) + "<Л0>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(3)) + "</TD>”);
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(4)) + ”</TD>");
out.println("</TR>"); i
}
s.closeO;
con.closeO;
}
catch (SQLException e) {
}
catch (Exception e) {
}
out.println("</TABLE>");
}
/**
* Отправка заголовка страницы HTML, включая название
* и тег <BODY>
* /
private void sendPageHeader(HttpServletResponse response)
throws ServletException, lOException {
Доступ к базам данных с помощью JDBC
147
response. setContentType( "text/html ’’);
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Displaying Selected Record(s)</TITLE>");
out.println(”</HEAD>");
out.println("<B0DY>");
out.println("<CENTER>");
}
/★ ★
* Отправка завершения страницы HTML, т.е. </BODY>
» и </HTML>
* /
private void sendPageFooter(HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out.println("</CENTER>");
out.println("</BODY>");
out.println("</HTML>");
}
/*★ Отправка формы, где пользователь может ввести
* новые пользовательские данные
* /
private void sendSearchForm(HttpServletResponse response)
throws lOException {
Printwriter out = response.getWriter();
out.println("<BR><H2>Search Form</H2>");
out.println("<BR>Please enter the first name,7" last name or part of any.");
out.println("<BR>");
out. println( "<BRXF0RM METHOD=POST>");
out.print("Name: <INPUT TYPE=TEXT Name=keyword");
out.println(" VALUE=\’’" + StringUtil.encodeHtmlTag(keyword) + ‘V’’);
out.println(">");
out.println("<INPUT TYPE=SUBMIT>");
out.println("</FORM>");
out.println("<BR>");
out.println("<BR>");
148
Глава 4
Сетевая утилита SQL
При работе с базой данных может понадобиться изменить данные в базе
данных для тестирования. Однако при установке соединения с базой данных в
сервлете нередко оказывается, что база данных располагается вне пределов фи-
зического доступа. Часто единственным способом соединения с базой данных
является использование клиентской программы базы данных. Конечно,
клиентскую программу необходимо установить на компьютере, который
применяется для соединения с базой данных. Это неудобно, если требуется
мобильность или если нужно иметь возможность соединения с базой данных
из различных мест. Поскольку база данных доступна д ля сервлета, можно также
сделать ее доступной в сети.
Предлагаемая программа является сетевой утилитой SQL, которая позво-
ляет обрабатывать данные или структуры данных, используя web-браузер. Можно
ввести любой оператор SQL и послать его серверу базы данных для выполне-
ния. Если сервер базы данных вернет ResultSet, утилита выведет его в форме
таблицы. Если сервер базы данных пошлет сообщение об ошибке, оно также
будет выведено в форме. Форма показана на рис. 4.7.
г»
Рис. 4.7. Сетевая утилита SQL
Пользователь может ввести в поле оператор SQL. На рис. 4.8 и 4.9 показано,
как выглядит форма, когда сервер базы данных возвращает ResultSet и когда он
уведомляет пользователя о том, что успешно выполнил оператор SQL, изменив-
ший одну запись.
При первом обращении сервлет запускает метод doGet, который вызывает
sendSqlForm. Этот метод посылает форму SQL браузеру. Метод doGet представлен
в листинге 4.16.
Доступ к базам данных с помощью JDBC
149
Л истин г 4.16 Метод doGet сервлета SQLTootServlet
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
sendSqlForm(request, response);
}
Метод sendSqlForm посылает форму SQL (см. листинг 4.17).
Листинг 4.17 Метод sendSqlForm
private void sendSqlForm(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>”);
out.println("<TITLE>SQL Tool Servlet</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<BR><H2>SQL Tool</H2>");
out.println("<BR>Please type your SQL statement in the following box.");
out.println("<BR>");
out.println("<BR><FORM METHOD=POST>");
out.printIn("<TEXTAREA NAME=sql C0LS=80 R0WS=8>");
String sql = request.getParameter("sql");
// Воспроизведение введенного ранее SQL в TextArea
if (sql’=null)
out.println(sql);
out.println("</TEXTAREA>");
out.println("<BR>");
out.println("<INPUT TYPE=SUBMIT VALUE=Execute>");
out.println("</FORM>");
out.println(”<BR>");
out.println("<HR>");
out.println("<BR>”);
if (sql!=null) {
executeSql(sql.trim(), response);
}
out.println("</BODY>”);
out.println("</HTML>");
150
Глава 4
Метод sendSql Form извлекает значение sql из объекта HttpServletRequest.
Когда этот метод вызывается методом doGet, параметр sql содержит null.
Однако при вызове из метода doPost параметр sql не равен null. В последнем
случае вызывается метод executeSql.
Метод executeSql получает String, содержащую оператор SQL, который нуж-
но выполнить, и объект HttpServletResponse, в который записывается текст,
отправляемый браузеру. Метод executeSql представлен в листинге 4.18.
Рис. 4.8. Утилита SQL с полученным ResultSet
SQL Tool
РЬам^ре your SQL rtttuaothAe ЫийфЬов.
Ш*к*Т INTO Vsers
trirstNam, 1 >TN—, nirw—, Password)
VALUES
('Pridaec', 'Joses', 'SJoses', 'sodaslei*)
Record(i) affected 1
Рис. 4.9. Утилита SQL после вставки новой записи
Доступ к базам данных с помощью JDBC
151
Л исти нг 4.18 Метод executeSql
public void executeSql(String sql, HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
try {
//Class. forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
if (sql.toUpperCase().startsWith("SELECT")) {
out.println("<TABLE BORDER=1>");
ResultSet rs = s.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaDataO;
// Запись заголовков таблицы
int columnCount = rsmd.getColumnCountO;
out.println("<TR>");
for (int i=1; i<=columnCount; i++) {
out.println("<TD><B>" + rsmd.getColumnName(i) + "</B></TD>\n");
}
out.println("</TR>");
while (rs.nextO) {
out.println("<TR>");
for (int i=1; i<=columnCount; i++) {
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(i)) + "<7TD>");
}
out.p ri nt1n("</TR>”);
}
rs.closeO;
out.println("</TABLE>");
}
else {
int i = s.executeUpdate(sql);
out.println("Record(s) affected: + i);
}
s.closeO;
con.closeO;
out.printin("</TABLE>");
}
catch (SQLException e) {
out.println("<B>Error</B>”);
152
Глава 4
Листинг 4.18 Продолжение
— — - , , ь
out.println(”<BR>”);
out. println(e. toStringO);
}
catch (Exception e) {
out.println("<B>Error</B>");
out.println("<BR>");
out. println(e. toStringO);
}
}
Весь код сервлета SQLToolServlet приведен в листинге 4.19.
Листинг 4.19 Сервлет SQLToolServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class SQLToolServlet extends HttpServlet {
/** Загрузка драйвера JDBC */
public void init() {
try {
Class. forName("sun.jdbc.odbc.JdbcOdbcDriver”);
System.out.println("JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System, out. println(e. toStringO);
}
}
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException
{
sendSqlForm(request, response);
}
/** Обработка запроса HTTP Post */
Доступ к базам данных с помощью JDBC
153
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
sendSqlForm(request, response);
}
/** Отправка формы, где пользователь может ввести
* оператор SQL для обработки
★ I
private void sendSqlForm(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response.setContentType(”text/html");
Printwriter out = response.getWriterO;
out. println("<HTML>’');
out. println( ',<HEAD>'’);
out.println(”<TITLE>SQL Tool Servlet</TITLE>”);
out. println( ”</HEAD>’');
out.println("<BODY>");
out.println(”<BR><H2>SQL Tool</H2>’’);
out.println(”<BR>Please type your SQL statement in the following box.’’);
out.println("<BR>”);
out.println(”<BR><FORM METHOD=POST>”);
out.println(”<TEXTAREA NAME=sql C0LS=80 R0WS=8>");
String sql = request.getParameterC'sql");
// Воспроизведение введенного ранее SQL в TextArea
if (sql!=null)
out.println(sql);
out.println(”</TEXTAREA>”);
out. println(”<BR>’’);
out.println("<INPUT TYPE=SUBMIT VALUE=Execute>”);
out. println( ’’</FORM>'’);
out.println(”<BR>");
out.println(”<HR>");
out.println(”<BR>");
if (sql!=null) {
executeSql(sql.t rim(), response);
}
out. println( X/BODYX);
out. println( "</HTML>’’);
}
154
Глава 4
Листинг 4.19 Продолжение
/** Выполнение SQL */
public void executeSql(String sql, HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriterO;
try {
//Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
if (sql.toUpperCase().startsWith("SELECT")) {
out.println("<TABLE BORDER=1>");
ResultSet rs = s.executeQuery(sql);
ResultSetMetaOata rsmd = rs.getMetaDataO;
// Запись заголовков таблицы
int columnCount = rsmd.getColumnCountO;
out.println("<TR>");
for (int i=1; i<=columnCount; i++) {
out.println("<TD><B>" + rsmd.getColumnName(i) + "</B></TD>\n");
}
out.println("</TR>”);
while (rs.nextO) {
out.println("<TR>");
for (int i=1; i<=columnCount; i++) {
out.println("<TD>" +
StringUtil.encodeHtmlTag(rs.getString(i)) + "</TD>” );
}
out.println("</TR>");
}
rs.closeO;
out.println("</TABLE>");
}
else {
int i = s.executeUpdate(sql);
out.println("Record(s) affected: ” + i);
}
s.close();
con.closeO;
out.println(”</TABLE>");
}
Доступ к базам данных с помощью JDBC
155
catch (SQLException е) {
out.println("<B>Error</B>");
out.println("<BR>");
out.println(e.toSt ring());
}
catch (Exception e) {
out.println("<B>Error</B>");
out.println("<BR>");>
out. println(e. toStringO);
}
}
}
Проблема поддержки открытого
соединения
В примерах, приведенных выше, объект Connection закрывался после того, как
сервлет обслуживал запрос. Однако может возникнуть вопрос: если сервлет
вызывается неоднократно и он открывает каждый раз объект Connection с од-
ними и теми же аргументами (url, пользователь и пароль), не будет ли лучше
отставить объект Connection открытым для последующего использования.
Фактически открытие объекта Connection является одной из самых дорогих
операций при программировании базы данных. Возможность сберечь процес-
сорное время, сохранив объект Connection, является весьма привлекательной.
Однако во многих справочных руководствах, включая посвященные web-npo-
граммированию без применения Java, всегда рекомендуется закрывать объект
Connection, как только он становится ненужным. Рекомендация является такой
настойчивой, что многие воспринимают ее как запрет на сохранение соединения
открытым. Почему?
В действительности открытое соединение не является табу. Оставить или
нет объект Connection открытым, зависит от создаваемого приложения. Однако
сначала необходимо удостовериться в том, что время ответа действительно
уменьшится, если доступ к базе данных не будет требовать открытия объекта
Connection. Например, можно создать объектную переменную уровня класса
объекта Connection, чтобы к ней можно было обращаться из любого места в
сервлете, и поместить код, который открывает объект Connection, в мётод
init. Таким образом, объект Connection будет открываться при начальной
инициализации сервлета. При последующих запросах Connection уже будет
открыт, поэтому реакция на действия пользователя будет более быстрой.
Однако сервлет может быть доступен нескольким пользователям одновремен-
но. Плохо то, что базовая архитектура объекта Connection допускает обработку
156
Глава 4
только одного запроса в каждый момент времени. В результате лишь один
пользователь может использовать соединение в данный момент времени. Дру-
гие вынуждены ожидать в очереди. При условии, что современные базы данных
допускают множественные соединения, это будет в действительности беспо-
лезной тратой времени.
Вывод состоит в том, что если сервлет доступен только одному пользователю
в определенный момент времени, то сохранение объекта Connection открытым
делает время ответа короче. Однако если невозможно гарантировать, что не бу-
дет одновременного доступа к сервлету, время ответа начнет увеличиваться, так
как второй и последующие пользователи должны будут ждать завершения
обслуживания первого пользователя.
Именно поэтому, за исключением некоторых редких случаев, необходимо
закрывать объект Connection. Вместо того чтобы держать объект Connection от-
крытым, драйвер реализует технологию, называемую пулом соединений, которая
управляет объектами соединения с базой данных так, что несколько соедине-
ний могут группироваться в пул. Соединение может использоваться и по завер-
шении использования возвращаться в пул для обслуживания других запросов.
Транзакции
Транзакция является единицей работы, которая может включать в себя одну
или несколько операций SQL. В примерах, рассмотренных выше, каждая опе-
рация SQL выполняется и изменение фиксируется (делается постоянным) в
базе данных сразу после выполнения оператора SQL.
Однако в некоторых случаях набор операций SQL должен быть выполнен
успешно или не выполнен как единое целое. Если один из операторов SQL
отказывает, другие операторы в группе также должны откатываться. Рассмот-
рим следующий сценарий.
В сетевом магазине покупатель приобретает несколько книг. Когда покупа-
тель заканчивает оформление и производитоплату с помощью кредитной кар-
ты, должны быть выполнены следующие действия:
1. В таблицу Orders должна быть добавлена запись, определяющая заказ.
Она включает в себя адрес доставки и данные кредитной карты. Эта опе-
рация создает Orderld, который используется для идентификации каж-
дого элемента в таблице OrderDetails.
2. Для каждого покупаемого элемента в таблицу OrderDetails должна быть
вставлена запись. Каждый элемент связывается с таблицей Orders с по-
мощью значения Orderld, возвращаемого предыдущей операцией SQL.
Если все идет нормально, оба оператора SQL будут успешно выполнены в
базе данных. Однако ситуация может пойти неправильно. Например, первая
Доступ к базам данных с помощью JDBC
157
операция завершилась успешно, но вторая операция SQL не проходит. В этом
случае данные заказа теряются, и покупатель ничего не получит. Однако кре-
дитная карта покупателя все равно будет дебетована на величину стоимости
покупки, так как запись была добавлена в таблицу Orders.
В этом случае требуется, чтобы оба оператора SQL выполнялись или не
выполнялись как единое целое. Это делается с помощью транзакции. При
отказе транзакции можно уведомить покупателя, что он может попробовать
оформить покупку заново.
По умолчанию состояние автозавершения объекта Connection задано как
true,это означает, что база данных обновляется при выполнении оператора SQL.
Е :ли необходимо сгруппировать набор операторов SQL в одну транзакцию, не-
обходимо прежде всего уведомить объект Connection о том, что он не должен
обновлять данные, пока его не попросят об этом явно. Это делается с помощью
метода setAutoCommit, которому передается false в качестве аргумента:
connection.setAutoCommit(false);.
Затем можно выполнить все операторы SQL в группе обычным образом,
используя методы executeQuery и updateQuery. После последнего вызова мето-
да executeQuery или updateQuery вызывается метод commit объекта Connection,
который сделает изменения в базе данных постоянными:
connection.commit();
Если метод commit не вызывается в течение определенного времени, все
операторы SQL откатываются и вызывается метод setAutoCommit. Можно также
явно откатить транзакцию, вызвав метод rollback объекта Connection:
connection.rollback();
(^Примечанием Об использовании транзакций рассказывается в главе 18.
Создание пулов соединений
Создание объекта Connection является одной из самых дорогих операций при
программировании базы данных, особенно в тех случаях, когда объект
Connection используется лишь для одной или двух операций SQL.
Чтобы сделать использование объектов Connection более эффективным, эти
объекты объединяются в пул. При запуске приложения создается некоторое
число объектов Connection и они сохраняются в пуле. Когда клиенту базы дан-
ных, такому как сервлет, необходимо использовать объект Connection, он не
создает объект, а запрашивает его из пула. Когда клиент заканчивает работу,
объект Connection возвращается в пул.
158
Глава 4
Можно создать пул объектов Connection программным путем. Однако
существует более простой и надежный способ: использовать средство создания
пулов дополнительного пакета JDBC. Хорошим свойством этого средства является
то, что создание пула соединений прозрачно. Для его применения программисту
не требуется изменять в коде ни одной строки.
Чтобы использовать средство создания пулов соединений из пакета javax.sql,
необходимо установить соединение с источником данных с помощью интер-
фейса javax.sql. DataSource. Код для получения соединения применяет JNDI (см.
главу 28) и имеет вид:
Context context = new InitailContextO;
DataSource ds = (DataSource)context.lookup("jdbc/myDB");
Connection connection = ds.getConnection(user, password);
Заключение
Доступ к базе данных является одним из наиболее важных аспектов програм-
мирования Web. Java имеет для этого свою собственную технологию, называе-
MyioJDBC; соответствующие функции помещены enaKeTjava.sql. Вэтой главе
описаны различные члены этого пакета и показано, как их использовать. Вы
также узнали о создании сервлетов, которые обращаются к базе данных.
Управление сеансом
1. Протокол пересылки гипертекста (HTTP) — это сетевой протокол, ис-
пользуемый web-сервером и клиентскими браузерами для общения друге другом.
HTTP является языком Web. Соединения HTTP инициируются клиентским
браузером, который посылает запрос HTTP. Затем web-сервер отправляет от-
вет HTTP и закрывает соединение. Если тот же клиент запрашивает другой
ресурс сервера, он должен открыть другое соединение HTTP с сервером. Сер-
вер всегда закрывает соединение, как только он посылает ответ, требуется или
нет пользователю браузера какой-то другой ресурс сервера.
Этот процесс аналогичен телефонному разговору, где получатель всегда
вешает трубку после ответа на последнее замечайие/вопрос вызывающего або-
нента. Вызов происходит примерно следующим образом:
Абонент звонит. Абонент соединяется.
Абонент: «Здравствуйте, доброе утро».
Получатель: «Доброе утро».
Получатель вешает трубку.
Абонент снова звонит. Абонент соединяется.
Абонент: «Могу я поговорить с доктором Зеусом?»
Получатель: «Конечно».
Получатель вешает трубку.
Абонент снова звонит, и т. д., и т. д.
160
Глава 5
Так как web-сервер всегда отсоединяется после ответа на запрос, он не знает,
запрос пришел от пользователя, который только что запрашивал первую стра-
ницу, или от пользователя, который перед этим запросил девять других страниц.
Поэтому говорят, что HTTP не поддерживает состояние.
Отсутствие состояния имеет важные последствия. Рассмотрим, например,
пользователя, который зашел в сетевой магазин. Как обычно, процесс начи-
нается с того, что пользователь ищет какой-то продукт. Если продукт найден,
пользователь вводит количество этого товара в форму корзины покупателя и
отправляет форму на сервер. Но пользователь пока еще не оформил покупку —
он хочет купить что-то еще. Поэтому он ищет в каталоге другой продукт. Од-
нако заказ на первый продукт теряется, так как предыдущее соединение было
закрыто и web-сервер ничего не помнит о нем.
К счастью, web-программисты могут справиться с этой проблемой. В качестве
решения предлагается средство управления сеансом пользователя. Web-сервер свя-
зывает запросы HTTP и клиентские браузеры.
Основы управления сеансом
Отсутствие состояния в HTTP оказывает глубокое влияние на программиро-
вание web-приложений. Чтобы понять проблему, рассмотрим LoginServlet из
главы 3 и главы 4. Скелет приложения показан ниже:
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendLoginForm(response, false);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (login(userName, password)) {
// регистрация успешна, вывести информацию на экран
}
else {
И регистрация не прошла, снова послать форму регистрации Login
sendLoginForm(response, true);
}
}
Управление сеансом
161
Сервлет Login служит для того, чтобы заставить пользователя ввести дей-
ствительное имя пользователя и пароль, прежде чем он сможет получить дос-
туп к какой-либо информации. Когда пользователь впервые запрашивает сер-
влет, выводится форма Login. Пользователь может ввести имя и пароль и
отправить форму. При условии, что форма применяет метод POST, информа-
ция пользователя обрабатывается методом doPost, который выполняет аутен-
тификацию, вызывая метод login. Если регистрация успешна, выводится ин-
формация. Если нет, снова посылается форма Login.
А что, если имеется другой сервлет, который также разрешает просматри-
вать информацию только авторизованным пользователям? Этот сервлет не
знает, что тот же самый пользователь успешно зарегистрировался в первом сер-
влете. Следовательно, от пользователя снова потребуется регистрация.
Это, конечно, не практично. Каждый раз, когда пользователь собирается
запросить защищенный сервлет, он сновадолжен регистрироваться, даже если
все сервлеты являются частью одного приложения. Это быстро начинает раз-
дражать пользователя и может привести к потере заказчика.
К счастью, справиться с этой проблемой позволяет технология запомина-
ния сеанса пользователя. Однажды зарегистрировавшись, пользователь не дол-
жен регистрироваться снова. Приложение запомнит его. Это называется уп-
равлением сеансом.
Управление сеансом, называемое также отслеживанием сеанса, это не толь-
ко запоминание пользователя, который успешно зарегистрировался. Все, что
заставляет приложение запомнить информацию, введенную или запрошенную
пользователем, может рассматриваться как управление сеансом. Управление
сеансом не изменяет природы HTTP, не поддерживающего состояния, — оно
лишь предоставляет способ решения этой проблемы.
В общем, управление сеансом пользователя заключается в выполнении сле-
дующих действий для сервлетов/страниц, которым необходимо помнить со-
стояние пользователя:
1. Когда пользователь запрашивает сервлет, в дополнение к ответу сервлет
посылает также маркер или идентификатор.
2. Если пользователь не возвращается со следующим запросом того же или
другого сервлета, то все нормально. Если пользователь возвращается, то
маркер или идентификатор посылается назад на сервер. Получив мар-
кер, следующий сервлет должен опознать идентификатор и выполнить
определенное действие на основе этого маркера. Отвечая на запрос, сер-
влет также посылает тот же самый или другой маркер. Это делается для
всех сервлетов, которым необходимо помнить сеанс пользователя.
Для управления сеансом применяются четыре метода. Принцип их действия
одинаков. Отличаются они тем, что и как передается. Это методы:
162
Глава 5
• Перезапись URL
• Скрытые поля
• Cookie
• Объекты сеанса
Какой метод использовать, зависит от того, что необходимо делать в при-
ложении.
Перезапись URL
При перезаписи URL маркер или идентификатор добавляется к URL следую-
щего сервлета или следующего ресурса. Пары имя параметра/значение можно
посылать в формате:
url?name1=value1&name2=value2&...
Имя и значение разделяются знаком равенства (=); пары имя параметра/
значение отделяются друг от друга амперсандом (&). Когда пользователь щел-
кает мышью на гиперссылке, пары имя параметра/значение пересылаются сер-
веру. В сервлете для получения значения параметра можно использовать метод
getParameter интерфейса HttpServletRequest. Например, чтобы получить зна-
чение второго параметра, нужно написать:
request.getParameter(name2);
При использовании перезаписи URL необходимо учитывать несколько
моментов:
• Число символов, которое может передаваться в U RL, ограничено. Обыч-
но браузер способен передавать до 2000 символов.
• Передаваемое значение можно увидеть в URL. Иногда это нежелательно.
Например, некоторые пользователи предпочитают, чтобы их пароль не
появлялся в URL.
• Необходимо кодировать некоторые символы, такие как &, ? и пробелы,
при их добавлении в URL.
В качестве примера создадим приложение, которое можно применять для ад-
министрирования всех, кто зарегистрировался в базе данных. Это приложение
использует таблицу Users, созданную в главе 4, и позволяет делать следующее:
1. Вводить ключевое слово для поиска по столбцам имени и фамилии в таб-
лице Users.
2. Выводить на экран все записи, которые соответствуют ключевому слову.
Управление сеансом
163
3. В дополнение к данным, для каждой записи существуют гиперссылки
Delete и Update. Идентификатор (id) пользователя включается в гиперс-
сылки.
4. При щелчке мыши на гиперссылке Delete соответствующая запись будет
удалена из таблицы Users.
5. При щелчке мыши на гиперссылке Update данные соответствующего
человека будут выводиться в форме. Затем можно изменить данные и
отправить форму для обновления записи.
Приложение Administration показано на рис. 5.1,5.2. и 5.3:
Рис. 5.1. Страница Search (Поиск) приложения Administration
Рис. 5.2. Страница Delete (Удалить) приложения Administration
164
Глава 5
Update Form
Please edit the first name, last name or pisrword.
►
A
First Name |Samartha
Lest Name |don«s
User Name sjones
Password
Рис. 5.3. Страница Update (Обновить) приложения Administration
Первый сервлет, SearchServlet, аналогичен сервлету, приведенному в гла-
ве 4. Метод doGet посылает форму пользователю для ввода ключевого слова.
Ключевым словом может быть имя, фамилия или часть имени или фамилии.
Для обеспечения дополнительной гибкости этот пример отделяет форму от ос-
тальной части страницы. Поэтому заголовок и завершение страницы посыла-
ются отдельно. Метод doGet приведен в листинге 5.1.
Л исти н г 5.1 Метод doGet сервлета SearchServlet
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
sendSearchForm(response);
sendPageFooter(response);
}
Заголовок страницы содержит заголовок страницы HTML, включая назва-
ние страницы и открывающий тег <BODY>. Для отправки заголовка страницы
вызывается метод send Page Header. Завершение страницы содержит нижнюю
часть страницы, т. е. закрывающие теги </BODY> и </HTML>. Вызов метода
send Page Footer передает завершение страницы.
Метод sendSearchForm посылает форму HTML. Эта форма использует метод
POST При отправке она передает серверу ключевое слово. Сервер вызывает
метод doPost, код которого приведен в листинге 5.2.
Управление сеансом
165
Листинг 5.2 Метод doPost сервлета SearchServlet
public void doPost(HttpServletRequest request,
HttpServletResponse response) ‘
throws ServletException, lOException {
keyword = request.getParameter("keyword");
sendPageHeader(response);
sendSearchForm(response);
sendSearchResult(response);
sendPageFooter(response);
}
Метод doPost прежде всего извлекает ключевое слово из объекта
HttpServerRequest и сохраняет его в переменной уровня класса keyword. Затем
он посылает браузеру заголовок страницы, форму поиска, результат поиска и
завершение страницы.
Метод sendSearchResult формирует SQL-оператор select, который содержит
keyword. Синтаксис оператора:
SELECT Id, FirastName, LastName, UserName, Password
FROM Users
WHERE FirstName LIKE ’%keyword%’
OR LastName LIKE ’%keyword%’
Отметим, что символ % обозначает любой текст длиной нуль или больше
символов. Следовательно, jo% найдет jo, john, jones и т. д. Однако этот груп-
повой символ различается на разных серверах, и перед его использованием
необходимо свериться с документацией сервера базы данных.
Так как вводимое пользователем ключевое слово может содержать сим-
вол одиночной кавычки, применяется методfixSqlFieldValue классаStringUtil
для «исправления» значения. Класс StringUtil является частью пакета
com.brainysoftware.java, который содержится на сайте www.lory-press.ru (см. главу
3). Необходимо скопировать файл StringUtil Java в каталог com/brainysoftware/
java/ каталога, где расположены файлы исходного кода.
Оператор SQL создается с помощью кода:
String sql =
"SELECT Id, FirstName, LastName, UserName, Password" +
" FROM Users" +
" WHERE FirstName LIKE '%" +
StringUtil.fixSqlFieldValue(keyword) + +
" OR LastName LIKE ’%"
166
Гпава 5
После выполнения оператора SQL можно просмотреть в цикле возвращаемый
объект ResultSet, чтобы получить значения ячеек. Особый интерес представляет
вызов метода getString, которому передается значение 1. Он возвращает Id
указанного лица. Этот Id важен тем, что он используется в качестве маркера
лица. Id передается в URL при вызове DeleteServlet и UpdateServlet следую-
щим образом:
out.println(”<TDxA HREF=DeleteServlet?id=" + id +
”>Delete</AX/TD>");
out.println(”<TD><A HREF=UpdateServlet?id=" + id +
”>Update</AX/TD>”);
Поэтому для лица, Id которого равен 6, гиперссылка на DeleteServlet будет
следующей:
<А HREF=DeleteServlet?id=6>Delete</A>
А для лица, Id которого равен 8, гиперссылка на UpdateServlet будет:
<А HREF=UpdateServlet?id=8>Update</A>
Как же информация добавляется в URL?
Код SearchServlet представлен в листинге 5.3.
Листинг 5.3 SearchServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class SearchServlet Extends HttpServlet {
private String keyword =
public void init() {
try {
Class. forName("sun.jdbc.odbc.JdbcOdbcDriver”);
System.out.println("JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
}
Управление сеансом
167
/★* Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
sendPageHeader(response);
sendSearchForm(response);
sendPageFooter(response);
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
keyword = request.getParameter("keyword");
sendPageHeader(response);
sendSearchForm(response);
sendSearchResult(response);
sendPageFooter(response);
}
void sendSearchResult(HttpServletResponse response)
throws lOException {
Printwriter out = response.getWriterO;
try {
Connection con =
DriverManager.getConnection("jdbc:odbc:JavaWeb");
System.out.println("got connection");
Statement s = con.createStatementO;
out.println("<TABLE>");
out.printin("<TR>");
out.println("<TH>First Name</TH>");
out.println("<TH>Last Name</TH>");
out.println("<TH>User Name</TH>");
out.println("<TH>Password</TH>");
out. println( "<THx/TH>");
out.println("<TH></TH>");
out.println("</TR>");
String sql =
"SELECT Id, FirstName, LastName, UserName, Password" +
" FROM Users" +
" WHERE FirstName LIKE '%” +
StringUtil.fixSqlFieldValue(keyword) + "%'" +
" OR LastName LIKE ’%" +
StringUtil. fixSqlFieldValue( keyword) + "%”';
ResultSet rs = s.executeQuery(sql);
168
Глава 5
Листинг 5.3 Продолжение
while (rs.nextO) {
String id = rs.getString(l);
out.println("<TR>");
out. println(" <TD>" + StringUtil.encodeHtmlTag(rs.getString(2)) + "<Д0>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(3)) + "</TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(4)) + "</TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(rs.getString(5)) + "</TD>");
out.println("<TD><A HREF=DeleteServlet?id=” + id + ">Delete</AX/TD>");
out.println("<TDxA HREF=UpdateServlet?id=" + id + ">Update</A><AD>");
out.println("</TR>");
>
s.closeO; con.closeO;
}
catch (SQLException e) {
}
catch (Exception e) {
}
out.println("</TABLE>");
}
/★ *
* Отправка заголовка страницы HTML, включая название
* и тег <BODY>
* /
private void sendPageHeader(HttpServletResponse response)
throws ServletException, lOException {
response. setContentType( ’’text/html");
Printwriter out = response.getWriter();
out. println('’<HTML>"); <
out. println(”<HEAD>’’);
out.println("<TITLE>Displaying Selected Record(s)</TITLE>");
out.println("</HEAD>");
out. println(’’<BODY>”);
out.println("<CENTER>");
}
/ ★ ★
* Отправка завершения страницы HTML, т.е. </BOOY> и
* </HTML>
* /
private void sendPageFooter(HttpServletResponse response)
throws ServletException, lOException {
Управление сеансом
169
Printwriter out = response.getWriter();
out.println("</CENTER>");
out.println("</B0DY>");
out.println("</HTML>");
}
/** Отправка формы, где пользователь может ввести
* новые пользовательские данные
* /
private void sendSearchForm(HttpServletResponse response)
throws lOExceptiory {
Printwriter out = response.getWriter();
out.println("<BR><H2>Search Form</H2>");
out.println(’’<BR>Please enter the first name, last name or part of any.”);
out.println(”<BR>");
out.println(”<BR><FORM METHOD=POST>");
out.print(’’Name: <INPUT TYPE=TEXT Name=keyword’’);
out.print(" VALUE=\”” + StringUtil.encodeHtmlTag(keyword) + "\"");
out.println(">");
out. println("<INPUT TYPE=SUBMIT>");
out.println(”</FORM>”);
out. println('’<BR>’');
out.println("<BR>");
}
_ }
Сервлет DeleteServlet получает значение id, добавленное в URL, и удаляет
запись о лице, имеющем этот id.
Извлечение id выполняется с помощью метода getParameter интерфейса
HttpServletRequest:
String id = request.getParameter("id");
После получения id можно составить оператор SQL следующим образом:
String sql = "DELETE FROM Users WHERE Id=" + id;
Затем создаются объект Connection и объект Statement, который используется
для выполнения оператора SQL:
Connection con= DriverManager.getConnection(”jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
recordAffected = s.executeUpdate(sql);
170
Гпава 5
Код сервлета DeleteServlet представлен в листинге 5.4. Отметим, что в
DeleteServlet и в Updateservlet нет кода для загрузки драйвера JDBC. Это
делается в сервлете SearchServlet, и драйвер остается для других сервлетов,
которым необходимо соединяться с той же базой данных.
Листинг 5.4 DeleteServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
public class DeleteServlet extends HttpServlet {
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
int recordAffected = 0;
try {
String id = request.getParameter("id”);
String sql = ’’DELETE FROM Users WHERE Id=” + id;
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
recordAffected = s.executeUpdate(sql);
s.close();
con.closeO;
}
catch (SQLException e) {
}
catch (Exception e) {
}
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>”);
out.println("<HEAD>”);
out.println(’’<TITLE>Deleting A Record</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
if (recordAffected==1)
but.println(”<P>Record deleted.</P>’’);
else
out.println("<P>Error deleting record.</P>’’);
out.println(”<A HREF=SearchServlet>Go back</A> to the Search page");
}
_________
Управление сеансом
171
UpdateServlet получает id, переданный в URL, и посылает форму, содержа-
щую данные о соответствующем лице. Пользователь может обновить имя,
фамилию и пароль для этого человека, но не имя пользователя (username).
Поэтому имя, фамилия и пароль представлены в текстовых полях, в то время
как имя пользователя выводится как текст HTML.
Бизнес-правило этого сервлета устанавливает, что имя пользователя не мо-
жет изменяться, так как гарантируется его уникальность во время вставки дан-
ных. Если разрешить изменение этого значения, данное ограничение может
быть нарушено. Конечно, реализация сервлета зависит от ваших собственных
требований и бизнес-правил. UpdateServlet вызывается щелчком мыши на URL
в SearchServlet. U RL всегда содержит id лица, данные которого будут изменяться.
Этот запрос вызывает метод doGet сервлета UpdateServlet, который посылает
заголовок стран и цы, форму обновления и завершение страницы. Метод doGet
представлен в листинге 5.5.
Листинг 5.5 Метод doGet сервлета UpdateServlet
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
sendUpdateForm(request, response);
sendPageFooter(response);
}
sendUpdateForm сначала извлекает id из U RL, используя метод get Parameter:
String id = request.getParameter("id”);
Затем метод соединяется с базой данных для извлечения данных из табли-
цы Users. Применяется оператор SQL:
SELECT FirstName, LastName, UserName, Password
FROM Users
WHERE Id = id
Затем метод посылает данные в форме HTML, аналогичной форме, показан-
ной на рис. 5.3.
Интересной частью кода является отправка тега <FORM>:
out.println(”<BR><FORM METHOD=POST ACTI0N=” +
request. getRequestURIO + "?id=” + id + ”>”);
172
Глава 5
Обычно форме не требуется атрибут ACTION, так как она будет отправлять-
ся тому же сервлету. В этот раз, однако, нужно добавить в URL значение id. По-
этому используется метод getRequestURl для получения текущего URI (уни-
версального идентификатора ресурса) и добавляется такая информация, как
?id=6
для пользователя, id которого равен 6.
Когда пользователь отправляет форму, вызывается метод doPost сервлета
UpdateServlet. Метод doPost показан в листинге 5.6.
Листинг 5.6 Метод doPost сервлета UpdateServlet
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
updateRecord(request, response);
sendPageFooter(response);
}
Важным методом, который вызывается в методе doPost, является
updateRecord. Метод updateRecord сначала извлекает значения id, firstName,
lastName и password с помощью метода getParameter. Отметим, что id извлека-
ется из URL, а другие значения — из тела запроса:
String id = request.getParameter("id");
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
String password = request.getParameter("password");
С помощью этих значений можно создать оператор SQL, который будет
обновлять запись. Он имеет следующий синтаксис:
UPDATE Users
SET FirstName=firstName,
Last Name=7 as t/Va/ne,
Password=password
WHERE Id=id
Затем можно выполнить SQL обычным образом:
Connection con = DriverManager.getConnection(dbUrl);
Statement s = con.createStatementO;
int i = s.executeUpdate(sql);
Управление сеансом
173
Теперь executeUpdate должен вернуть число записей, затронутых опера-
тором SQL. Так как id является уникальным, должно возвращаться значение
1. Если оно равно 1, то посылается сообщение «Record updated» («Запись
обновлена»). Если это число отлично от 1 из-за неожиданной ошибки, посылается
сообщение «Error updating record» («Ошибка обновления записи»).
if (i==D
out.println(”Record updated”);
else
out.println(”Error updating record”);
Код сервлета UpdateServlet приведен в листинге 5.7.
Листинг 5.7 UpdateServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class UpdateServlet extends HttpServlet {
private String dbUrl = "jdbc:odbc:JavaWeb”;
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
sendPageHeader(response);
sendUpdateForm(request, response);
sendPageFooter(response);
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
sendPageHeader(response);
updateRecord(request, response);
sendPageFooter(response); -
}
Отправка заголовка страницы HTML, включая название
174
Глава 5
Листинг 5.7 Продолжение
* и тег <BODY>
* /
private void sendPageHeader(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out. println(’’<HTML>”);
out.println("<HEAD>”);
out.println("<TITLE>Updating Record</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out. println( "<CENTER>");
}
/* *
* Отправка завершения страницы HTML, т. e. </BODY>
* и </HTML>
* /
private void sendPageFooter(HttpServletResponse response)
throws ServletException, lOException {
Printwriter out = response.getWriter();
out. println( "</CENTER>'’);
out.println(”</BODY>”);
out.println("</HTML>");
}
/** Отправка формы, где пользователь может ввести
* новые пользовательские данные
* /
private void sendUpdateForm(HttpServletRequest request,
HttpServletResponse response)
throws lOException {
String id = request.getParameter("id");
Printwriter out = response.getWriter();
out.println("<BR><H2>Update Form</H2>");
out.println("<BR>Please edit the first name, last name or password.”);
out.println("<BR>");
try {
String sql = "SELECT FirstName, LastName," +
" UserName, Password" +
" FROM Users" +
" WHERE Id=" + id;
Connection con = DriverManager.getConnection(dbUrl);
Управление сеансом
175
Statement s = con.createStatementO;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
String firstName = rs.getString(l);
String lastName = rs.getString(2);
String userName = rs.getString(3);
String password = rs.getString(4);
out.println(”<BR><FORM METH0D=P0ST ACTI0N=” +
request, get RequestURIO + "?id=" + id +
out.println("<ТАВ1Е>”);
out.println("<TR>”);
out.println("<TD>First Name</TD>”);
out.print("CTDXINPUT TYPE=TEXT Name=firstName");
out.print(” VALUE=\’’” + StringUtil.encodeHtmlTag(firstName) +
out. println( ’’x/TD>’’);
out.println(”</TR>”);
out.println(,,<TR>’’);
out. println(’’<TD>l_ast Name</TD>");
out.print(”<TD><INPUT TYPE=TEXT Name=lastName”);
out.print(" VALUE=\’”’ + StringUtil.encodeHtmlTag(lastName) +
out. println( "x/TDX);
out.println("</TR>”);
out.println(”<TR>");
out.println("<TD>User Name</TD>");
out.print("<TD>” + StringUtil. encodeHtmlTag( userName) + ’’</TD>’’);
out.println(‘’</TR>’’);
out.println(”<TR>”);
out. println( ’’<TD>Password</TD>’’);
out.print(”<TDXINPUT TYPE=PASSWORD Name=password’’);
out.print(" VALUE=V" + StringUtil.encodeHtmlTag(password) +
out. println( "x/TD>");
out.println(”</TR>”);
out.println("<TR>");
out. println(XTDXINPUT TYPE=RESET></TD>");
out. println(XTDXINPUT TYPE=SUBMIT></TD>");
out.println(’’</TR>”);
out.println("</TABLE>”);
out.println("</FORM>”);
}
s.closeO;
con.close();
}
176
Глава 5
Листинг 5.7 Продолжение
catch (SQLException е) {
out.рrintIn(e.toString());
}
catch (Exception e) {
out. println(e. toStringO);
}
I
}
void updateRecord(HttpServletRecjuest request, HttpServletResponse
response)
throws lOException {
String id = request.getParameter("id");
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
String password = request.getParameter("password");
Printwriter out = response.getWriterO;
try {
String sql = "UPDATE Users" +
" SET FirstName=’" + StringUtil.fixSqlFieldValue(firstName) + +
" LastName='" + StringUtil.fixSqlFieldValue(lastName) + +
" Password=’" + StringUtil.fixSqlFieldValue(password) + ..... +
" WHERE Id=" + id;
Connection con = DriverManager.getConnection(dbUrl);
Statement s = con.createStatementO;
int i = s.executeUpdate(sql);
if (i==1)
out.println("Record updated");
else
out.println("Error updating record");
s.closeO;
con.closeO;
}
catch (SQLException e) {
out. println(e. toStringO);
}
catch (Exception e) {
out.println(e.toSt ring());
}
out.println(”<A HREF=SearchServlet>Go back</A> to the Search
Page”);
}
Управление сеансом
177
Скрытые поля
Другим методом управления пользовательскими сеансами является передача
маркера в качестве значения скрытого поля HTML. В отличие от перезаписи
URL значение не показывается в URL, но может быть прочитано при про-
смотре исходного кода HTML. В случае применения этого метода требуется
форма HTML.
Использование скрытых полей
В качестве первого примера использования этого метода изменим метод
send Update Form в UpdateServlet (см. листинг 5.7). Этот сервлет должен по-пре-
жнему применяться с сервлетами SearchServlet и DeleteServlet, представлен-
ными в листингах 5.3 и 5.4. Эти два сервлета не модифицируются, и их код
здесь не будет повторяться.
Новый метод send Update Form приведен в листинге 5.8.
Листинг 5.8 Модификация метода sendUpdateForm в сервлете
UpdateServlet
private void sendUpd^teForm(HttpServletRequest request,
HttpServletResponse response)
throws lOException {
String id = request.getParameter("id");
Printwriter out = response.getWriter();
out. println("<BR><H2>Update Form</H2>’’);
out.println(’’<BR>Please edit the first name, last name or password.”);
out.println(”<BR>”);
try {
String sql = "SELECT FirstName, LastName," +
" UserName, Password" +
" FROM Users" +
" WHERE Id=" + id;
Connection con = DriverManager.getConnection(dbUrl);
Statement s = con.createStatementO;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
String firstName = rs.getString(l);
String lastName = rs.getString(2);
String userName = rs.getString(3);
String password = rs.getString(4);
178
Глава 5
Листинг 5.8 Продолжение
out.р rintIn("<BRxF0RM METHOD=POST>”);
out.print("<INPUT TYPE=HIDDEN Name=id VALUE=" + id +
out.println(”<TABLE>");
out.println(’’<TR>”);
out.println("<TD>First Name</TD>”);
out.print(”<TD><INPUT TYPE=TEXT Name=firstName”);
out.print(" VALUE=\”’’ + StringUtil.encodeHtmlTag(firstName) +
out. println(”x/TD>“);
out. println(’’</TR>”);
out.println("<TR>”);
out. println("<TD>Last Name</TD>‘');
out.print(XTDXINPUT TYPE=TEXT Name^astName");
out.print(" VALUE=\"” + StringUtil.encodeHtmlTag(lastName) +
out. println("x/TD>");
out.println(”</TR>”);
out.println(”<TR>");
out.println("<TD>User Name</TD>”);
out.print("<TD>” + StringUtil.encodeHtmlTag(userName) + "</TD>”);
out. println(”</TR>’’);
out.println(”<TR>”);
out.println("<TD>Password</TD>");
out.print(XTDXINPUT TYPE=PASSWORD Name=password’’);
out.print(” VALUt=\”” + StringUtil.encodeHtmlTag(password) +
out. println( "x/T0>");
out.println(”</TR>”);
out.println(”<TR>");
out. println( XTDXINPUT TYPE=RESETx/TD>");
out. println(XTDXINPUT TYPE=SUBMIT></TD>”);
out.println(”</TR>”);
out.println(”</TABLE>”);
out.println(”</FORM>");
}
s.closeO;
con.closeO;
}
catch (SQLException e) {
out. println(e. toStringO);
}
catch (Exception e) {
out. println(e. toStringO);
}
}____________________________________________________________________
Управление сеансом
179
Если выполнить это приложение в web-браузере, то можно будет увидеть,
что id не добавляется к URL. Поэтому форме не требуется атрибут ACTION, id
записывается теперь в скрытом (HIDDEN) поле следующим образом:
out.print("<INPUT TYPE=HIDDEN Name=id VALUE=" id +
Когда форма отправляется, скрытое поле посылается вместе с другими зна-
чениями элементов ввода формы. В сервлете id извлекается с помощью метода
getParameter.
Разделение форм
Скрытое поле применяется также в случае, когда желательно разделить боль-
шую форму на несколько меньших форм для удобства пользователя. Для примера
вновь воспользуемся
таблицей Users. Напишем приложений, которое пользователь может при-
менять для вставки новой записи в таблицу Users (см. главу 4). Однако вместо
одной формы, где пользователь может одновременно ввести имя, фамилию,
имя пользователя и пароль, применим две меньшие формы. Первая форма будет
принимать имя и фамилию, а вторая — имя пользователя и пароль.
При отправке второй формы все четыре значения должны быть переданы
третьему сервлету, который составляет и выполняет SQL-оператор insert.
В этом примере будут использоваться три сервлета для каждой страницы по
очереди. Они называются Pagel Servlet, Page2Servlet и Page3Servlet. Pagel Servlet
посылает форму НТМЕсдвумя текстовыми полями. При желании можно заме-
нить этот сервлет статическим файлом HTML. Pagel Servlet показан на рис. 5.4.
Рис. 5.4. Pagel Servlet
180
Глава 5
После отправки первой формы происходит переход к Page2Servlet.
Page2Servlet посылает вторую форму, а также значения из первой формы. Он
показан на рис. 5.5.
Рис. 5.5. Page2Servlet
Когда вторая форма будет отправлена, все четыре значения попадут в
Page3Servlet. Можно вставить новую запись в базу данных. Однако для кратко-
сти мы лишь выведем значения, не пытаясь получить доступ к какой-либо базе
данных. Результат работы сервлета Page3Servlet показан на рис. 5.6.
Here are the values you have entered.
First Name: Ten
Last Nbm: O’Connor
UserName toccaacr
Password relaod
Рис. 5.6. Page3Servlet
Управление сеансом
181
Теперь разделим код.
Как упоминалось, Pagel Servlet является простой формой НТМ L. Он при-
веден в листинге 5.9.
Листинг 5.9 PagelServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class PagelServlet extends HttpServlet {
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
sendPagel(response); }
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
sendPagel(response);
}
void sendPagel(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>”);
out.println("<HEAD>");
out.println("<TITLE>Page 1</TITLE>");
out.println("</HEAD>”);
out.println("<BODY>");
out.println("<CENTER>");
out.println("<H2>Page 1</H2>");
out.println("<BR>”);
out.println("<BR>");
out.println("Please enter your first name and last name.");
out.println("<BR>");
out.println("<BR>”);
out.println("<FORM METHOD=POST ACTI0N=Page2Servlet>");
out.println(”<TABLE>");
out.println(”<TR>”);
out.println("<TD>First Name </TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=firstName></TD>");
182
Глава 5
Листинг 5.9 Продолжение
out.println(”</TR>”);
out.println(”<TR>”);
out.println(”<TD>Last Name </TD>”);
out.println(”<TD><INPUT TYPE=TEXT NAME=lastNamex/TD>’’);
out.println(”</TR>”);
out.println(”<TR>”);
out.println(”<TD><INPUT TYPE=RESETx/TD>”);
out. println(”<TDxlNPUT TYPE=SUBMIT VALUE=Submitx/TD>”);
out.println(”</TR>");
out. prin«tln( "</TABLE>”);
out. println( "</FORM>”);
out.println(”</CENTER>”);
out.println("</B0DY>”);
out.println(”</HTML>”);
}
}
Необходимо отметить, что для формы здесь используется атрибут ACTION:
out.println(”<FORM METHOD=POAST ACTI0N=Page2Servlet>”);
Значением атрибута ACTION является Page2Servlet. Это гарантирует, что
форма будет передана сервлету Page2Servlet.
Page2Servlet извлекает имя и фамилию из формы Pagel Servlet и сохраняет
их в скрытых полях. Эти скрытые поля включаются в форму, которая также
посылает два текстовых поля для имени пользователя и пароля. Код этого сер-
влета приведен в листинге 5.10.
Листинг 5.10 PagelServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.brainysoftware.java.StringUtil;
public class Page2Servlet extends HttpServlet {
String pagelUrl = ’’PagelServlet”;
String firstName;
String lastName;
Управление сеансом
183
/★* Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response.sendRedi rect(pagelUrl);
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
firstName = request.getParameter("firstName");
lastName = request.getParameter("lastName”);
if (firstName==null || lastName==null)
response.sendRedirect(pagelU rl);
sendPage2(response);
}
void sendPage2(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println(”<HEAD>");
out.println(”<TITLE>Page 2</TITLE>”);
out.println(”</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
out.println("<H2>Page 2</H2>”);
out.println("<BR>");
out.println("<BR>”);
out.println("Please enter your user name and password.”);
out.println("<BR>");
out.println("<BR>");
out.println(”<FORM METHOD=POST ACTI0N=Page3Servlet>”);
out.println(”<INPUT TYPE=HIDDEN NAME=firstName VALUE=\’”’ +
StringUtil.encodeHtmlTag(firstName) + ”\”>");
out. println("<INPUT TYPE=HIDDEN NAME=lastName VALUE=\”’’ +
StringUtil.encodeHtmlTag(lastName) + ”\">");
out.println(”<TABLE>”);
out.println("<TR>");
out.println("<TD>User Name </TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=userNamex/TD>”);
out.println("</TR>");
out.println("<TR>");
out.println(”<TD>Password </TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>”);
184
Глава 5
Листинг 5.10 Продолжение
out.println(”</TR>”);
out.println(”<TR>”);
out. prin11 n("<TDXINPUT TYPE=RESETX/TD>”);
out.println(’’<TDxlNPUT TYPE=SUBMIT VALUE=Submitx/TD>");
out.println(”</TR>”);
out.println("</TABLE>”);
out.println(”</FORM>”);
out.println(”</CENTER>");
out.println(”</BODY>”);
out.p ri nt1n("</HTML>");
}
}
Если ввести «Tim» и «O'Connor» в качестве имени и фамилии в первой фор-
ме, то исходный код HTML, возвращаемый на страницу 2 (Page 2), будет сле-
дующим (обратите внимание на строки, выделенные жирным шрифтом, здесь
значения предыдущей формы возвращаются браузеру):
<НТМ1_>
<HEAD>
<TITLE>Page 2</TITLE>
</HEAD>
<BODY>
<CENTER>
<H2>Page 2</H2>
<BR>
<BR>
Please enter your user name and password.
<BR>
<BR>
<FORM METHOD=POST ACTI0N=Page3Servlet>
CINPUT TYPE=HIDDEN NAME=firstName VALUE="Tim">
<INPUT TYPE=HIDDEN NAME=lastName VALUE="O’Connor">
<TABLE>
<TR>
<TD>User Name </TD>
<TD><INPUT TYPE=TEXT NAME=userNamex/TD>
</TR>
<TR>
<TD>Password </TD>
<TD><INPUT TYPE=PASSWORD NAME=passwordX/TD>
</TR>
Управление сеансом
185
<TR>
CTDXINPUT TYPE=RESETx/TD>
<TD><INPUT TYPE=SUBMIT VALUE=Submitx/TD>
</TR>
</TABLE>
</FORM>
</CENTER>
</BODY>
</HTML>
Наконец, листинг 5.11 представляет Page3Servlet, который извлекает все
значения из второй формы.
Листинг 5.11 Page3Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.brainysoftware.java.StringUtil;
public class Page3Servlet extends HttpServlet {
String pagelUrl = ’’PagelServlet";
String firstName;
String lastName;
String userName;
String password;
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
response.sendRedi rect(pagelU rl);
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
firstName = request.getParameter("firstName");
lastName = request.getParameter("lastName");
userName = request.getParameter("userName");
password = request.getParameter("password");
if (firstName==null || lastName==null ||
186
Глава 5
Листинг 5.11 Продолжение
userName==null || password==null)
response. sendRedi rect (pagelll rl);
// вывести все значения из предыдущих форм
displayValues(response);
}
void displayValues(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Page 3</TITLE>");
out.println("</HEAD>");
out.println("<B0DY>");
out.println("<CENTER>”);
out.println("<H2>Page 3 (Finish)</H2>");
out.println("<BR>");
out.println("<BR>”);
out.println("Here are the values you have entered.");
out.println("<BR>");
out.println("<BR>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>First Name: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(firstName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Last Name: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(lastName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>User Name: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(userName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(password) + "</TD>");
out.println("</TR>”);
out.println("</TABLE>");
out.println("</CENTER>");
out.println("</BODY>");
out.println("</HTML>");
}
}___________________________________________________________________
Управление сеансом
187
Несколько форм в одном сервлете
В предыдущем примере значения сохранялись в скрытых полях в трех сер-
влетах. Использование более одного сервлета — нежелательное решение для
простого приложения, если учитывать затраты на сопровождение каждого сер-
влета. Попробуем решить ту же задачу с помощью только одного сервлета.
При использовании одного сервлета каждая форма будет передавать дан-
ные в один и тот же сервлет, при этом будет вызываться один и тот же метод
doPost. Как узнать, какую форму вызывать следующей? Решение состоит в со-
здании скрытого поля page, содержащего значение номера формы. Для первой
формы поле page будет иметь значение 1, а для второй формы это поле будет
иметь значение 2.
При первом вызове сервлета вызывается метод doGet. Он отправляет
первую форму в браузер с помощью метода sendPagel:
sendPagel(response);
Первая форма вызовет метод doPost, так как форма использует метод POST.
Метод doPost будет извлекать значение параметра с именем page с помощью
метода get Parameter. Значение должно предоставляться всегда. Однако если по
какой-то причине этого не происходит, метод посылает первую форму и воз-
вращает управление следующим образом:
String page = request.getParameter("page”);
if (page==null) {
sendPagel(response);
return;
}
Если значение page найдено, оно должно быть равно 1 или 2. Если возвраща-
ется 1, значит, предыдущий запрос поступил с первой страницы, поэтому долж-
на быть послана страница 2 (Page 2). Запрос, который передает первую форму,
должен содержать параметры firstName и lastName. Другими словами, вызовы
методов getParameter(”firstName") и getParameter("lastName") не должны возвра-
щать null. Сами значения могут быть пустыми строками, если, например, пользо-
ватель ничего не ввел в текстовые поля. Однако правильный запрос с первой
станицы должен доставить эти параметры:
if (page.equals(’T’)) {
If (firstName==null) || lastName==null)
sendPagel(response);
else
sendPage2(response);
}
188
Глава 5
Если либо firstName, либо lastName не найден, снова вызывается sendPagel,
так как запрос недействительный.
Если первая страница выполнилась нормально, метод doPost вызывает ме-
тод sendPage2, который посылает вторую форму и значения из первой формы.
Если значение page равно 2, значит, запрос пришел со второй страницы и в
нем должны присутствовать четыре параметра: firstName, lastName, userName
и password. Отсутствие одного из этих значений является достаточной причи-
ной для повторной отправки первой страницы, как показано ниже:
else if(page.equals("2”)) {
if (firstName==null || lastName==null ||
userName==null || password==null)
sendPagel(respond);
else
displayValues(response); >
}
Если значение page равно 2 и все другие значения найдены, вызывается ме-
тод display Values, и он выводит все четыре значения из первой и второй форм.
Полный код показан в листинге 5.12.
Л исти н г 5.12 MultipleFormsServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.brainysoftware.java.StringUtil;
firstName;
lastName;
userName;
password;
public class MultipleFormsServlet extends HttpServlet {
String
String
String
String
/**0бработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
sendPagel(response);
}
/★★Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
String page = request.getParameter(”page");
Управление сеансом
189
firstName = request.getParameter(”firstName”);
lastName = request.getParameter("lastName");
userName = request.getParameter("userName”);
password = request.getParameter("password");
if (page==null) {
sendPagel(response);
return;
}
if (page.equals('T’)) {
if (firstName==null || lastName==null)
sendPagel(response);
else
sendPage2(response);
}
else if (page.equals("2")) {
if (firstName==null || lastName==null ||
userName==null || password==null)
sendPagel(response);
else
displayValues(response);
}
}
void sendPage1(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Page 1</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
out.println("<H2>Page 1</H2>");
out.println("<BR>");
out.println("<BR>");
out.println("Please enter your first name and last name.");
out.println("<BR>");
out.println("<BR>");
out.println("<FORM METHOD=POST>");
out:println("<INPUT TYPE=HIDDEN NAME=page VALUE=1>");
out.println("<TABLE>");
out.println("<TR>");
190
Глава 5
Листинг 5.12 Продолжение
out.ргint1n("<TD>First Name </TD>”);
out.println("<TD><INPUT TYPE=TEXT NAME=firstNamex/TD>");
out.println("</TR>");
out.println("<TR>");
out.println(”<TD>Last Name </TD>”);
out.println(”<TD><INPUT TYPE=TEXT NAME=lastNameX/TD>");
out.println("</TR>");
out.println("<TR>");
out. println(XTDXINPUT TYPE=RESETX/TD>”);
out.println("<TD><INPUT TYPE=SUBMIT VALUE=Submitx/TD>");
out. println(,,</TR>”);
out.println(”</TABLE>”);
out.println("</FORM>");
out.println("</CENTER>”);
out. println( "</BODY>") ;
out.println("</HTML>");
}
void sendPage2(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response. getWriterQ;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Page 2</TITLE>");
out.println("</HEAD>");
out.println("<B0DY>");
out.println("<CENTER>");
out.println(”<H2>Page 2</H2>");
out.printin(”<BR>”);
out.println("<BR>");
out.println("Please enter your user.name and password.");
out.println("<BR>");
out.println("<BR>");
out.println("<FORM METH0D=P0ST>");
out.println("<INPUT TYPE=HIDDEN NAME=page VALUE=2>");
out.println("<INPUT TYPE=HIDDEN NAME=firstName VALUE=\"’’ +
StringUtil. encodeHtmlTag(firstName) + "\"x/TD>");
out.println("<INPUT TYPE=HIDDEN NAME=lastName VALUE=\"” +
StringUtil.encodeHtmlTag(lastName) + "\"x/TD>");
out.p ri nt1n("<TABLE>");
out.println("<TR>");
out.println("<TD>User Name </TD>");
Управление сеансом
191
out. println("<TD><INPUT TYPE=TEXT NAME-userNameX/TD>");
out.println(”</TR>”);
out.println("<TR>");
out.println("<TD>Password </TD>");
out.println(”<TDXlNPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println(’’</TR>’’);
out.println("<TR>");
out. println( XTDXINPUT TYPE=RESETX/TD>”);
out. println(”<TD><INPUT TYPE=SUBMIT VALUE=Submitx/TD>");
out.println("</TR>");
out.println("</TABLE>") ;
out. println( ''</FORM>");
out. println("</CENTER>") ;
out.println("</B0DY>");
out. println("</HTML>");
}
void displayValues(HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Page 3</TITLE>");
out.println(”</HEAD>”);
out.println("<BODY>");
out.println("<CENTER>");
out.println("<H2>Page 3 (Finish)</H2>");
out.println("<BR>");
out.println("<BR>");
out.println("Here are the values you have entered.");
out.println("<BR>");
out.println("<BR>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>First Name: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(firstName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Last Name: </TD>");
out.println("<TD>” + StringUtil.encodeHtmlTag(lastName) + "</TD>");
out.println("</TR>"):
out.println("<TR>");
out.println("<TD>User Name: </TD>");
out.println("<TD>" + StringUtil.encodeHtmlTag(userName) + "</TD>");
192
Глава 5
Листинг 5.12 Продолжение
out.println(’’</TR>’’);
out. printing’’<TR>’’);
out.println("<TD>Password: </TD>");
out.println(’’<TD>" + StringUtil.encodeHtmlTag(password) + "</TD>");
out.println(”</TR>”);
out. println( ’’</TABLE>’’);
out.println(’’</CENTER>’’);
out.println("</BODY>”);
out.println(”</HTML>");
}
}
Cookie
Третий метод, который служит для управления пользовательскими сеанса-
ми, состоит в применении cookie. Cookie является небольшим фрагментом
информации, который передается туда и обратно в запросах и ответах HTTP.
Хотя cookie может создаваться на клиентской стороне с помощью языка сце-
нариев, например JavaScript, обычно он создается серверным ресурсом, таким
как сервлет. Cookie, посланный сервлетом клиенту, возвращается назад на сер-
вер, когда клиент запрашивает другую страницу у того же приложения.
Cookie были впервые определены компанией Netscape (см. http://
home.netscape.com/newsref/std/cookie_spec.html) и теперь являются частью
стандарта Интернета, как определено в RFC 2109: The HTTP State Management
Mechanism (RFC 2109: Механизм управления состоянием HTTP). Cookie пе-
реносятся клиенту и обратно в заголовках HTTP.
В программировании сервлетов cookie представляется классом Cookie в па-
кете javax.servlet.http. Можно создать cookie, вызвав конструктор класса Cookie
и передав два объекта String: имя и значение cookie. Например, следующий
код создает объект cookie в cl. Cookie имеет имя «MyCookie» и значение «secret»:
Cookie d = new CookieC’myCookie", ’’secret");
Затем можно добавить cookie в ответ HTTP с помощью метода addCookie
интерфейса HttpServletResponse:
response.addCookie(c1);
Так как cookies переносятся в заголовках запроса и ответа, нельзя добав-
лять cookie после того, как выходные данные были записаны в объект
HttpServletResponse. Иначе будет порождаться исключение.
Управление сеансом
193
Следующий пример показывает, как можно создать два cookie с именами
userName и password и как они переносятся назад на сервер. Сервлет называет-
ся CookieServlet, и его код приведен в листинге 5.13.
При первом вызове сервлета вызывается метод сервлета doGet. Метод созда-
ет два cookie и добавляет их в объект HttpServletResponse следующим образом:
Cookie d = new Cookie("userName", "Helen”);
Cookie c2 = new Cookie("password", "Keppler");
response.addCookie(c1);
response.addCookie(c2);
Затем метод doGet посылает форму HTML, в которой пользователь может
щелкнуть мышью, чтобы отправить другой запрос сервлету:
response.setContentType("text/html");
Printwriter out = response.getWriterQ;
out.println("HTML");
out. println(’’HEAD’’);
out.println("<TITLE>Cookie Test</TITLE>”);
out.println("</HEAD>");
out.println("</BODY>");
out.println("Please click the button to see the cookies sent to you.");
out. println("<BR>’’);
out.println("<FORM METHOD=POST>");
out.println("INPUT TYPE=SUBMIT VALUE=Submit");
out.println("</FORM>”);
out.println("</B0DY>");
out.println("</HTML>");
Форма не имеет других элементов, кроме кнопки отправки. Когда форма
отправляется, вызывается метод doPost. Он просматривает все заголовки в зап-
росе, чтобы показать, как cookie пересылаются на сервер, и извлекает cookie и
выводит их значения.
Для отображения на экране всех заголовков в методе HttpServletRequest сна-
чала извлекается объект Enumeration, содержащий имена заголовков. Затем
метод просматривает объект Enumeration, получает имя следующего заголовка
и передает его в метод getHeader, чтобы вывести значение заголовка:
Enumeration enum = request.getHeaderNamesO;
while (enum.hasMoreElements()) {
String header = (String) enum. nextElementO;
out.print("<B>" + header + "</B>: ");
out.print(request.getHeader(header) + "<BR>");
}
194
Глава 5
Для извлечения cookie используется метод getCookies интерфейса
HttpServletRequest. Этот метод возвращает массив Cookie, содержащий все
cookie запроса. Чтобы пол учить требуемые cookie, необходимо просмотреть этот
массив в цикле следующим образом:
Cookie[] cookies = request. getCookiesO;
int length = cookies.length;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
out.println("<B>Cookie Name:</B> " +
cookie.getName() + ”<BR>”);
out.println("<B>Cookie Value:</B> ” +
cookie. getValue() + ”<BR>’’);
Заголовки и cookie показаны на рис. 5.7
l*№;//toc«IXKt:eoeQ/f«vtoUCooktoS«vM
Here are all the headers.
Cookie: userName=Helen, password=Keppler
Cache-Control no-cache
Host localho$t:8080
Accept: */*
User-Agent. МопДа/4 0 (compatible, MSIE 6 0b. Windows NT 5.0, .NET CLR 1.0.2914)
Content-Length: 0
Accept-Language en-us
Accept-Encoding: gap, deflate
Content-Type, appkcahonfa-www-fonn-uriencoded
Connection: Keep-Akve
Referer http//localhost:8080/servietfCookieServlet
And, here are all the cookies.
Cookie Name: userName
Cookie Value: Helen
Cookie Name: password
Cookie Vahie: Keppler
Рис. 5.7. Заголовки, содержащие cookie, и значения cookie
Листинг 5.13 Отправка и получение cookie
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
Управление сеансом
195
public class CookieServlet extends HttpServlet {
/** Обработка запроса HTTP Get ★/
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
Cookie c1 = new Cookie("userName", "Helen”);
Cookie c2 = new Cookie("password", "Keppler");
response.addCookie(c1);
response.addCookie(c2);
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out. println(”<TITLE>Cookie Test</TITLE>"); •'
out.println("</HEAD>");
out.println("<BODY>");
out.println("Please click the button to see the cookies sent to you.");
out.println("<BR>");
out.println("<FORM METH0D=P0ST>");
out.println("<INPUT TYPE=SUBMIT VALUE=Submit>");
out.println("</FORM>");
out.println("</BODY>");
out.println("</HTML>");
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println(”<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Cookie Test</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<H2>Here are all the headers.</H2>");
Enumeration enum = request.getHeaderNamesO;
while (enum. hasMoreElementsO) {
String header = (String) enum.nextElementO;
out.print("<B>" + header + "</B>: ");
out.print(request.getHeader(header) + "<BR>");
196
Глава 5
Листинг 5.13 Продолжение
out.println(”<BR><BR><H2>And, here are all the cookies.</H2>”);
Cookief] cookies = request.getCookies();
int length = cookies.length;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
out.println(”<B>Cookie Name:</B> ” + cookie.getName() + "<BR>");
out.println("<B>Cookie Value:</B> ” + cookie.getValue() + ’<BR>’);
}
out.println("</BODY>");
out. print In (’’c/HTMLX);
}
}
Еще одним примером является сервлет Login, который использует cookie
для переноса информации об имени пользователя и пароле. Применение cookie
более удобно, чем использование перезаписи URL или скрытых значений. В
отличие от перезаписи URL, значения cookie не видны непосредственно (не-
желательно, чтобы эта информация была кому-то видна). И не нужно приме-
нять форму, как в случае использования скрытых полей.
Однако cookie имеют недостатки: пользователь может отключить прием
cookie. Поэтому
обычной практикой является выдача предупреждения пользователю, если
приложение работает не так, как ожидается. Предупреждением может быть
простое сообщение, предлагающее пользователю активизировать прием cookie,
или это может быть гиперссылка на страницу, которая подробно описывает,
как задать настройки cookie в различных браузерах.
Итак, CookieLoginServlet является модификацией LoginServlet из главы 4.
Код представлен в листинге 5.14. Если настройка cookie отключена, сервлет
пошлет сообщение. Сервлет также будет посылать сообщение, если аутенти-
фикация потерпит неудачу (см. рис. 5.8).
Поясним часть листинга, которая переадресует пользователя на другой ре-
сурс в случае успешной регистрации. Прежде всего необходимо создать два cookie
с именами userName и password и добавить их в объект HttpServletResponse. Cookie
всегда будут возвращаться на сервер, если пользователь переадресуется на дру-
гой ресурс. Этот ресурс может затем извлечь cookie и снова выполнить аутен-
тификацию с применением той же базы данных. Таким образом, пользователь
не должен регистрироваться больше одного раза.
Затем идет код, который перенаправляет пользователя. Обычно для пере-
адресации пользователя применяется метод sendRedirect. Однако этот метод
Управление сеансом
197
не обеспечивает возврат cookie на сервер. В качестве альтернативы использу-
ется тег МЕТА со следующим синтаксисом:
<МЕТА HTTP-EQUIV=Refresh C0NTENT=x;URL=ContentServlet>
В части URL этого тега указывается другой ресурс. Ах — это число секунд,
которое будет ожидать браузер, прежде чем произойдет переадресация.
Все это делает код:
if (login(userName, password)) {
// посылаем cookie браузеру
Cookie d = new Cookie("userName", userName);
Cookie c2 = new Cookie("password", password);
response.addCookie(c1);
response.addCookie(c2);
response.setContentType("text/html");
Printwriter out = response.getWriter();
// response.sendRedirect здесь не работает,
// используем тег Meta для переадресации на ContentServlet
out.println(
"<МЕТА HTTP-EQUIV=Refresh CONTENTS; URL=ContentServlet»");
}
htte://kx«tx^:8060/i«rvioMCoofcieloy6erviBt
Login failed. Please try agam.
If you think you have entered the correct user name and password, the cookie setting in your browser might be off
Click here for information on how to turn it on.
Login Page
Please enter your user name and password.
Рис. 5.8. CookieLoginServlet.bmp
198
Глава 5
Листинг 5.14 CookieLoginServlet
import javax.servlet.*;
import javax.servlet.http.★;
import java.io.★;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class CookieLoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
sendLoginForm(response, false);
} z
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (login(userName, password)) {
//посылаем cookie браузеру
Cookie c1 = new Cookie("userName", userName);
Cookie c2 = new Cookie("password", password);
response.addCookie(c1);
response.addCookie(c2);
response.setContentType("text/html");
Printwriter out = response.getWriter();
//response.sendRedirect здесь не работает,
// используем тег Meta для переадресации на ContentServlet
out.println("<МЕТА HTTP-EQUIV=Refresh
CONTENTS; URL=ContentServlet>");
}
else {
sendLoginForm(response, true);
}
}
private void sendLoginForm(HttpServletResponse response, boolean
withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html");
Управление сеансом
199
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Login</TITLE>");
out. println("</HEAD>");
out.println("<BODY>");
out.println(”<CENTER>”);
if (withErrorMessage) {
out.println("Login failed. Please try again.<BR>");
out.println("If you think you have entered the correct user name” +
and password, the cookie setting in your browser might be off." +
"<BR>Click <A HREF=InfoPage.html>here</A> for information" +
on how to turn it on.<BR>");
}
out.println("<BR>");
out.println("<BR><H2>Login Page</H2>");
out.printin(”<BR>");
out.println(”<BR>Please enter your user name and password.");
out.printin("<BR>");
out.println(”<BR><FORM METH0D=P0ST>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>User Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=userName></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password:</TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out. println("<INPUT TYPE=SUBMIT VALUE=LoginX/TD>");
out.println("</TR>");
out.println("</TABLE>");
out.println("</FORM>");
out.printin("</CENTER>");
out.println("</B0DY>”);
out.println(”</HTML>");
}
public static boolean login(String userName, String password) {
try {
Class, forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
200
Глава 5
Листинг 5.14 Продолжение
Statement s = con.createStatement();
String sql = "SELECT UserName FROM Users" +
WHERE UserName='" + StringUtil.fixSqlFieldValue(userName) + .. +
AND Password="' + StringUtil.fixSqlFieldValue(password) + ....;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.closeO;
s.close();
con.close();
return true;
}
rs.closeO;
s.closeO;
con.closeO;
f
}
catch (ClassNotFoundException e) {
System, out. println(e.toStringO);
}
catch (SQLException e) {
System, out. println(e. toStringO);
}
catch (Exception e) {
System, out. println(e. toStringO);
return false;
}
Второй ресурс (например, другой сервлет) должен проверить присутствие
двух cookie, прежде чем выводить их содержимое. Для демонстрации этого со-
здадим сервлет с именем ContentServlet. CookieLoginServlet будет переадресо-
вывать пользователя наэтотсервлет при успешной регистрации. ContentServlet
показан в листинге 5.15.
Листинг 5.15 ContentServlet
import javax.servlet.*;
import javax.servlet.http.★;
import java.io.*;
import java.util.★;
Управление сеансом
201
public class ContentServlet extends HttpServlet {
public String loginUrl = "CookieLoginServlet";
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
Cookie[] cookies = request.getCookiesO;
int length = cookies.length;
String userName = null;
String password = null;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("userName"))
userName = cookie.getValue();
else if (cookie.getName().equals("password"))
password = cookie.getValue();
'}
if (userName==null || password==null ||
!CookieLoginServlet.login(userName, password))
response.sendRedirect(loginUrl);
// Это авторизованный пользователь, можно вывести содержимое
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Welcome</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("Welcome.");
out.println("</BODY>");
out.println("</HTML>");
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
doGet(request, response);
202
Глава 5
Может возникнуть вопрос: зачем создавать два cookie для имени пользова-
теля и пароля? Нельзя ли использовать один cookie, который будет содержать
значение true в случае успешной регистрации?
ContentServlet сможет найти этот cookie и не должен будет снова аутенти-
фицировать имя пользователя и пароль, исключая тем самым работу с базой
данных.
В действительности это делается в целях безопасности. Выяснив, какой флаг
несет успешный cookie, опытный пользователь может создать cookie на клиен-
тской стороне. Тогда он сможет пройти аутентификацию, не зная подлинного
имени пользователя и пароля.
Предупреждение об отказе в переадресации
Если для управления сеансами пользователя применяются cookie, то при
выполнении переадресации может возникнуть ошибка. В коде, поддерживаю-
щем переадресациюпользователя, необходимо предоставить ссылку, которую
пользователь сможет применить вручную в случае отказа автоматической пе-
реадресации.
(Примечание'} Если ПРИ тестировании cookie оказывается, что код работает
4,1 не так как ожидается, закройте браузер, чтобы удалить cookie.
Устойчивые cookie
Cookie, создаваемые в предыдущем примере, существуют, пока открыт брау-
зер. Когда браузер закрывается, cookie удаляются. Можно использовать устой-
чивые cookies, которые будут храниться дольше. Класс javax.servlet.http.Cookie
имеет метод setMaxAge, который задает максимальное время жизни cookie в
секундах.
В следующем примере создается сервлет Login, в котором применяется
cookie с периодом жизни 10 000 секунд. Если пользователь закрывает браузер,
но возвращается в течение 10 000 секунд, ему не нужно будет снова вводить
имя пользователя. На рис. 5.9 показана страница Login, где имя пользователя
было подставлено сервером.
Сервлет называется PersistentCookieServlet и представлен в листинге 5.16.
Л исти нг 5.16 PersistentCookieServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
Управление сеансом
203
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class PersistentCookieServlet extends HttpServlet {
String persistedUserName;
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
Cookie[] cookies = request.getCookiesO;
int'length = cookies.length;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("userName”))
persistedUserName = cookie. getValueO;
}
sendLoginForm(response, false);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (login(userName, password)) {
// посылаем cookie браузеру
Cookie c1 = new Cookie("userName", userName);
Cookie c2 = new Cookie("password", password);
c1.setMaxAge(10000);
response.addCookie(c1);
response.addCookie(c2);
response.setContentType("text/html”);
Printwriter out = response.getWriterO;
//response.sendRedirect здесь не работает,
// используем тег Meta для переадресации на ContentServlet
out.println("<META HTTP-EQUIV=Refresh
CONTENTS; URL=ContentServlet>");
}
else {
sendLoginForm(response, true);
}
}
204
Глава 5
Рис. 5.9. Сервлет с устойчивым cookie
Листинг 5.16 Продолжение
private void sendl_oginForm(HttpServletResponse response, boolean
withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Login</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>");
if (withErrorMessage) {
out.println("Login failed. Please try again.<BR>");
out.println("If you think you have entered the correct user name" +
" and password, the cookie setting in your browser might be off." +
"<BR>Click <A HREF=InfoPage.html>here</A> for information" +
" on how to turn it on.<BR>");
}
out.println("<BR>");
out.println("<BR><H2>Login Page</H2>");
out.p rintln("<BR>");
out.println("<BR>Please enter your user name and password.");
out.println("<BR>");
out.println("<BR><FORM METHOD=POST>");
Управление сеансом
205
out. println (” СТАВ LE> ’’) ;
out.println(”<TR>”);
out.println("<TD>User Name:</TD>”);
out.print("<TD><INPUT TYPE=TEXT NAME=userName");
if (persistedUserName! =null)
out.print(" VAI_UE=\”" + persistedUserName +
out. print(’’x/TD>’’);
out.println("</TR>");
out.println(”<TR>”);
out.println("<TD>Password:</TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.printing "</TR>");
out. println("<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out.println("<INPUT TYPE=SUBMIT VALUE=Login></TD>");
out.println("</TR>");
out.println("</TABLE>");
out. println("</FORM>");
out.println("</CENTER>");
out. println("</BQDY>");
out.println("</HTML>");
}
public static boolean login(String userName, String password) {
try {
Class. fо rName("sun.j dbc.odbc.JdbcOdbcDriver");
Connection con = riverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
" WHERE UserName=’" + StringUtil.fixSqlFieldValue(userName) + +
" AND Password="' + StringUtil.fixSqlFieldValue(password) + ....;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.closeO;
s.close();
con.closeO;
return true;
}
rs.closeO;
s.closeO;
206
Глава 5
Листинг 5.16 Продолжение
con.close();
}
catch (ClassNotFoundException е) {
System.out.println(e.toString());
catch (SQLException e) {
System.out.println(e.toString());
}
catch (Exception e) {
System, out. println(e. toStringO);
}
return false;
Когда пользователь запрашивает PersistentCookieServlet, в первый ли раз
или в очередной раз, вызывается метод doGet. Этот метод просматривает
совокупность Cookie, полученную из метода getCookies() интерфейса
javax.servlet.http.HttpServletResponse. Для каждого Cookie код проверяет, не имеет
ли он имя «userName». Если это так, значение cookie присваивается полю
persistentUserName:
Cookie[] cookies = request. getCookiesO;
int length = cookies.length;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("userName"))
persistedUserName = cookie. getValueO;
}
Затем метод doGet вызывает метод sendLoginForm для отправки формы
Login, с помощью которой пользователь может зарегистрироваться:
sendLoginForm(response, false);
Когда пользователь посылает форму Login, вызывается метод doPost сервлета
PersistentCookieServlet. Сначала метод doPost пытается извлечь значения userName
и password, введенные пользователем:
String userName = request.getParameter("userName");
String password = request.getParameter("password");
Управление сеансом
207
Затем он посылает значения userName и password в метод login. Этот метод
возвращает true, если userName и password являются действительными. При
успешной регистрации будут созданы два cookie, cl и с2. cl имеет имя userName,
а с2 — password:
if (login(userName, password)) {
// посылаем cookie браузеру
Cookie d = new Cookie("userName”, userName);
Cookie c2 = new Cookie("password", password);
Срок существования cookie cl задается равным 10 000 секунд с помощью
метода setMaxAge следующим образом:
c1.setMaxAge(10000);
Затем метод doPost посылает оба cookie, cl и с2, браузеру и переадресует
браузер на другой сервлет:
response.addCookie(c1);
response.addCookie(c2);
response.setContentType("text/html”);
Printwriter out = response.getWriter();
// response.sendRedirect здесь не работает,
// используем тег Meta для переадресации на ContentServlet
out/println(
”<МЕТА HTTP-EQUIV=Refresh CONTENTS; URL=ContentServlet>”);
Если пользователь вернется в течение этих 10 000 секунд, браузер пошлет
cookie userName на сервер, и cookie будет обнаружен в методе doGet.
Метод login, вызванный из метода doPost, устанавливает соединение с ба-
зой данных и посылает следующий оператор SQL:
SELECT UserName FROM Users
WHERE UserName='userName'
AND Password^'password'
Если оператор SQL возвращает непустой объект ResultSet, то учетная
запись пользователя с указанным именем и паролем найдена, и метод login
возвращает true:
public static boolean login(String userName, String password) {
try {
208
Глава 5
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
WHERE UserName='" + StringUtil.fixSqlFieldValue(userName) +
AND Password='" + StringUtil.fixSqlFieldValue(password) +
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
rs.close();
s.close();
con.close();
return true;
}
rs.close();
s.close();
con.close();
}
catch (ClassNotFoundException e) {
System, out. println(e. toStringO);
}
catch (SQLException e) {
System, out. println(e. toStringO);
}
catch (Exception e) {
System.out.println(e.toString());
}
return false;
}
}
Проверка поддержки cookie
Все связанные c cookie примеры предполагают, что в браузере пользователя
включена поддержка cookie. Браузеры поставляются с включенной поддерж-
кой cookie, но пользователь может ее отключить. Одним из подходов к реше-
нию этой проблемы является отправка предупреждающего сообщения, если
приложение работает не так, как ожидается. Другая возможность состоит в
автоматической проверке этой настройки. В качестве примера рассмотрим сер-
влет с именем CheckCookieServlet.
Сервлет делает простую вещь. Он посылает ответ, который заставляет брау-
зер обратиться второй раз. С первым ответом передается cookie. Когда браузер
обращается второй раз, сервлет проверяет, содержит ли запрос посланный
перед этим cookie. Если cookie присутствует, можно считать, что поддержка
cookie в браузере включена. Иначе, возможно, пользователь применяет очень
Управление сеансом
209
старый браузер, который вообще не понимает cookie, или поддержка cookie в
браузере выключена.
Сервлет CheckCookieServlet показан в листинге 5.17.
Листинг 5.17 CheckCookieServlet
import javax.servlet.*;
import javax.servlet.http.★;
import java.io.★;
import java.util.*;
public class CheckCookieServlet extends HttpServlet {
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriter();
if (request.getParameter(”flag”)==null) {
// первый запрос
Cookie cookie = new Cookie(”browserSetting", ”on");
response.addCookie(cookie);
String nextllrl = request.getRequestURI() + "?flag=1";
out.println("<META HTTP-EQUIV=Refresh CONTENTS; URL=" + nextUrl +”>");
}
else {
// второй запрос
Cookie[] cookies = request.getCookiesO;
if (cookies!=null) {
int length = cookies.length;
boolean cookieFound = false;
for (int i=0; Klength; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("browsersetting") &&
cookie.getValue().equals(”on")) {
cookieFound = true;
break;
}
}
if (cookieFound) {
out.println(”Your browser's cookie setting is on.");
}
else {
out.println("Your browser does not support cookies or" +
" the cookie setting is off.");
210
Глава 5
Листинг 5.17 Продолжение
}
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
doGet(request, response);
}
}
Объекты сеанса
Из четырех методов управления сеансом, рассмотренных в этой главе, объект
Session, предоставляемый интерфейсом javax.servlet.http.HttpSession, является
самым простым в применении и самым мощным. Для каждого пользователя сер-
влет может создать объект HttpSession, который ассоциируется только с этим
пользователем и может быть доступен только этому конкретному пользователю.
Объект HttpSession действует как Hashtable, где можно хранить любое число пар
ключ/объект. Объект HttpSession доступен другим сервлетам в том же приложе-
нии. Чтобы извлечь сохраненный ранее объект, необходимо только передать ключ.
Объект HttpSession использует cookie или перезапись URL для отправки
клиенту маркера. Если для передачи идентификаторов сеансов используются
cookie, браузеры клиентов должны принимать cookie.
Однако в отличие от предыдущих методов сервер не передает никаких зна-
чений. Он посылает только уникальное число, называемое идентификатором
сеанса. Этот идентификатор применяется для соединения пользователя с
объектом Session на сервере. Поэтому если одновременно работают 10 пользо-
вателей, на сервере будет создано 10 объектов Session, и каждый пользователь
сможет получить доступ только к своему собственному объекту HttpSession.
Способ создания объекта HttpSession и его извлечения при последующих
запросах показан на рис. 5.10.
При отслеживании сеанса с помощью объекта HttpSession выполняются
четыре шага:
1. Сервлетом с именем Servlet 1 создается объект HttpSession. Для этого объекта
генерируется идентификатор сеанса. В примере идентификатор сеанса
равен 1234, но в реальности контейнер сервлетов будет генерировать
более длинное случайное число, которое гарантированно будет уникаль-
ным. Затем объект HttpSession сохраняется на сервере и ассоциируется с
созданным идентификатором сеанса. Программист также может сохра-
нить значения сразу после создания HttpSession.
Управление сеансом
211
Рис. 5.10. Отслеживание сеанса с помощью объекта HttpSession
2. В ответе сервлет посылает идентификатор сеанса браузеру клиента.
3. Когда браузер клиента запрашивает другой ресурс в том же приложении, на-
пример Servlet2, идентификатор сеанса посылается назад серверу в объекте
javax.servlet.http. HttpServletRequest и передается Servlet2.
4. Чтобы клиент получил доступ к объекту HttpSession, сервлет Servlet2 ис-
пользует метод getSession интерфейсаjavax.servlet.http.HttpServletRequest.
Этот метод автоматически извлекает идентификатор сеанса из запроса
и получает объект HttpSession, ассоциированный с идентификатором
клиента.
А что если после создания объекта HttpSession пользователь
больше не возвращается? Тогда контейнер сервлетов
ожидает заданный период времени и удаляет объект
HttpSession. Одной из проблем при использовании объектов
Session является масштабируемость. В некоторых
контейнерах сервлетов объекты Session хранятся в
оперативной памяти, и когда число пользователей
превышает определенный предел, сервер начинает
испытывать нехватку оперативной памяти.
Одним из решений этой проблемы является сохранение
объектов Session в базе данных или на диске. Однако
спецификация сервлетов 2.3 нечетко определяет, как
контейнер сервлетов должен это делать. В случае
применения Tomcat 4 объекты Session перемещаются во
вторичную память, когда число объектов Session превышает
некоторое значение.
2/2
Глава 5
Метод getSession интерфейса javax.servlet.http.HttpServletRequest имеет две
перегружаемые версии:
• HttpSession getSession()
• HttpSession getSession(boolean create)
Первая версия возвращает текущий сеанс, связанный с запросом: если зап-
рос не имеет идентификатора сеанса, создается новый идентификатор.
Вторая версия возвращает объект HttpSession, ассоциированный с запро-
сом, если в запросе содержится действительный идентификатор сеанса. Если
в запросе нет действительного идентификатора, то создание нового объекта
HttpSession зависит от значения create. Если это значение равно true, создается
новый объект HttpSession. Иначе метод getSession возвращает null.
Теперь более подробно рассмотрим интерфейс javax.servlet.http. HttpSession.
Интерфейс javax.servlet. http. HttpSession
Этот интерфейс имеет следующие методы:
• getAttribute
• getAttributeNames
• getCreationTime
• getld
• get Last AccessedTime
• getMaxInactivelnterval
• getServletContext
• getSessionContext
• get Value
• getValueNames
• invalidate
• isNew
• putValue
• removeAttribute
• removeValue
• setAttribute
• setMaxInactivelnterval
Каждый из методов описывается ниже.
getAttribute
Этот метод извлекает атрибут из объекта HttpSession. Возвращаемое значение
является объектом типа Object; поэтому может понадобиться привести атрибут к
его исходному типу данных. Для извлечения атрибута в метод передается связан-
Управление сеансом
213
ное с атрибутом имя. Метод возвращает исключение IllegalStateException, если он
применяется к недействительному объекту HttpSession.
Сигнатура этого метода:
public Object getAttribute(String name) throws IllegalStateException
getAttributeNames
Метод getAttributeNames возвращает java.util.Enumeration, содержащее все
имена атрибутов объекта HttpSession. Этот метод возвращает исключение
IllegalStateException, если он применяется к недействительному объекту
HttpSession.
Сигнатура метода:
public java.util.Enumeration getAttributeNamesO
throws IllegalStateException
getCreationTime
Метод getCreationTime возвращает время создания объекта HttpSession в
миллисекундах, прошедших с 1 января 1970 г. 00:00:00 GMT. Этот метод
возвращает исключение IllegalStateException, если он применяется к недей-
ствительному объекту HttpSession.
Сигнатура метода:
public long getCreationTime() throws IllegalStateException
getld
Метод getld возвращает идентификатор сеанса. Сигнатура этого метода:
public String getld()
getLastAccessedTime
Метод getLastAccessedTime возвращает время, когда клиент последний раз
получал доступ к объекту HttpSession. Возвращаемое значение является чис-
лом миллисекунд, прошедших с 1 января 1970 г. 00:00:00 GMT. Метод имеет
следующую сигнатуру:
public long getLastAccessedTimeO
getMaxInactivelnterval
MeroflgetMaxInactivelnterval возвращает число секунд, втечение которых объект
HttpSession будет сохраняться контейнером сервлетов после последнего обраще-
ния к этому объекту, прежде чем он будет удален. Метод имеет сигнатуру:
public int getMaxInactivelnterval!)
getServletContext
Метод getServletContext возвращает объект javax.servlet.ServletContext, ко-
торому принадлежит объект HttpSession. Метод имеет сигнатуру:
214
Глава 5
public javax.servlet.ServletContext getServletContext
getSessionContext
Этот метод не рекомендуется использовать.
getValue
Этот метод не рекомендуется использовать.
getValueNames
Этот метод не рекомендуется использовать.
invalidate
Метод invalidate делает недействительным объект HttpSession и отсоединя-
ет все связанные с ним объекты. Этот метод порождает исключение
IllegalStateException, если применяется к уже недействительному объекту
HttpSession. Метод имеет сигнатуру:
public void invalidate() throws IllegalStateException
isNew
Метод isNew указывает, что объект HttpSession был создан в этом запросе и
клиент еще не присоединился к отслеживанию сеанса. Этот метод возвращает
исключение IllegalStateException, если применяется к недействительному объек-
ту HttpSession. Метод имеет сигнатуру:
public boolean isNew() throws IllegalStateException
putValue
Этот метод не рекомендуется использовать.
removeAttribute
Метод removeAttribute удаляет атрибут, связанный с объектом HttpSession.
Этот метод возвращает IllegalStateException, если применяется к недействи-
тельному объекту HttpSession.
Метод имеет следующую сигнатуру:
public void removeAtribute(String name) throws IllegalException
removeValue
Этот метод не рекомендуется использовать.
setAttribute
Метод setAttribute добавляет пару имя/атрибут в объект HttpSession. Этот
метод возвращает IllegalStateException, если применяется к недействительно-
му объекту HttpSession. Метод имеет сигнатуру:
Управление сеансом
215
public void setAttribute(String name, Object attribute) throws
IllegalStateException
setMaxInactivelnterval
Метод setMaxInactivelnterval задает число секунд с момента доступа к объекту
HttpSession, которое будет ожидать контейнер сервлетов, прежде чем удалить
объект HttpSession. Сигнатура метода:
public void setMax!nactiveInterval(int interval)
Передача отрицательного значения в метод сделает объект HttpSession бес-
срочным (т. е. он никогда не будет удален).
Использование объекта HttpSession
Следующий пример является модификацией страницы Login, в нем исполь-
зуются объекты HttpSession. Нет необходимости создавать два cookie для имени
пользователя и пароля. Так как только сервер может сохранять и извлекать зна-
чения из объекта HttpSession, можно создать ключ с именем loggedln и присво-
ить ему значение «true», если пользователь успешно зарегистрировался раньше.
Этот код представлен в листинге 5.18.
Листинг 5.18 SessionLoginServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import com.brainysoftware.java.StringUtil;
public class SessionLoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
sendLoginForm(response, false);
}
public void doPost(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
String userName = request.getParameter("userName”);
String password = request.getParameter("password");
if (login(userName, password)) {
216
Глава 2
Листинг 5.18 Продолжение
//отправка cookie браузеру
HttpSession session = request.getSession(true);
session.setAttribute("loggedIn", new String("true"));
response.sendRedirect("Content2Servlet");
}
else {
sendLoginForm(response, true);
}
}
private void sendl_oginForm(HttpServletResponse response, boolean
withErrorMessage)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>”);
out.println(”<HEAD>”);
out.println(”<TITLE>Login</TITLE>”); >
out.println(”</HEAD>”);
out.println(”<BODY>”);
out.println(”<CENTER>”);
if (withErrorMessage) {
out.println(”Login failed. Please try again.<BR>”);
out.println(”If you think you have entered the correct user name” +
" and password, the cookie setting in your browser might be off." +
"<BR>Click <A HREF=InfoPage.html>here</A> for information" +
" on how to turn it on.<BR>");
}
out.println("<BR>");
out.println("<BR><H2>Login Page</H2>”);
out:println("<BR>");
out.println("<BR>Please enter your user name and password.”);
out.println("<BR>”);
out.println("<BR><FORM METHOD=POST>");
out.println("<TABLE>");
out.println("<TR>”);
out.println(”<TD>User Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=userName></TD>");
out.println("</TR>");
out.println(”<TR>”);
out.println("<TD>Password:</TD>");
out.println("<TD><INPUT TYPE=PASSWORD NAME=passwordx/TD>”);
out.println(”</TR>");
Как устроены сервлеты
217
out.println(”<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out. println("<INPUT TYPE=SUBMIT VALUE=LoginX/TD>");
out.println("</TR>");
out.println("</TABLE>");
out. println( "</FORM>");
out.println("</CENTER>");
out.println("</B0DY>");
out. println("</HTML>");
}
public static boolean login(String userName,. String password) {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT UserName FROM Users" +
" WHERE UserName=’" + StringUtil.fixSqlFieldValue(userName) + .. +
AND Password^”’ + StringUtil.fixSqlFieldValue(password) + ....;
ResultSet rs = s.executeQuery(sql);
if (rs.next()) {
rs.closeO;
s.close();
con.close();
return true;
}
rs.closeO;
s.closeO;
con.closeO;
}
catch (ClassNotFoundException e) {
System. out. println(e. toStringO);
}
catch (SQLException e) {
System.out.println(e.toString());
}
catch (Exception e) {
System, out. println(e. toStringO);
}
return false;
}
}
218
Глава 5
Листинг 5.19 Content2Servlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class Content2Servlet extends HttpServlet {
public String loginUrl = "SessionLoginServlet”;
/** Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
HttpSession session = request.getSession();
if (session==null)
response.sendRedi rect(loginUrl);
else {
String loggedln =(String) session.getAttribute(”loggedIn");
if (!loggedln.equals("true"))
response.sendRedi rect(loginUrl);
}
// Это авторизованный пользователь, можно вывести содержимое
response.setContentType("text/html”);
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Welcome</TITLE>”);
out.println("</HEAD>");
out.println(”<BODY>");
out.println("Welcome.");
out.println(”</BODY>");
out.printIn("</HTML>");
/** Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
doGet(request, response);
Управление сеансом
219
Отслеживание сеанса с помощью перезаписи URL
Как упоминалось ранее, по умолчанию идентификатор сеанса посылается
браузеру клиента и обратно на сервер с помощью cookie. Это означает, что в
браузере клиента должна быть включена поддержка cookie, и необходимо вы-
полнять проверку cookie браузера.
В качестве альтернативы можно передавать идентификаторы сеансов в URL
с помощью техники перезаписи URL. К счастью, API Servlet предоставляет про-
стой способ добавления идентификатора сеанса в URL — с помощью метода
encodeURLna интерфейса]ауах.5егу1е1.ЬПр.HttpServletResponse. Например, код:
out.println(”<a href=Servlet2>Click here</a>”);
должен быть заменен следующим:
out.println(”<a href=” + response.encodeURL(”Servlet2") +
">Click here</a>");
Идентификатор сеанса будет добавлен в конец строки. Результирующий
URL будет, например, таким:
http://localhost:8080/myApp/servlet/
Servlet2;jsessionid=AB348989283429489234At
где число, идущее после jsessionid=, является идентификатором пользователя.
Одним из недостатков использования для пересылки идентификатора се-
анса перезаписи U RL, в сравнении с cookie, состоит втом, что перезапись U RL
не будет действовать после просмотра статических страниц. Например, если
во время своего сеанса пользователь заходит на страницу HTML, то иденти-
фикатор сеанса будет потерян.
Решение (правда, не очень удобное) состоит в преобразовании всех стати-
ческих страниц в сервлеты или страницы JSP.
Выбор метода
После знакомства с четырьмя методами управления пользовательскими сеан-
сами возникает вопрос, какой из них выбрать для реализации.
Очевидно, что применение объектов Session является самым простым.
Следует остановиться на этом способе, если используемый контейнер серв-
летов поддерживает перенос объектов Session из оперативной памяти во вто-
ричную память. В Tomcat 4 это свойство доступно.
220
Глава 5
Одной из проблем при использовании объектов Session является поддерж-
ка cookie браузером клиента. В случае возникновения этой проблемы:
• Можно протестировать поддержку cookie, как описано выше.
• Можно использовать перезапись URL.
Добавление идентификатора сеанса в иКЬявляется хорошим методом. Он
создает некоторую дополнительную работу для программиста, однако осво-
бождает от необходимости полагаться на cookie.
Использование cookie является не таким гибким, как применение объектов
Session. Однако cookie более предпочтительны в случаях, когда нежелатель-
но, чтобы сервер хранил какую-либо связанную с клиентом информацию,
или если требуется, чтобы клиентская информация сохранялась после зак-
рытия браузера.
Скрытые поля используются редко. Однако если необходимо разделить
форму на несколько меньших форм, то скрытые поля являются самым деше-
вым и наиболее эффективным методом. Не требуются серверные ресурсы для
временного хранения значений из предыдущих форм, и не нужно полагаться
на cookie.
Заключение
В этой главе вы узнали, что протокол HTTP не поддерживает состояние, и это
вызываетопределенные проблемы. Были представлены четыре метода, которые
можно использовать для управления сеансами пользователей, и приведены
примеры их применения.
6
События
приложения
и сеанса
главе 3 было показано, как с помощью объекта ServletContext сделать ин-
фсрмацию доступной всем сервлетам в web-приложении. Вы также узнали, как
получить этот объект и использовать его методы. Объект ServletContext создается
контейнером сервлетов, и помимо всего прочего он имеет метод setAttribute. Этот
метод заставляет объект действовать как Hashtable, где можно сохранять объект,
идентифицируемый ключом, и извлекать объект, передавая этот ключ.
В главе 5 рассказывалось о том, как с помощью объекта HttpSession запоми-
нать информацию о сеансе пользователя. Аналогично объекту ServletContext объект
HttpSession позволяет сохранять пары ключ/объект. В отличие от ServletContext,
который доступен всем сервлетам, объект HttpSession связан с определенным
пользователем и доступен только этому пользователю.
В этой главе рассматривается новое свойство спецификации API сервлетов
2.3, которое имеет дело с объектами ServletContext и HttpSession. Это свойство
поддерживает события уровня приложения и сеанса. Tomcat 4 реализует это
новое свойство.
Концепция очень проста. API сервлетов 2.3 позволяет получить уведомление
о том, что создается или разрушается объект ServletContext или что атрибут
(пара ключ/объект) создается, удаляется или заменяется. Моменты исполнения
этих операций являются событиями Java.
Конечно, можно обойтись и без событий. Однако они весьма полезны, так как
можно написать код, который вызывается автоматически, когда происходит одно
222
Глава 6
из событий. Например, известно, что объект ServletContext создается контейнером
сервлетов при инициализации. В ответ на это событие можно, скажем, загрузить
драйвер JDBC, или создать объект соединения с базой данных, или инициали-
зировать URL соединения и сохранить его как переменную в объекте
ServletContext, чтобы она была доступна во всех сервлетах web-приложения.
Для событий, инициализируемых при разрушении объекта ServletContext,
можно написать код, который выполняет некоторую очистку, закрывает файлы
или завершает соединение с базой данных.
То же самое применимо к событиям, связанным с атрибутами ContextServlet.
Например, можно сделать счетчик страниц, значение которого записывается
в файл каждый раз, когда значение изменяется. При условии, что значение
этого счетчика сохраняется как атрибут в объекте ServletContext, можно напи-
сать код ввода/вывода, который автоматически выполняется при изменении
атрибута в ServletContext, обеспечивая тем самым точный подсчет.
Кроме того, с помощью событий можно узнавать, когда создается или де-
лается недействительным объект HttpSession либо когда атрибут объекта
HttpSession добавляется, удаляется или заменяется.
В этой главе рассматриваются различные классы событий и интерфейсы
приемников, которые поддерживают уведомление о событии изменения со-
стояния ServletContext и HttpSession.
Прием событий приложения
На уровне приложения пакет javax.servlet предоставляет два интерфейса при-
емника, которые поддерживают уведомление о событиях изменения состоя-
ния объекта ServletContext: интерфейс ServletContextListener и интерфейс
ServletContextAttributesListener.
Интерфейс ServletContextListener
Интерфейс ServletContextListener можно использовать для отслеживания
событий жизненного цикла ServletContext. Его сигнатура имеет вид:
public interface ServletContextListener extends java.util.EventListener
Класс приемника должен реализовать этот интерфейс для приема событий
жизненного цикла ServletContext. Интерфейс ServletContextListener имеет два
метода: contextlnitialized и context Destroyed. Сигнатуры этих методов имеют
следующий вид:
public void context!nitialized(ServletContextEvent see)
public void contextDestroyed(ServletContextEvent see)
Событияприложения и сеанса
223
Метод contextlnitialized вызывается, когда web-приложение готово обслу-
живать запросы. Этот метод автоматически вызывается контейнером сервлетов
позавершении его собственного процесса инициализации. Можно написать код,
который должен выполняться при инициализации приложения, например:
загрузка драйвера JDBC, создание объекта соединения с базой данных или при-
своение начальных значений глобальным переменным.
Метод contextDestroyed вызывается, когда требуется закрыть контекст сервлета.
Этот метод можно использовать для написания кода, который будет выполняться
при закрытии приложения. Например, он может закрыть соединение с базой
данных или внести запись в журнал.
Использование метода contextlnitialized аналогично написанию кода в ме-
тоде сервлета init(), а метод contextDestroyed производит эффект, подобный
методу сервлета destroy(). Однако использование событий приложения делает
код доступным для всего приложения, а не только для сервлета.
Класс ServletContextEvent
Как было показано, оба метода интерфейса ServletContextListener передают
класс ServletContextEvent. Этот класс имеет сигнатуру:
public class ServletContextEvent extends java.util.Eventobject
ServletContextEvent имеет только один метод: getServletContext. Этот метод
возвращает ServletContext, который изменяется.
Дескриптор развертывания
Чтобы можно было использовать события приложения и добавить класс
приемника сервлета, необходимо сообщить об этом контейнеру сервлетов,
зарегистрировав класс приемника в дескрипторе развертывания. Элемент <web-
арр> должен содержать элемент <listener>:
<web-app>
<listener>
<listener-class>
AppLifeCycleEvent
</listener-class>
</listener>
</web-app>
Элемент <listener> должен идти перед частью <servlet>.
Пример: класс AppLifeCycleEvent
Рассмотрим класс приемника, который отслеживает события жизненного
цикла ServletContext. Он выводит строку «Application initialized», когда ини-
циализируется объект ServletContext, и строку «Application destroyed», когда
ServletContext разрушается. Код программы представлен в листинге 6.1.
224
Глава 6
Л исти н г 6.1 Класс AppLifeCycleEvent
import javax.servlet.ServletContextListener;
impo rt j avax.se rvlet.Se rvletContext Event;
public class AppLifeCycleEvent implements ServletContextListener {
public void context!nitialized(ServletContextEvent cse) {
System.out.printin("Application initialized");
}
public void contextDestroyed(ServletContextEvent cse) {
System.out.println("Application shut down");
}
}
Чтобы класс AppLifeCycleEvent работал, необходимо зарегистрировать его в
дескрипторе развертывания. Дескриптор развертывания показан в листинге 6.2.
Листинг6.2 Дескриптор развертывания для класса AppLifeCycleEvent
<?xml version="1.О" encoding="IS0-8859-1”?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<listener>
<listener-class>
AppLifeCycleEvent
</listener-class>
</listener>
</web-app>
Теперь перезапустите Tomcat и понаблюдайте за окном консоли. Вы увиди-
те на экране строку «Application initilized».
При желании можно использовать более одного класса приемника. Для это-
го перечислите все классы приемников в дескрипторе развертывания следую-
щим образом:
<?xml version="1.О" encoding="IS0-8859-1"?>
<’DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://java.sun.com/dtd/web-app_2_3.dtd">
Событияприложения и сеанса
225
<web-app>
<listener>
<listener-class>
AppLifeCycleEventl
</listener-class>
</listener>
<listener>
<listener-class>
AppLifeCycleEvent2
</listener-class>
</listener>
</web-app>
Если используется подобный дескриптор развертывания, то при запуске
Tomcat будет вызываться метод contextlnitialized в классе AppLifeCycleEventl,
а затем тот же метод в классе AppLifeCycleEventl.
Пример: загрузка драйвера JDBC и задание атрибута
ServletContext
В следующем примере используется метод contextlnitialized интерфейса
ServletContextListener для загрузки драйвера JDBC и для установки атрибута
ServletContext с именем «dbUrl» в значение «jdbc:mysql///Fred». Загрузка драй-
вера JDBC делает его доступным при следующем обращении к базе данных.
Этот пример имеет два класса: класс приемникас именем AppLifeCycleEventDemo
и сервлет с именем ApplicationEventDemoServlet. Класс приемника предос-
тавляет метод (contextlnitialized), который будет вызываться автоматически при
инициализации объекта ServletContext. Именно здесь размещается код, кото-
рый загружает драйвер JDBC и устанавливает атрибут ServletContext. Сервлет
используется для вывода значения атрибута. Класс AppLifeCycleEventDemo по-
казан в листинге 6.3, a ApplicationEventDemoServlet — в листинге 6.4.
Листинг 6.3 AppLifeCycleEventDemo
import javax.servlet.ServletContext;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
public class AppLifeCycleEventDemo implements ServletContextListener {
public void context!nitialized(ServletContextEvent see) {
System.out.println("Initializing Application ...");
// Загрузка драйвера JDBC
try {
226
Глава 6
Листинг 6.3 Продолжение
Class. forName(’’org. gjt. mm. mysql. Driver
}
catch (ClassNotFoundException e) {
System, out. print ln(e. toStringO);
}
// Получение объекта ServletContext
ServletContext ServletContext = sce.getServletContextO;
// Установка атрибута ServletContext
ServletContext.setAttribute("dbUrl", ”jdbc:mysql///Fred");
System. out. println(’’Application initialized");
}
public void contextDestroyed(ServletContextEvent cse) {
System.out.printing’Application shut down");
}
}.
Отметим, что прежде чем можно будет задать атрибут ServletContext, необ-
ходимо получить объект ServletContext с помощью метода getServletContext
класса ServletContextEvent:
ServletContext ServletContext = sce.getServletContextO;
В этом сервлете для получения атрибута из объекта ServletContext необхо-
димо сначала получить объект ServletContext. В сервлете, который расширяет
классjavax.servlet.http.HttpServlet, можно использовать методgetServletContext,
как показано в листинге 6.4.
Л истинг 6.4 Сервлет ApplicationEventDemoServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ApplicationEventDemoServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.printIn("<HTML>");
out.println("<HEAD>");
out. println("<TITLE>Application Event Demo Servlet</TITLE>’’);
Событияприложения и сеанса
227
out.println(”</Н EAD>”);
out.println("<BODY>");
out.println("Your database connection is ”);
// Получение объекта ServletContext
ServletContext ServletContext = getServletContextO;
// Вывод атрибута "dbUrl”
out.println(servletContext.getAtt ribute("dbUrl"));
out.println("</BODY>");
out.println("</HTML>");
}
}
Листинг 6.5 представляет дескриптор развертывания, который нужен для
работы приложения.
Л истинг 6.5 Дескриптор развертывания
<?xml version="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<listener>
<listener-class>
AppLifeCycleEventDemo
</listener-class>
</listener>
<servlet>
<servlet-name>
ApplicationEventDemo
</servlet-name>
<servlet-class>
ApplicationEventDemoServlet
</servlet-class>
</servlet>
</web-app>
После компиляции обоих классов перезапустите Tomcat и задайте в браузере
URL сервлета. Должно появиться что-то похожее на рис. 6.1.
228
Глава 6
Рис. 6.1. Установка значения атрибута при инициализации приложения
Прием в ServletContextAttributeListener
В дополнение к приемнику событий жизненного цикла можно реализовать
класс ServletContext Attribute Listener, который будет получать уведомление о
том, что в ServletContext добавлен атрибут или что какой-либо из атрибутов
ServletContext изменен или удален. Необходимо предоставить реализации трех
методов этого интерфейса: attributeAdded, attribute Removed и attributeReplace.
Сигнатуры этих методов:
public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeRemoved(ServletContextAttributeEvent scae)
public void attributeReplaced(ServletContextAttributeEvent scae)
Метод attributeAdded вызывается при добавлении нового атрибута в объект
ServletContext. Метод attribute Removed вызывается, когда атрибут удаляется из
объекта ServletContext, а метод attribute Replaced вызывается в случае замены
атрибута в объекте ServletContext.
Отметим, что изменения атрибутов могут происходить одновременно. Обес-
печение синхронизации доступа к общим ресурсам входит в обязанности про-
граммиста.
Пример: PageCounterServlet
В качестве примера рассмотрим приложение, которое предоставляет счет-
чик страниц для сервлета. Сервлет увеличивает счетчик при каждом запросе.
Значение счетчика сохраняется как атрибут объекта ServletContext, чтобы к нему
Событияприложения и сеанса
229
можно было обратиться из любого сервлета в приложении. Однако, если кон-
тейнер сервлетов разрушается, это значение не теряется, так как оно записы-
вается в текстовый файл counter.txt. Ясно, что приложение такого типа можно
использовать для измерения популярности сайта.
Приложение состоит из двух классов. Первый — класс приемника с име-
нем AppAttribute Event Demo, а второй — сервлет с именем PageCounterServlet.
AppAttribute Event Demo реализует интерфейсы ServletContextListener и
ServletContextAttribute Listener. В методе context Initialized записывается код, ко-
торый будет считывать значение счетчика из файла counter.txt и использовать
это значение для инициализации атрибута ServletContext с именем pageCounter.
Как и в предыдущем примере, необходимо получить объект ServletContext из
объекта ServletContextEvent, переданного в метод contextlnitialized.
При каждом запросе сервлета PageCounterServlet он увеличивает значение
pageCounter. Когда это происходит, срабатывает метод attribute Replaced класса
приемника. Код этого метода записывает значение pageCounter в файл
counter.txt.
Так как к сервлету могут обращаться одновременно несколько пользовате-
лей, метод writeCounter, вызываемый из метода attribute Replaced, является син-
хронизированным. Его код записывает pageCounter в файл.
Класс приемника представлен в листинге 6.6, а сервлет PageCounterServlet —
в листинге 6.7. После компиляции этихдвух классов откройте браузер и введи-
те URL сервлета. Вы должны увидеть что-то похожее на рис. 6.2.
You «те viator amber 16
Рис. 6.2. PageCounterServlet
Листинг 6.6 Класс AppAttributeEventDemo
import javax.servlet.ServletContext;
impo rt j avax.servlet.ServletContext Listener;
230
Глава 6
Листинг 6.6 Продолжение
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextAttributeEvent;
import java.io.*;
public class AppAttributeEventDemo
implements ServletContextListener, ServletContextAttributeListener {
int counter;
String counterFilePath = "C:\\counter.txt";
public void context!nitialized(ServletContextEvent cse) {
try {
BufferedReader reader = new
BufferedReader(new FileReader(counterFilePath));
counter = Integer. parselnt( reader. readLineO );
reader.close();
System.out.println("Reading" + counter);
}
catch (Exception e) {
System, out. println(e. toStringO);
}
ServletContext ServletContext = cse.getServletContextO;
ServletContext.setAttribute("pageCounter",
Integer.toString(counter));
System.out.println("Application initialized");
}
public void contextDestroyed(ServletContextEvent cse) {
System.out.println("Application shut down");
}
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("ServletContext attribute added");
}
public void attributeRemoved(ServletContextAttributeEvent scae) {
System.out.println("ServletContext attribute removed");
}
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("ServletContext attribute replaced");
writeCounter(scae);
}
synchronized void writeCounter(ServletContextAttributeEvent scae) {
ServletContext ServletContext = scae.getServletContextO;
Событияприложения и сеанса
231
counter = Integer.parse!nt((String)
servletContext.getAtt ribute("pageCounter"));
try {
BufferedWriter writer = new
BufferedWriter(new FileWriter(counterFilePath));
writer.write(Integer.toString(counter));
writer.close();
System.out.println("Writing");
}
catch (Exception e) {
System, out. println(e. toStringO);
}
}
}
Листинг6.7 СервлетPageCounterServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class PageCounterServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
- response.setContentType("text/html”);
Printwriter out = response.getWriter();
out.println("<HTML>”);
out.println(”<HEAD>”);
out.println(”<TITLE>Page Counter</TITLE>”);
out.println(”</HEAD>”);
out.println("<BODY>”);
ServletContext servletContext = getServletContextO;
int pageCounter = Integer.parseInt((String)
servletContext.getAtt ribute("pageCounter”));
pageCounter++;
out.println("You are visitor number " + pageCounter);
servletContext.setAttribute("pageCounter", •
Integer.toString(pageCounter));
out.println("</B0DY>");
out.println("</HTML>");
}
232
Глава 6
Дескриптор развертывания для этого примера приведен в листинге 6.8.
Листинг 6.8 Дескриптор развертывания для примера PageCounter
<?xml version=”1.0” encoding=”IS0-8859-1’’?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<listener>
<listener-class>
AppAtt ributeEventDemo
</listener-class>
</listener>
<servlet>
<servlet-name>
PageCounter
</servlet-name>
<servlet-class>
PageCounterServlet
</servlet-class>
</servlet>
</web-app>
Отметим, что attribute Replaced также вызывается, когда срабатывает
attributeAdded.
Прием событий HttpSession
Пакет javax.servlet.http предоставляет два интерфейса, которые можно ре-
ализовать для приема событий HttpSession: HttpSessionListener и
HttpSessionAttributeListener. Первый позволяет отслеживать события жизнен-
ного цикла сеанса, т. е. событие, которое возникает при создании объекта
HttpSession, и событие, которое инициируется при уничтожении объекта
HttpSession. Интерфейс HttpSessionAttributeListener предоставляет события,
которые инициируются при добавлении, удалении или изменении атрибута
объекта HttpSession.
Событияприложения и сеанса
233
Интерфейс HttpSessionListener
Интерфейс HttpSessionListener имеет два метода: sessionCreated и
session Destroyed. Сигнатуры этих методов представлены ниже:
public void sessionCreated(HttpSessionEvent se)
public void sessionDestroyed(HttpSessionEvent se)
Метод sessionCreated автоматически вызывается при создании объекта
HttpSession. Метод session Destroyed вызывается, когда объект HttpSession ста-
новится недействительным. Оба метода передаются в класс HttpSessionEvent,
который можно использовать в методе.
Класс HttpSessionEvent является производным от классаjava.util.EventObject.
Класс HttpSessionEvent определяет один новый метод, называемый getSession. Его
можно использовать для получения объекта HttpSession, который был изменен.
Пример: счетчик пользователей
Следующий пример является счетчиком числа различных пользователей,
которые в настоящее время «поддерживают сеанс». Он создает объект
HttpSession для пользователя, когда пользователь в первый раз обращается к
сервлету. Если пользователь снова обращается к тому же сервлету, объект
HttpSession не создается во второй раз. Поэтому счетчик увеличивается только
при создании объекта HttpSession.
Однако объект HttpSession может быть уничтожен. Когда это происходит,
счетчик должен быть уменьшен. Чтобы счетчик был доступен всем пользова-
телям, он хранится как атрибут в объекте ServletContext.
Этот пример имеет два класса: класс приемника и класс сервлета, который вы-
водит значение счетчика. Класс приемника называется SessionLifeCycleEventDemo,
он реализует интерфейсы ServletContextListener и HttpSessionListener. С помо-
щью первого интерфейса вы создаете атрибут ServletContext и присваиваете
ему начальное значение 0. Этот код следует поместить в метод contextlnitialized:
public void contextInitialized(ServletContextEvent see) {
ServletContext = sce.getServletContextO;
ServletContext.setAtt ribute(("userCounter”),
Integer.toString(counter));
}
Объект ServletContext присваивается переменной класса ServletContext, что
делает объект ServletContext доступным в любом месте класса.
Счетчик должен увеличиваться, когда создается HttpSession, и уменьшаться, —
когда HttpSession разрушается. Поэтому необходимо предоставить реализации
обэих методов, sessionCreated и session Destroyed:
234
Глава 6
public void sessionCreated(HttpSessionEvent hse) {
System, out. println(’’Session created. ”);
incrementUserCounter();
public void sessionDestroyed(HttpSessionEvent hse) {
System.out. println(’’Session destroyed. ”);
decrementUserCounter() ;
}
Метод sessionCreated вызывает следующий синхронизированный метод
incrementUserCounter, а метод session Destroyed вызывает синхронизиро-
ванный метод decrementUserCounter.
Метод incrementUserCounter получает атрибут с именем userCounter из
объекта ServletContext, увеличивает счетчик и сохраняет счетчик снова в атри-
буте userCounter.
synchronized void incrementllserCounter() {
counter = Integer.parselnt(
(St ring )servletContext. getAtt ribute( ’’userCounter”));
counter++;
servletContext.setAtt ribute(("userCounter"),
Integer.toString(counter));
System.out.println(”User Count: ” + counter);
}
Метод decrementUserCount получает атрибут userCounter из объекта
ServletContext, уменьшает счетчик и сохраняет счетчик снова в атрибуте
userCounter:
synchronized void decrementUserCounterO {
int counter = Integer.parselnt(
(String)servletContext.getAttribute("userCounter"));
counter-;
servletContext.setAttribute(("userCounter"),
Integer.toSt ring(counter));
System.out.println("User Count: " + counter);
}
Класс приемника приведен в листинге 6.9.
Листинг 6.9 Класс SessionLifeCycleEventDemo
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionListener;
Событияприложения и сеанса
235
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.ServletContextListener;
import j avax.se rvlet.ServletContext;
import javax.servlet.ServletContextEvent;
public class SessionLifeCycleEventDemo
implements ServletContextListener, HttpSessionListener {
ServletContext ServletContext;
int counter;
public void context!nitialized(ServletContextEvent see) {
ServletContext = sce.getServletContextO;
ServletContext.setAtt ribute(("userCounter"),
Integer.toString(counter));
}
public void contextDestroyed(ServletContextEvent see) {
}
public void sessionCrpated(HttpSessionEvent hse) {
System.out.println("Session created.");
incrementUserCounter();
}
public void sessionDestroyed(HttpSessionEvent hse) {
System.out.println("Session destroyed. ’’);
decrementUserCounter();
}
synchronized void incrementUserCounter() {
counter = Integer.parselnt(
(String)servletContext.getAttribute("userCounter"));
counter**;
ServletContext.setAtt ribute(("userCounter"),
Integer.toString(counter));
System.out.println("User Count: " + counter);
}
synchronized void decrementUserCounter() {
int counter = Integer.parselnt(
(String)servletContext.getAtt ribute("userCounter"));
counter-;
ServletContext.setAtt ribute(("userCounter"),
Integer.toString(counter));
System.out.println("User Count: " + counter);
}
}
Вторым классом приложения является UserCounterServlet. Сервлет выво-
дит значение атрибута userCounter объекта ServletContext всякий раз, когда
236
Глава 6
кто-либо запрашивает сервлет. Код, который это делает, записывается в методе
doGet.
После вызова метода doGet вызывается метод getServletContext для получе-
ния объекта ServletContext:
ServletContext ServletContext = getServletContextO;
Затем метод пытается получить объект HttpSession из объекта HttpServletRequest.
Если объект HttpSession не существует, он будет создан следующим образом:
HttpSession session = request.getSession(true);
Затем необходимо получить атрибут userCounter из объекта ServletContext:
int userCounter = 0;
userCounter = Integer.parselnt(
(String)servletContext.getAttribute("userCounter"));
Код сервлета приведен в листинге 6.10.
Л исти н г 6.10 Сервлет userCoiinterServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class UserCounterServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
ServletContext ServletContext = getServletContextO;
HttpSession session = request.getSession(true);
int userCounter = 0;
userCounter =
Integer. parselnt( (St ring )servletContext. getAtt ribute( "userCounter"));
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>User Counter</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("There are " + userCounter + " users.");
Событияприложения и сеанса
237
out. р ri nt 1 n ("</B0DY>");
out.println("</HTML>");
}
}
Чтобы пример работал, необходимо создать дескриптор развертывания, как
показано в листинге 6.11.
Л истинг 6.11 Дескриптор развертывания для примера UserCounter
<?xml version=”1.0” encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<listener>
<listener-class>
SessionLifeCycleEventDemo
</listener-class>
</listener>
<servlet>
<servlet-name>
UserCounter
</servlet-name>
<servlet-class>
UserCounterServlet
</servlet-class>
</servlet>
</web-app>
После компиляции двух классов откройте браузер и введите URL сервлета.
Вы должны увидеть что-то похожее на рис. 6.3.
Интерфейс HttpSessionAttributeListener
Вторым интерфейсом приемника событий сеанса является
HttpSessionAttributeListener. Этот интерфейс можно реализовать, если необ-
ходимо принимать события, связанные с атрибутами сеанса. Интерфейс пре-
доставляет три метода: attributeAdded, attribute Removed и attribute Replaced. Эти
методы имеют следующие сигнатуры:
public void attributeAdded(HttpSessionBindingEvent sbe)
public void attributeRemoved(HttpSessionBindingEvent sbe)
public void attributeReplaced(HttpSessionBindingEvent sbe)
238
Глава 6
Метод attributeAdded вызывается, когда атрибут добавляется в объект
HttpSession. Методы attribute Removed и attributeReplaced вызываются, когда
атрибут HttpSession удаляется или заменяется соответственно.
Все методы получают объект HttpSession BindingEvent.
HttpSessionBindingEvent является производным от класса HttpSessionEvent,
поэтому он наследует методgetSession. Крометого, класс HttpSessionBindingEvent
имеет еще два метода: getName и getValue. Сигнатуры этих методов:
public String getName()
public Object getValue()
Метод getName возвращает имя атрибута, который связывается с HttpSession
или отсоединяется от HttpSession. Метод getValue возвращает значение атри-
бута HttpSession, который был добавлен, удален или заменен.
Рис. 6.3. Счетчик пользователей
Заключение
В этой главе говорилось о событиях приложения и сеанса и об их использова-
нии. Возможность принимать эти события не является критически важной,
однако может быть очень полезной, как было показано в примерах.
Фильтрация
сервлетов
ilr ильтры являются новым элементом в спецификации сервлетов 2.3. Они
позволяют перехватить запрос прежде, чем он дойдет до ресурса. Другими сло-
вами, фильтр предоставляет доступ к объектам HttpServletRequest и
HttpServletResponse до того, как они передаются сервлету.
Фильтры могутбыть весьма полезными. Например, можно написать фильтр,
записывающий все входящие запросы и регистрирующий IP-адреса компью-
теров, с которых исходят запросы. Можно также использовать фильтры как
устройства для шифрования и дешифровки. К другим применениям относят-
ся аутентификация пользователей, сжатие данных, проверка пользовательс-
кого ввода и т. д.
Чтобы фильтр перехватывал запрос к сервлету, необходимо объявить фильтр
с помощью элемента <filter> в дескрипторе развертывания и отобразить фильтр
на сервлет с помощью элемента <filter-mapping>. Иногда желательно, чтобы
фильтр работал с несколькими сервлетами. Для этого нужно отобразить фильтр
на шаблон URL — будет фильтроваться любой запрос, соответствующий шаб-
лону URL.
Можно также построить цепочку фильтров. Первый фильтр в цепочке бу-
дет вызываться первым, затем управление будет передаваться второму фильт-
ру, и т. д. С помощью цепочки фильтров можно написать фильтр, который
выполняет определенную задачу, но добавляет некоторую функциональность
в другой фильтр.
240
Глава 7
Начнем с рассмотрения интерфейса javax.servlet. Filter, который должен быть
реализован классом фильтра. Затем создадим различные фильтры и попутно
познакомимся с другими классами и интерфейсами.
Обзор API
При создании фильтра в основном имеют дело со следующими тремя интер-
фейсами из пакета javax.servlet:
• Filter
• FilterConfig
• FilterChain
Эти три интерфейса описываются ниже.
Интерфейс Filter
javax.servlet.Filter является интерфейсом, который необходимо реализовать
при написании фильтра. Жизненный цикл фильтра представлен тремя мето-
дами этого интерфейса. Они имеют сигнатуры:
public void init(FilterConfig filterConfig)
public void doFilter(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
public void destroyO
Фильтр начинает свою жизнь, когда контейнер сервлетов вызывает его метод
init. Контейнер сервлетов вызывает метод init фильтра только один раз — по за-
вершении создания экземпляра фильтра. Контейнер сервлета передает объект
FilterConfig, который представляет конфигурацию фильтра. Обычно этот объект
присваивается объектной переменной, чтобы объект FilterConfig был доступен в
других методах. Сравните этот метод init с методом init интерфейса Servlet.
Фильтрация выполняется в методе do Filter. Контейнер сервлетов вызывает
метод doFilter каждый раз, когда пользователь запрашивает ресурс, например
сервлет, на который отображается фильтр. При вызове doFilter контейнер сер-
влетов передает объекты HttpServletRequest, HttpServletResponse и FilterChain.
Объекты HttpServletRequest и HttpServletResponse — это те объекты, которые
будут передаваться в сервлет. Над ними можно выполнять какие-то действия.
Например, можно добавить атрибут в HttpServletRequest, используя метод
setAttribute, или можно получить объект PrintWriter из HttpServletResponse и
произвести запись в него. Объект FilterChain позволяет передать управление
следующему ресурсу (см. ниже).
Фильтрация сервлетов
241
Метод doFilter аналогичен методу service интерфейса Servlet. Контейнер сер-
влетов вызывает метод destroy, чтобы сообщить фильтру о завершении его рабо-
ты. Фильтр может выполнить, если необходимо, некоторую очистку, например,
закрыть ресурс, который был открыт при инициализации. Метод destroy интер-
фейса Filter аналогичен методу destroy интерфейса Servlet.
Интерфейс FilterConfig
Объект FilterConfig представляет конфигурацию фильтра. Этот объект по-
зволяет получить объект ServletContext и передать начальные значения в фильтр
через его параметры, которые определяются в дескрипторе развертывания при
объявлении фильтра.
Интерфейс FilterConfig имеет четыре метода: getFilterName,
getlnitParameter, getlnitParameterNames и getServletContext.
Сигнатуры этих методов:
public String getFilterNameO
public String get!nitParameter(String parameterName)
public java. util. Enumeration getlnitParameterNamesO
public ServletContext getServletContextO
Метод getFilterName возвращает имя фильтра, а метод getServletContext
возвращает объект ServletContext. Метод getlnitParameterNames выдает объект
Enumeration, содержащий все имена параметров фильтра. Затем можно из-
влечь значение каждого начального параметра с помощью следующего метода
getlnitParameter, передав ему имя параметра.
Интерфейс FilterChain
Объект FilterChain передается контейнером сервлетов в метод do Filter класса
фильтра. Фильтры используют объект FilterChain для вызова следующего филь-
тра в цепочке или, если фильтр является последним в цепочке, для вызова сле-
дующего ресурса (сервлета).
Интерфейс FilterChain имеет только один метод: doFilter. Его сигнатура:
public void doFilter(HttpServletRequest request, HttpServletResponse
response)
(^Примечание M He Г|Утайте этот метод с методом doFilter интерфейса Filter.
> Последний имеет три аргумента.
Всегда необходимо вызывать метод doFilter интерфейса FilterChain для пе-
редачи управления следующему фильтру. Если используется только один
2А2
Глава 7
фильтр, метод doFilter передает управление следующему ресурсу, например
сервлету, запросы к которому нужно фильтровать. Отказ при вызове этого ме-
тода заставит программу остановиться.
Базовый фильтр
В качестве первого примера рассмотрим простой фильтр, который не делает
ничего, кроме вывода на экран сообщения всякий раз, когда вызывается его
метод. Этот фильтр объявляет объектную ссылку FilterConfig с именем
filterConfig. В методе init этой переменной передается объект FilterConfig. От-
метим, что метод doFilter фильтра вызывает метод doFilter объекта FilterChain
в последней строке метода. Код фильтра приводится в листинге 7.1.
Листинг 7.1 Класс BasicFilter
import java.io.lOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class BasicFilter implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter initialized”);
this.filterConfig = filterConfig;
}
public void destroyO {
System.out.println("Filter destroyed");
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws lOException, ServletException {
System.out.println(”doFilter");
chain.doFilter(request, response);
Фильтрация сервлетов
243
Чтобы фильтр работал, необходимо решить, какой сервлет или сервлеты
необходимо фильтровать, и сообщить об этом контейнеру сервлетов, объя-
вив фильтр в дескрипторе развертывания с помощью элементов <filter> и
<filter-mapping>. Эти два элемента должны идти перед любыми элементами
<listener> и <servlet>. Дескриптор развертывания для этого примера показан
в листинге 7.2.
Л истинг 7.2 Дескриптор развертывания
<?xml version="1.О” encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Basic Filter
</filter-name>
<filter-class>
BasicFilter
</filter-class>
</filter>
<!- Определение отображений для конкретных фильтров ->
<filter-mapping>
<filter-name>
Basic Filter
</filter-name>
<servlet-name>
FilteredServlet
</servlet-name>
</filter-mapping>
<listener>
<listener-class>
SessionLifeCycleEventDemo
</listener-class>
</listener>
<servlet>
<servlet-name>
FilteredServlet
</servlet-name>
244
Глава 7
Листинг 7.2 Продолжение
<servlet-class>
FilteredServlet
</servlet-class>
</servlet>
</web-app>
Можно видеть, что именем фильтра является Basic Filter, а его классом —
BasicFilter. Элемент <filter-mapping> отображает фильтр на сервлет с именем
FilteredServlet. Необходимо создать сервлет (который что-нибудь делает) с име-
нем FilteredServlet, чтобы пример заработал.
После компиляции классов фильтра и сервлета и модификации дескрип-
тора перезапустите контейнер сервлетов. Вы должны увидеть сообщение, по-
сланное методом init фильтра на консоль.
Откройте браузер и введите URL сервлета FilteredServlet. На экране появятся
сообщения, которые показывают, что фильтр вызывается перед сервлетом.
Если требуется применить фильтр к более чем одному сервлету, необходи-
мо только повторить элемент <filter-mapping> для каждого сервлета. Напри-
мер, в дескрипторе развертывания, приведенном в листинге 7.3, фильтр при-
меняется к сервлетам FilteredServlet и FilteredServlet2.
Л исти н г 7.3 Дескриптор развертывания, который применяет фильтр
к нескольким сервлетам
<?xml version=”1.0" encoding="IS0-8859-1”?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Basic Filter
</filter-name>
<filter-class>
SasicFilter
</filter-class>
<!- Определение отображений для конкретных фильтров ->
<filter-mapping>
<filter-name>
Фильтрация сервлетов
245
Basic Filter
</filter-name>
<servlet-name>
FilteredServlet
</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>
Basic Filter
</filter-name>
<servlet-name>
FilteredServlet2
</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
FilteredServlet
</servlet-name>
<servlet-class>
FilteredServlet
</servlet-class>
</servlet>
<servlet>
<servlet-name>
FilteredServlet2
</servlet-name>
<servlet-class>
FilteredServlet2
</servlet-class>
</servlet>
</web-app>
Отображение фильтра на URL
В дополнение к отображению фильтра на сервлет или ряд сервлетов, можно
отобразить фильтр на шаблон U RL, так что все запросы, соответствующие это-
му шаблону, будут вызывать фильтр.
Рассмотрим сервлет, URL которого:
http://localhost:8080/myApp/servlet/FilteredServlet
Для отображения фильтра на шаблон URL используется элемент <filter-
mapping> дескриптора развертывания:
<!- Определение отображений фильтров ->
<filter-mapping>
246
Глава 7
<filter-name>
Loggin Filter
</filter-name>
</url-pattern>/servlet/FilteredServlet</url-pattern>
</filter-mapping>
В качестве альтернативы можно использовать /*, чтобы заставить фильтр
работать для всех статических и динамических ресурсов:
<!- Определение отображений фильтров ->
<filter-mapping>
<filter-name>
Logging Filter
</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Фильтр регистрации
Рассмотрим другой пример фильтра, который регистрирует IP-адреса пользо-
вателей в файле журнала. Расположение файла журнала зависит от реализации
контейнера сервлетов (см. приложение А).
Класс фильтра приведен в листинге 7.4.
Листинг 7.4 Фильтр регистрации
import java.io.lOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import j avax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class LoggingFilter implements Filter {
private FilterConfig filterConfig = null;
public void destroyO {
System.out.printin("Filter destroyed");
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
Фильтрация сервлетов
247
throws lOException, ServletException {
. System.out.println("doFilter");
// Записать IP-адрес пользователя
ServletContext ServletContext = filterConfig.getServletContextO;
ServletContext. log (request. getRemoteHostO);
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter initialized");
this.filterConfig = filterConfig;
}
}
Между этим фильтром и BasicFilter не слишком большие различия, за
исключением того, что метод doFilter получает объект ServletContext и исполь-
зует свой журнал для записи IP-адреса клиента, полученного из метода
getRemoteHost объекта HttpServletRequest:
// Записать IP-адрес пользователя
ServletContext ServletContext = filterConfig.getServletContextO;
ServletContext. log (request. getRemoteHostO);
Дескриптор развертывания этого примера показан в листинге 7.5.
Листинг 7.5 Дескриптор развертывания для LoggingFilter
<?xml version="1.О” encoding="IS0-8859-1"?>
<’. DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Logging Filter
</filter-name>
<filter-class>
LoggingFilter
</filter-class>
</filter>
248
Глава 7
Листинг 7.5 Продолжение
<!- Определение отображений для конкретных фильтров ->
<filter-mapping>
<filter-name>
Logging Filter
</filter-name>
<servlet-name>
FilteredServlet
</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
FilteredServlet
</servlet-name>
<servlet-class>
FilteredServlet
</servlet-class>
</servlet>
</web-app>
Любой сервлет с именем FilteredServlet будет вызывать фильтр. Листинг
7.6 показывает пример такого сервлета.
Листинг 7.6 Сервлет, который будет использоваться с LogginFilter
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FilteredServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriterO;
out.printIn("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>User Counter</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
Фильтрация сервлетов
249
out.println("IP:" + request. getRemoteHostO);
out.println("</B0DY>");
out.println("</HTML>");
}
}
Конфигурация фильтра
Можно передать в фильтр некоторые начальные параметры с помощью объекта
FilterConfig, который передается в метод init интерфейса Filter. Начальные
параметры объявляются в дескрипторе развертывания с помощью элемента
<init-param> внутри элемента <filter>. Например, дескриптор развертывания
в листинге 7.7 описывает фильтр с именем MyFilter с двумя начальными пара-
метрами: admin Phone и admin Email.
Л и сти н г 7.7 Дескриптор развертывания, описывающий фильтр с двумя
начальными параметрами
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
MyFilter
</filter-name>
<filter-class>
MyFilter
</filter-class>
<init-param>
<param-name>
adminPhone
</param-name>
<param-value>
0414789098
</param-value>
</init-param>
250
Глава 7
Листинг 7.7 Продолжение
<init-param>
<param-name>
adminEmail
</param-name>
<param-value>
admin@labsale.com
</param-value>
</init-param>
</web-app>
Затем можно извлечь значения параметров AdminPhone и AdminEmail с
помощью кода, который должен находиться в методе DoFilter фильтра:
String adminPhone = filterConfig.getInitParameter("adminPhone");
String adminEmail = filterConfig.get!nitParameter("adminEmail");
Фильтр, проверяющий ввод пользователя
При получении пользовательского ввода прежде всего необходимо проверить,
является ли он действительным. Если входные данные недействительны, то
обычно посылается сообщение об ошибке, извещающее пользователя о том,
что он должен правильно ввести данные.
Иногда ввод по сути своей правильный, но только содержит ведущие или за-
вершающие пробелы. В этом случае не требуется посылать сообщение об ошиб-
ке, поскольку можно сделать исправление самостоятельно; т. е. когда пользова-
тель вводит свое имя в поле firstName формы HTML, необходимо проверить, что
вводится что-то типа «John» или «George», а не «John» или «George» (с пробела-
ми). Обычно в таких ситуациях применяется функция trim класса String:
String firstName = request.getParameter("firstName");
if (firstName!=null)
firstName = firstName.trim();
Однако при наличии большого числа полей ввода в форме HTML необхо-
димо вызывать метод trim для каждого параметра. Кроме того, делать это нуж-
но в каждом сервлете, который получает пользовательский ввод. Фильтр спо-
собен облегчить эту задачу.
Можно написать фильтр, который будет обрезать значение каждого пара-
метра в объекте HttpServletRequest прежде, чем этот объект достигнет сервле-
Фильтрация сервлетов
251
та. В результате ничего не нужно обрезать в сервлете. Необходимо написать
только один фильтр для обслуживания всех сервлетов, которые нуждаются в
этой службе. В следующем примере создается фильтр, который урезает все
значения параметров.
При решении этой задачи не используйте жестко кодированные имена па-
раметров. Вместо этого примените метод getParameterNames для получения
объекта Enumeration, содержащего все имена параметров. Затем в цикле по
Enumeration нужно получить значения параметров и вызвать метод trim.
Но поскольку изменять значение параметра нельзя, невозможно обрезать
его непосредственно. Вы только можете установить атрибут объекта
HttpServletRequest, т. е. использовать усеченные значения параметров как ат-
рибуты. Имена параметров становятся именами атрибутов. Тогда в сервлете
вместо извлечения пользовательского ввода из параметров HttpServletRequest
вы получите усеченные версии входных данных из атрибутов
HttpServletRequest, как показано ниже:
Enumeration enum = request. getParameterNamesO;
while (enum.hasMoreElementsO) {
String parameterName = (String) enum.nextElementO;
String parametervalue = request.getParameter(parameterName);
request.setAttribute(parameterName, parametervalue.trim());
}
В сервлете для получения усеченного входного значения сделайте следующее:
request.getAttribute(parameterName);
Значение извлекается с помощью метода getAttribute, а не метода
getParameter объекта HttpServletRequest.
Фильтр, который выполняет эту службу, называется Trim Filter, и его код
представлен в листинге 7.8.
Листинг 7.8 TrimFilter
import java.io.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Enumeration;
public class TrimFilter implements Filter {
252
Глава 7
Л истинг 7.8 Продолжение
private FilterConfig filterConfig = null;
public void destroyO {
System.out.println("Filter destroyed”);
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws lOException, ServletException {
System. out. print 1 n (’’Filter");
Enumeration enum = request.getParameterNamesO;
while (enum.hasMoreElementsO) {
String parameterName = (String) enum.nextElementO;
String parametervalue = request.getParameter(parameterName);
request.setAttribute(parameterName, parametervalue.trim());
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
System.out.printIn("Filter initialized");
this.filterConfig = filterConfig;
}
}
Чтобы проиллюстрировать использование фильтра, можно написать серв-
лет, который делает следующее:
• Посылает форму НТМЬсчетырьмя полями ввода (firstName, lastName,
userName и password), когда вызывается его метод doGet.
• Выводит на экран входные данные пользователя, когда вызывается
его метод doPost.
Сервлет называется TrimFilteredServlet (см. листинг 7.9).
Листинг 7.9 THmFilteredServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
Фильтрация сервлетов
253
import com.brainysoftware.java.StringUtil;
public class TrimFilteredServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriter();
out.println("<HTML>”);
out.println(”<HEAD>");
out.println("<TITLE>User Input Form</TITLE>");
out.println("</HEAD>");
out.println(”<BODY>"); x
out.printin(”<CENTER>”);
out.println("<BR>Please enter your details.");
out.println(”<BR>");
out. printin("<BR><FORM METHOD=POST>");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>First Name:</TD>");
out.println(”<TD><INPUT TYPE=TEXT NAME=firstNameX/TD>”);
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Last Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=lastNameX/TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>User Name:</TD>");
out.println(”<TD><INPUT TYPE=TEXT NAME=userName></TD>");
out.println("</TR>”);
out.println("<TR>");
out.println(”<TD>Password:</TD>");
out.println("<TDX!NPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD ALIGN=RIGHT C0LSPAN=2>");
out. println("<INPUT TYPE=SUBMIT VALUE=LoginX/TD>");
out.println("</TR>");
out.printin("</TABLE>");
out.println("</FORM>") ;
out.println("</CENTER>");
out.println("</B0DY>");
out.println(”</HTML>");
/
254
Глава 7
Листинг 7.9 Продолжение
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
String firstName = (String) request.getAttribute("firstName");
String lastName - (String) request.getAttribute("lastName");
String userName = (String) request.getAttribute("userName");
String password = request.getParameter("password");
response.setContentType("text/html");
Printwriter out = response.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Displaying Values</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println(”<CENTER>");
out.println("Here are your details.");
out.println("<TABLE>");
out.println("<TR>");
out.println("<TD>First Name:</TD>");
out.println("<TD>" + StringUtil(firstName) + "</TD>”);
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Last Name:</TD>");
out.println("<TD>" + StringUtil(lastName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>User Name:</TD>");
out. println("<TD>" + StringUtil(userName) + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>Password:</TD>");
out. println("<TD>" + Stringlltil(password) + "</TD>");
out.println("</TABLE>”);
out.println("</CENTER>");
out.println("</BODY>");
out.println("</HTML>");
}
}
Для правильной работы приложению требуется дескриптор развертывания,
показанный в листинге 7.10.
Фильтрация сервлетов
255
Л истин г 7.10 Дескриптор развертывания
<?xml version=’'1.0” encoding=”IS0-8859-1’’?>
<! DOCTYPE web-app
PUBLIC ”-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
’’http: //j ava. sun. com/dtd/web-app_2_3. dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Trim Filter
</filter-name>
<filter-class>
TrimFilter
</filter-class>
</filter>
<!- Определение отображений фильтров ->
<filter-mapping>
<filter-name>
Trim Filter
</filter-name>
<servlet-name>
TrimFilteredServlet
</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
T rimFilteredServlet
</servlet-name>
<servlet-class>
TrimFilteredServlet
</servlet-class>
</servlet>
</web-app>
При выполнении сервлет выведет что-то похожее на рис. 7.1.
256
Глава 7
Рис. 7.1. Метод doGet
Отметим, что в этой форме пользователь вводит фамилию с ведущим про-
белом. Когда пользователь отправит форму, браузер выведет информацию, как
показано на рис. 7.2.
Рис. 7.2. Метод doPost
Обратите внимание, что ведущие пробелы исчезли. Это служит доказатель-
ством работы фильтра.
Фильтрация сервлетов
257
Фильтрация ответа
Можно также фильтровать ответ. В этом примере создается фильтр, который
добавляет заголовок и завершение каждого сервлета в приложении. Код фильт-
ра приведен в листинге 7.11.
Л исти нг 7.11 Класс ResponseFilter
import java.io.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Enumeration;
public class ResponseFilter implements Filter {
private FilterConfig filterConfig = null;
public void destroyO {
System, out. printing Filter destroyed’’);
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws lOException, ServletException {
System, out. println( ’’doFilter”);
Printwriter out = response.getWriter();
// это добавляется в начало Printwriter
out. println(”<HTML>’’);
out.println("<BODY>”);
out.println(”<CENTER>");
out.println(”Page header’’);
out. println(”<HR>’’);
chain.doFilter(request, response);
// это добавляется в конец Printwriter
out.println(”<HR>”);
out.println("Page footer");
out.println("<CENTER>”);
out.println(”</BODY>");
258
Глава 7
Листинг 7.11 Продолжение
out. println( "</НТМ1_>");
}
public void init(FilterConfig filterConfig) throws ServletException {
System.out.printin("Filter initialized");
this.filterConfig = filterConfig;
}
}
Пример сервлета, к которому применяется фильтр, представлен в лис-
тинге 7.12.
Л исти нг 7.12 ResponseFilteredServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ResponseFilteredServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html”);
Printwriter out = response.getWriterO;
out.println("<BR>Please enter your details.");
out.println("<BR>");
out. printin("<BRXF0RM METHOD=POST>");
out.println("<TABLE>");
out.println("<TR>”);
out.println("<TD>First Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=firstNamex/TD>");
out.println(”</TR>");
out.println("<TR>");
out. println("<TD>Last Name:</TD>”);
out.println("<TD><INPUT TYPE=TEXT NAME=lastName></TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD>User Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=userNameX/TD>");
out.println(”</TR>");
Фильтрация сервлетов
259
out.println(”<TR>");
out.println("<TD>Password:</TD>”);
out. println(”<TDXlNPUT TYPE=PASSWORD NAME=password></TD>”);
out.println(”</TR>");
out.println("<TR>");
out.println(”<TD ALIGN=RIGHT C0LSPAN=2>");
out. println("<INPUT TYPE=SUBMIT VALUE=Loginx/TD>");
out. println(’’</TR>”);
out. printin(”</TABLE>’’);
out.println("</FORM>");
}
}
Чтобы пример работал, требуется дескриптор развертывания, который пред-
ставлен в листинге 7.13.
Листинг 7.13 Дескриптор развертывания
<?xml version="1.0" encoding=’’IS0-8859-r?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Response Filter
</filter-name>
<filter-class>
ResponseFilter
</filter-class>
</filter>
<!- Определение отображений фильтров ->
<filter-mapping>
<filter-name>
Response Filter
</filter-name>
<servlet-name>
ResponseFilteredServlet
</servlet-name>
</filter-mapping>
260
Глава 7
Листинг 7.13 Продолжение
<servlet>
<servlet-name>
ResponseFilteredServlet
</servlet-name>
<servlet-class>
ResponseFilteredServlet
</servlet-class>
</servlet>
</web-app>
Результат работы этого фильтра показан на рис. 7.3.
Page beader
Pleace eater your detade.
Page footer
Рис. 7.3. Фильтрация HttpServletResponse
Исходный код HTML выглядит следующим образом:
<HTML>
<BOBY>
<CENTER>
Page header
<HR>
<BR>Please enter your details.
<BR>
<BR><FORM METHOD=POST>
<TABLE>
<TR>
<TD>First Name:</TD>
<TDXINPUT TYPE=TEXT NAME=firstName></TD>
Фильтрация сервлетов
261
</TR>
<TR>
<TD>Last Name:</TD>
<TDXINPUT TYPE=TEXT NAME=lastName></TD>
</TR>
<TR>
<TO>User Name:</TD>
<TD><INPUT TYPE=TEXT NAME=userName></TD>
</TR>
<TR>
<TD>Password:</TD>
<TDXINPUT TYPE=PASSWORD NAME=password></TD>
</TR>
<TR>
<TD ALIGN=RIGHT C0LSPAN=2>
CINPUT TYPE=SUBMIT VALUE=LoginX/TD>
</TR>
</TABLE>
</FORM>
<HR>
Page footer
<CENTER>
</BODY>
</HTML>
Цепочка фильтров
К ресурсу можно применить более одного фильтра. В этом примере создается
фильтр UpperCaseFilter и используются фильтр TrimFilter и сервлет
DoublyFilteredServlet. Фильтр UpperCaseFilter представлен в листинге 7.14, а
сервлет DoublyFilteredServlet — в листинге 7.15.
Листинг 7.14 UpperCaseFilter
import java.io.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import j avax.servlet.ServletResponse;
import java.util.Enumeration;
262
Глава 7
Листинг 7.14 Продолжение
public class UpperCaseFilter implements Filter {
private FilterConfig filterConfig = null;
public void destroyO {
System.out.println("Filter destroyed");
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) ,
throws lOException, ServletException {
System.out.println("Filter");
Enumeration enum = request.getAttributeNamesO;
while (enum. hasMoreElementsO) {
String attributeName = (String) enum.nextElementO;
String attributevalue = (String)
request.getAtt ribute(att ributeName);
request.setAttribute(attributeName, attributevalue.tollpperCase());
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter initialized".);
this.filterConfig = filterConfig;
}
}
Листинг7.15 DoublyFilteredServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import com.brainysoftware.java.StringUtil;
public class DoublyFilteredServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response.setContentType("text/html");
Printwriter out = response.getWriter();
Фильтрация сервлетов
263
out.printing”<HTML>");
out.println("<HEAD>”);
out.println(”<TITLE>User Input Form</TITLE>");
out.println(”</HEAD>");
out. println( "<B0DY>'');
out. println( "<CENTER>");
out. println(’’<BR>Please enter your details.”);
out.println("<BR>");
out. println(”<BRXF0RM METHOD=POST>”);
out.println("<TABLE>”);
out.println(”<TR>”);
out. println(”<TD>First Name:</TD>’’);
out.println("<TD><INPUT TYPE=TEXT NAME=firstNameX/TD>");
out.println("</TR>");
out.println(”<TR>");
out.println("<TD>Last Name:</TD>");
out.println(”<TD><INPUT TYPE=TEXT NAME=lastName></TD>");
out.println("</TR>");
out.println(”<TR>");
out.println(”<TD>User Name:</TD>");
out.println("<TD><INPUT TYPE=TEXT NAME=userNameX/TD>");
out.println(”</TR>”);
out.println(”<TR>");
out.println("<TD>Password:</TD>");
out. println("<TDX!NPUT TYPE=PASSWORD NAME=passwordx/TD>");
out.println(”</TR>”);
out.println(”<TR>”);
out.println(”<TD ALIGN=RIGHT C0LSPAN=2>”);
out. println(’’<INPUT TYPE=SUBMIT VALUE=Login></TD>");
out.println(”</TR>");
out.p ri nt1n(”</TABLE>”);
out.println(”</FORM>”);
out.println(”</CENTER>”);
out.println(”</BODY>");
out.println(”</HTML>”);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
String firstName = (String) request.getAttribute(’’firstName’’);
String lastName = (String) request.getAttribute(’’lastName”);
String userName = (String) request.getAttribute("userName");
String password = request.getParameter("password");
response.setContentType("text/html");
264
Глава 7
Листинг 7.15 Продолжение
Printwriter out = response.getWriter();
out. println(”<HTML>”);
out.println("<HEAD>”);
out. println(”<TITLE>Displaying Values</TITLE>");
out.println("</HEAD>");
out.println(”<BODY>”);
out.printin("<CENTER>”);
out.println(”Here are your details.”);
out.println(”<TABLE>”);
out.println("<TR>”);
out.println(”<TD>First Name:</TD>”);
out. println(”<TD>” + StringUtil. encodeHtmlTag(firstName) + "</TD>");
out.println(”</TR>”);
out.println("<TR>");
out.printing”<TD>Last Name:</TD>”);
out.println(”<TD>” + StringUtil.encodeHtmlTag (lastName) + ”</TD>");
out.println(”</TR>");
out.println(”<TR>");
out.println("<TD>User Name:</TD>”);
out.println(”<TD>” + StringUtil.encodeHtmlTag(userName) + ”</TD>");
out.println(”</TR>");
out.println(”<TR>”);
out.println(”<TD>Password:</TD>");
out. println("<TD>” + StringUtil.encodeHtmlTag(password) + ”</TD>”);
out.println(”</TABLE>");
out.println(”</CENTER>”);
out.println(”</B0DY>”);
out.println(”</HTML>”);
Г Примечание Л Фильтры в цепочке фильтров изменяют атрибуты, потому что
- вы не можете модифицировать параметры в объекте запроса
HTTP.
Дескриптор развертывания представлен в листинге 7.16.
Листинг 7.16 Дескриптор развертывания для этого примера
<?xml version="1.0” encoding='TS0-8859-T'?>
<!DOCTYPE web-app
Фильтрация сервлетов
265
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<•- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Trim Filter
</filter-name>
<filter-class>
TrimFilter
</filter-class>
</filter>
<filter>
<filter-name>
Uppercase Filter
</filter-name>
<filter-class>
UpperCaseFilter
</filter-class>
<!- Определение отображений фильтров ->
<filter-mapping>
<filter-name>
Trim Filter
</filter-name>
<servlet-name>
DoublyFilteredServlet
</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-natye>
Uppercase Filter
</filter-name>
<servlet-name>
DoublyFilteredServlet
</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
266
Глава 7
Листинг 7.16 Продолжение
DoublyFilteredServlet
</servlet-name>
<servlet-class>
DoublyFilteredServlet
</servlet-class>
</servlet>
</web-app>
На рис. 7.4 показаны два фильтра в действии. Пользовательский ввод те-
перь обрезается и переводится в символы верхнего регистра.
Рис. 7.4 . Результат работы метода doPost сервлета DoublyFilteredServlet
Заключение
Эта глава познакомила вас с фильтрами — новым средством спецификации
сервлетов 2.3, которое позволяет выполнить некоторые операции прежде, чем
запрос HTML достигнет сервлета. При создании фильтров используются три
интерфейса: Filter, FilterConfig и FilterChain. Чтобы фильтр работал с опреде-
ленным ресурсом, необходимо объявить фильтр в дескрипторе развертыва-
ния с помощью элемента <filter> и отобразить его на ресурсы, которые пред-
полагается фильтровать, с помощью элемента <filter-mapping>. Можно
отобразить фильтр на сервлет или шаблон URL.
Следующая глава представляет вторую технологию создания web-прило-
жений на Java — JavaServer Pages (JSP — серверные страницы Java).
8
Основы JSP
^^'ерверные страницы Java (JavaServer Pages, JSP) представляют собой еще
одну технологию Java для разработки web-приложений. JSP были выпущены в
то время, когда технология сервлетов завоевала популярность как одна из луч-
ших доступных web-технологий. Однако JSP не заменяют сервлеты. Фактически
JSP являются расширением технологии сервлетов, и общая практика состоит в
использовании сервлетов и страниц JSP в одном web-приложении.
Создание JSP является настолько простым, что можно писать приложения
JSP, не имея больших знаний о базовом API. Однако высококлассный web-
программист Java обязан знать как JSP, так и сервлеты. Даже если применяют-
ся только страницы JSP в web-приложениях Java, понимание технологии сер-
влетов по-прежнему очень важно. Как будет показано в этой и последующих
главах, JSP используют те же методы, что применяются при программирова-
нии сервлетов. Например, в JSP работают с запросами HTTP и ответами HTTP,
с параметрами запроса, атрибутами запроса, со средством управления сеан-
сом, cookie, перезаписью URL и т.д. В этой главе разъясняются отношения
между JSP и сервлетами, рассматривается технология JSP и дается много при-
меров, которые легко выполнить.
Если вы не знакомы с технологией сервлетов, читайте эту
главу только после изучения глав с 1 по 7, которые посвящены
созданию сервлетов и работе с ними.
268
Глава 8
Недостатки сервлетов
История Java-программирования на стороне сервера началась с сервлетов.
Компания Sun представила сервлеты в 1996 г. как небольшие приложения на
основе Java для добавления динамического содержимого в web-приложения.
С ростом популярности Java сервлеты стали одной из самых распространен-
ных технологий Интернета. Однако программисты сервлетов знают, сколь
обременительным является программирование с помощью сервлетов, особен-
но когда необходимо послать длинную страницу HTML, которая содержит
мало кода. Рассмотрим в качестве примералистинг8.1. Этоткодявляется фраг-
ментом приложения на основе сервлетов, который выводит имена и значения
всех параметров в запросе HTTP.
Л исти н г 8.1 Вывод всех пар параметр/значение в запросе с помощью
сервлета
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class MyDearServlet extends HttpServlet {
//Обработка запроса HTTP GET
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
doPost(request, response);
}
//Обработка запроса HTTP POST
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
response. setContentType( ’’text/html ”);
Printwriter out = response.getWriterO;
out. println(”<HTML>’’);
out.println("<HEAD><TITLE>Using Servlets</TITLE></HEAD>”);
out. println(’’<BODY BGC0L0R=#123123>”);
//Получение имен параметров
Enumeration parameters = request.getParameterNames();
String param = null;
while (parameters.hasMoreElementsO) {
Основы JSP
269
param = (String) parameters.nextElementO;
out.println(param + + request.getParameter(param) +
}
out.println("</B0DY>");
out.println("</HTML>");
out.closeO;
} //Конец метода doPost
/* остальные части класса
} //Конец класса
Почти половина содержимого, посланного методом doPost, является ста-
тическим HTML. Однако каждый тег НТМЬдолжен быть помещен в String и
передан с помощью метода println объекта Printwriter. Это скучная работа. Хуже
того, страница HTML может быть значительно больше.
Другим недостатком использования сервлетов является то, что любое из-
менение будет требовать вмешательства программиста сервлета. Даже незна-
чительная графическая модификация, например изменение значения атри-
бута BGCOLOR тега <BODY> с #DADADA на #FFFFFF, потребует работы
программиста (который в этом случае будет действовать под управлением бо-
лее сведущего в графике web-дизайнера).
Компания Sun увидела эту проблему и вскоре разработала ее решение. Ре-
зультатом явилась технология JSP. Согласно web-сайту Sun, «технология JSP
является расширением технологии сервлетов, созданным для поддержки раз-
работки страниц HTML и ХМL». Применение JSP значительно облегчает ком-
бинирование фиксированных или статических шаблонных данных с динами-
ческим содержимым. Даже если вас устраивают сервлеты, вы найдете в этой
главе несколько важных причин для изучения технологии JSP.
Необходимо подчеркнуть, что «технология JSP является расширением техно-
логии сервлетов». Это означает, что JSP не заменяет сервлеты как технологию
написания серверных Интернет/интранет приложений. Фактически JSP были
созданы на основе сервлетов и требуют, чтобы работала технология сервлетов.
JSP исправляет недостатки технологии сервлетов, например, позволяя про-
граммисту вставлять в код статическое содержимое. При работе с шаблоном
страницы HTML, написанным дизайнером Web, программист может добавить
код на страницу HTML и сохранить ее как файл .jsp. Если впоследствии дизай-
неру Web понадобится изменить цвет фона тела HTML, он сможет сделать это
без привлечения программиста, открыв файл .jsp и отредактировав его.
270
Глава 8
Код листинга 8.1 можно переделать в JSP, как показано в листинге 8.2.
Л истинг 8.2 Вывод всех пар параметр/значение в запросе с помощью JSP
<%@ page import=”java.util.Enumeration” %>
<HTML>
<HEAD><TITLE>Using JSP</TITLE></HEAD>
<BODY BGCOLOR=#DADADA>
<%
//Получение имен параметров
Enumeration parameters = request.getParameterNamesO;
String param = null;
while (parameters.hasMoreElementsO) {
param = (String) parameters.nextElement();
out.println(param + + request.getParameter(param) +
”<BR>”);
out.close();
%>
</BODY>
</HTML>
Можно видеть, что теги <НТМ L> не затрагиваются. Чтобы добавить дина-
мическое содержимое, нужно лишь поместить код между тегами <%...%>.
Еще раз подчеркнем, что JSP не являются заменой сервлетов. Скорее, тех-
нология JSP и сервлеты совместно предоставляют привлекательное решение
для программирования и создания сценариев Web, предлагая независимость
от платформы, улучшенную производительность, разделение логики и пред-
ставления, легкость администрирования, расширяемость на уровень предприя-
тия и, самое важное, легкость в использовании.
Создание простой страницы JSP
Напишем простую страницу JSP и выполним ее. Основное внимание сосредо-
точим не на архитектуре или синтаксисе и семантике страницы JSP, а на том,
как сконфигурировать контейнер сервлетов/JSP. Для выполнения приложений
JSP используем Tomcat 4. Если Tomcat 4 установлен и сконфигурирован для сер-
влетных приложений, то больше ничего делать не надо. В противном случае
обратитесь к приложению А.
В контексте JSP, Tomcat часто называют «контейнером JSP».
Однако Tomcat используется также для выполнения
сервлетов, поэтому его называют еще контейнером
сервлетов/JSP.
Основы JSP
271
JSP существенно упрощает работу с сервлетами. Для выполнения своей
страницы JSP необходимо только сконфигурировать контейнер JSP (Tomcat)
и написать страницу JSP. Конфигурирование выполняется лишь однажды в
самом начале. Не требуется никакой компиляции.
Конфигурирование Tomcat для выполнения
приложения JSP
Прежде чем можно будет выполнить приложение JSR, необходимо скон-
фигурировать Tomcat, чтобы он смог опознать это приложение. Для конфигу-
рирования Tomcat на выполнение определенного приложения JSP:
1. Создайте каталоге именем myJSPApp в %CATALINA_HOME%/webapps.
Структура каталогов показана на рис. 8.1.
2. Добавьте подкаталог с именем WEB-INF в каталог myJSPApp.
3. Отредактируйте server.xml, конфигурационный файл сервера, чтобы Tomcat
знал о новом приложении JSP. Файл serverl.xml расположен в каталоге conf
в %CATALINA_HOME%. Откройте файл в текстовом редакторе и най-
дите код, аналогичный следующему:
<Context path='7examples” docBase="examples” debug="0’’
reloadable="true”>
</Context>
Сразу после закрывающего тега </Context> добавьте код:
<Context path=”/niyJSPApp” docBase="myJSPApp" debug=”0'’
reloadable=”true">
</Context>
4. Перезапустите Tomcat.
Теперь можно написать свой файл JSP и сохранить его в каталоге myJSPApp.
Чтобы улучшить организацию, можно создать подкаталог с именем jsp в ката-
логе myJSPApp и сохранить файлы JSP в нем. В этом случае не потребуется
изменять настройки в файле server.xml.
272
Глава 8
Создание файла JSP
Страница JSP содержит вперемешку теги HTML и код Java. Теги HTML
выполняют задачу представления, а код создает содержимое. В своей наибо-
лее простой форме страница JSP может включать в себя только HTML, как
показано в листинге 8.3.
Листинг 8.3 Простейшая страница JSP
<HTML>
<HEAD>
</HEAD>
<BODY>
JSP is easy.
</BODY>
</HTML>
tomcat
common
lib
i server
examples
IfflmyJSPApp
webdav
El work
Рис. 8.1. Структура каталогов JSP-приложения для приложения myJSPApp
Сохраните этот файл как Simple Page .jsp в каталоге myJSPApp. Структура
вашего каталога должна быть похожа на каталог рис. 8.1.
Теперь запустите web-браузер и введите следующий URL:
httр://localhost:8080/myJSPApp/SimplePage.j sp
Основы JSP
273
Окно браузера показано на рис. 8.2.
JSP и eaiy
Рис. 8.2. Простая страница JSP
Другие примеры
Конечно, код в листинге 8.3 не является полезным, но он иллюстрирует тот
факт, что страница JSP вовсе не обязана иметь код. Если страница статичес-
кая, нет необходимости помещать ее в файл JSP, так как файлы JSP обрабаты-
ваются медленнее, чем файлы HTML. Использовать файл JSP для тегов HTML
можно в том случае, если предполагается добавить в него в будущем код Java.
Это избавит от проблемы изменения ссылок на эту страницу.
При помещении кода Java в файл JSP код вставляется между тегами <% ...
%>. Например, листинг 8.4 является примером соединения кода Java и HTML
в файле JSP.
Листинг 8.4 Соединение HTML и кода Java
<HTML>
<HEAD>
</HEAD>
<BODY>
<%
out.println("JSP is easy”);
%>
</BODY>
</HTML>
274
Глава 8
Код листинга 8.4 создает такие же выходные данные, что и код листинга
8.3. Отметим, однако, использование кода Java для отправки текста. Функции
out.println описываются ниже. А пока скажем лишь, что out.println служит для
отправки String web-браузеру.
Отметим также, что вывод страницы JSP является простым текстом, состо-
ящим из тегов HTML. Никакой код страницы не посылается браузеру.
Другой пример представлен в листинге 8.5. Этот фрагмент кода выводит
строку «Welcome. The server time is now» и время сервера.
Листинг 8.5 Вывод на экран времени сервера
<НТМ1_>
<HEAD>
<TITLE>Displaying the server time</TITLE>
</HEAD>
<BODY>
Welcome. The server time is now
<%
java.util.Calendar now = java.util.Calendar.getlnstanceO;
int hour = now.get(java.util.Calendar.HOUR_OF_DAY);
int minute = now.get(java.util.Calendar.MINUTE);
if (hour<10)
out.println(”0" + hour);
else
out.println(hour);
out.println(”: ”);
if (minute<10)
out.println(”0" + minute);
else
out.println(minute);
%>
</BODY>
</HTML>
Код листинга 8.5 выводит время в формате hh:mm. Поэтому, если час мень-
ше 10, то вставляется «0», т. е. девять часов будут выводиться как 09, а не как 9.
Принцип работы JSP
Внутри контейнера JSP имеется специальный сервлет, называемый компиля-
тором страниц. Контейнер сервлетов сконфигурирован для переадресации
Основы JSP
275
этому компилятору всех запросов HTTP с URL, которые соответствуют фай-
ловому расширению .jsp. Компилятор страниц превращает контейнер сервле-
тов в контейнер JSP. Если страница .jsp вызывается в первый раз, компилятор
страниц производит синтаксический разбор и компилирует страницу .jsp в
класс сервлета. Если компиляция успешна, класс сервлета jsp загружается в
память. При последующих вызовах класс сервлета для этой страницы .jsp уже
находится в памяти; однако он может обновляться. Поэтому сервлет компи-
ляции страниц всегда будет сравнивать отметки времени сервлета jsp и стра-
ницы jsp. Если страница .jsp имеет более позднюю отметку времени, то требует-
ся перекомпиляция. При таком процессе после развертывания страницы JSP
компилируются только один раз.
Можно предположить, что после развертывания первый запрос пользовате-
лем страницы .jsp будет обрабатываться медленно, поскольку необходимо вре-
мя на компиляцию файла .jsp в сервлет jsp. Во избежание этого механизм JSP
позволяет предварительно откомпилировать страницы .jsp до получения како-
го-либо запроса. Либо можно развернуть приложение JSP как архивный файл в
форме компилированного сервлета. Эти приемы обсуждаются в главе 16.
Сгенерированный код сервлета JSP
Когда вызывается JSP, Tomcat создает два файла в следующем каталоге
C:\%CATALINA_HOME%\work\ Iocalhost\examples\jsp. Этими двумя файлами
являются SimplePageJsp.java и SimplePage_jsp.class. Если открыть файл
SimplePageJsp.java, то можно увидеть следующее:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import org.apache.jasper.runtime.*;
public class SimplePage_jsp extends HttpJspBase {
static {
}
public SimplePage_jsp() {
}
private static boolean _jspx_inited = false;
public final void _jspx_init() throws org.apache.jasper.JasperException
{
276
Глава 8
public void _jspService(HttpServletRequest request, HttpServletResponse
response)
throws java.io.lOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String _value = null;
try {
if (_jspX-inited == false) {
synchronized (this) {
if(_jspx_inited == false) {
_jspx_init();
_jspx_inited = true;
}
}
}
_jspxFactory = JspFactory. getDefaultFactoryO;
response.setContentType("text/html;charset=IS0-8859-1");
pageContext = jspxFactory.getPageContext(this,
request, response, true, 8192, true);
application = pageContext. getServletContextO;
config = pageContext. getServletConfigO;
session = pageContext.getSession();
out = pageContext.getOut();
// begin
[file="C: \\tomcat4\\bin\\..\\webapps\\examples\\jsp\\SimplePage.jsp";from=(0,2);to(2,0)]
out.println("JSP is easy");
11 end
// HTML Ц begin
[file="C: \\tancat4\\bin\\.. \\webapps\\examples\\jsp\\SimplePage. jsp"; f rom=(2,2); to(3,0) ]
out. write("\r\n");
// end
}
catch (Throwable t) {
if (out != null && out.getBufferSizeO != 0)
out.clearBuffer();
if (pageContext != null)
pageContext.handlePageException(t);
}
finally {
if (JspxFactory != null)
Основы JSP
277
_jspxFactory.releasePageContext(pageContext);
}
}
}
Отложим полное объяснение этого кода до тех пор, пока мы не узнаем больше
о том, как интерфейсы и классы используются для выполнения страницы JSP.
API JSP
Технология JSP основывается на API JSP, который состоит из двух пакетов:
javax.servlet.jsp и javax.servlet.jsp.tagext. Оба пакета подробно представлены в
приложениях D и Е. В этой главе обсуждаются классы и интерфейсы пакета
javax.servlet.jsp, а пакет javax.servlet.jsp.tagext рассматривается в главе 11.
В дополнение к этим двум пакетам JSP нужны также два пакета сервлетов —
javax.servlet и javax.servlet.http. После изучения пакета javax.servlet.jsp станет
понятно, почему JSP является расширением технологии сервлетов и почему
важно, чтобы программист приложений JSP хорошо понимал технологию сер-
влетов.
Пакет javax.servlet.jsp имеет два интерфейса и четыре класса. Это интерфейсы:
• JspPage
• HttpJspPage
И классы:
• JspEnginelnfo
• Jsp Factory
• JspWriter
• PageContext
Кроме этого, существуют два класса исключений: JspException и JspError.
Интерфейс JspPage
JspPage является интерфейсом, который должен быть реализован всеми
классами сервлетов JSP. Он может напомнить вам интерфейсjavax.servelt.Servlet
(см. главу 1). И не удивительно, так как интерфейс JspPage расширяет интер-
фейс javax.servlet.Servlet.
Интерфейс JSPPage содержит два метода, Jsplnit и JspDestroy, сигнатуры
которых имеют следующий вид:
public void jsplnit()
public void jspDestroyO
278
Глава 8
jsplnit, который аналогичен методу init интерфейса javax.servlet.Servlet, вы-
зывается при создании объекта JspPage и может использоваться для выполне-
ния некоторой инициализации. Этот метод вызывается только один раз во
время жизненного цикла страницы JSP — при первом вызове страницы JSP.
Метод jspDestroy аналогичен методу destroy интерфейса javax.servlet.Servlet.
Этот метод вызывается перед уничтожением объекта сервлета JSP. При жела-
нии этот метод можно использовать для выполнения очистки. Однако авторы
JSP редко используют эти два метода в полной мере. Следующий пример пока-
зывает, как можно реализовать эти методы на странице JSP:
<%
public void jsplnit() {
System.out.println("Init”);
}
public void jspDestroyO {
System.out.println("Destroy”);
}
%>
<%
out.println("JSP is easy");
%>
Отметим, что первая строка кода начинается с <%!. Эта конструкция описы-
вается в главе 9.
Интерфейс HttpJspPage
Этот интерфейс непосредственно расширяет интерфейс JspPage и предос-
тавляет только один метод: _JspService. Этот метод вызывается контейнером JSP
для генерации содержимого страницы JSP. Метод JspService имеет сигнатуру:
public void _jspService(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException
Этот метод нельзя использовать на странице JSP так, как показано ниже:
<%!
public.void jsplnit() {
System.out.println("Init”);
}
public void jspDestroyO {
System.out.println("Destroy");
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response) throws ServletException, lOException {
System.out.println("Service");
}
%>
Основы JSP
279
Это связано с тем, что само содержимое страницы представляет этот метод
(см. ниже раздел «Повторное обращение к созданному сервлету»).
Класс JspFactory
JspFactory является абстрактным классом, который предоставляет методы
для получения других объектов, необходимых для обработки страницы JSP.
Этот класс имеет статический метод get Default Factory, возвращающий объект
JspFactory. Из объекта JspFactory можно получить объекты PageContext и
JspEnginelnfo, полезные для обработки страницы JSP. Эти объекты получают
с помощью метода getEnginelnfo класса JspFactory и метода getPageContext, сиг-
натуры которых приведены ниже:
public abstract JspEnginelnfo getEnginelnfoO
public abstract PageContext getPageContext (
Servlet requestingServlet, ServletRequest request,
ServletResponse response, String errorPageURL,
boolean needsSession, int buffer, boolean autoFlush)
Следующий код является частью метода JspService, который генерирует-
ся контейнером JSP:
JspFactory _jspxFactory = null;
PageContext pageContext = null;
jspxFactory = JspFactory. getDefaultFactoryO;
pageContext = _jspxFactory. getPageContext(this, request,
response, true, 8192, true);
Класс JspEnginelnfo
JspEnginelnfo является абстрактным классом, который предоставляет ин-
формацию о контейнере JSP. Его метод getSpecificationVersion возвращает
номер версии контейнера JSP. Так как это единственный доступный в настоя-
щее время метод, этот класс используется не часто.
Можно получить объект JspEnginelnfo с помощью метода getEnginelnfo
класса JspFactory.
Класс PageContext
PageContext предоставляет методы, зависящие от реализации. Сам класс
PageContext является абстрактным, поэтому в методе JspService класса серв-
лета JSP объект PageContext получают путем вызова метода getPageContext
класса JspFactory.
280
Глава 8
Класс PageContext предоставляет методы, которые используются для со-
здания других объектов. Например, его метод getOut возвращает объект
JspWriter, служащий для отправки строк web-браузеру. К другим методам, воз-
вращающим связанные с сервлетами объекты, относятся:
• getRequest, возвращает объект ServletRequest
• getResponse, возвращает объект ServletResponse
• getServletConfig, возвращает объект ServletConfig
• getServletContext, возвращает объект ServletContext
• getSession, возвращает объект HttpSession
Класс JspWriter
Класс JspWriter является производным от класса java.io.Writer и представ-
ляет Writer, который можно использовать для записи в браузер клиента. Из его
многочисленных методов наиболее важными являются print и printin. Оба
имеют перегружаемые версии, что гарантирует возможность записи любого
типа данных. Различие между print и printin состоит в том, что printin всегда
добавляет символ новой строки в выводимые данные.
Дополнительные методы позволяют манипулировать буфером. Например,
метод clear очищает буфер. Он порождает исключение, если содержимое бу-
фера уже было сброшено. Метод clearBuffer тоже очищает буфер, но никогда
не порождает никаких исключений.
Анализ сгенерированного сервлета
Теперь, имея представление о различных интерфейсах и классах пакета
javax.servlet.jsp, проанализируем созданный ранее сервлет JSP.
Первое, что мы видим при изучении кода:
public class SimplePage_jsp extends HttpJspBase<{(l,xju.,,.
F
Обсуждение должно начаться с вопросов: «Что такое HttpJspBase?» и «По-
чему класс сервлета JSP не реализует интерфейс javax.servlet.jsp.JspPage или
javax.servlet.jsp. HttpJsp Page?»
Прежде всего напомним, что спецификация JSP определяет только стан-
дарты для написания страниц JSP. Сама страница JSP транслируется в файл
java, который в свою очередь компилируется в класс сервлета. Эти два процес-
са зависят от реализации и не влияют на способ кодирования страницы JSP.
Поэтому контейнер JSP свободен в выборе способа трансляции страницы.
Сгенерированный сервлетом JSP код, представленный в этой главе, взят из
Tomcat. Поэтому можно ожидать, что другими контейнерами JSP будет гене-
рироваться другой файл Java.
Основы JSP
281
В Tomcat исходный код класса HttpJspBase находится в каталоге src\jasper\
src\share\org\apache\jasper\runtime\ внутри каталога %CATAL1NA_HOME%.
Сигнатура этого класса имеет вид:
public abstract class HttpJspBase extends HttpServlet
implements HttpJspPage
Теперь можно видеть, что HttpJspBase является HttpServlet, и он реализует
интерфейс javax.servlet.jsp.HttpJspPage. HttpJspBase более всего напоминает
класс оболочки, так что его производный класс не должен предоставлять реа-
лизацию для методов интерфейса, если автор страницы JSP их не переопреде-
ляет. Ниже приводится листинг класса:
/ *
* The Apache Software License, Version 1.1
* Copyright (C) 1999 The Apache Software Foundation. All rights
* reserved.
★
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
★ 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
★
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following diclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. The end-user documentation included with the redistribution, if
★ any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
★ if and wherever such third-party acknowlegements normally appear.
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
★ from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
282
Глава 8
* 5. Products derived from this software may not be called "Apache”
* nor may ."Apache” appear in their names without prior written
* permission of the Apache Group.
★
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES. INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE
* FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED ANDON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
★
*/
package org.apache.jasper.runtime;
import java.io.lOException;
import java. io.Fileinputstream;
import java,, io. InputStreamReader;
import java.net.URL;
import java.net.MalformedURLException;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.JasperException;
import оrg.apache.jasper.Constants;
Основы JSP
283
I * ★
* Это подкласс всех сгенерированных JSP сервлетов.
★
* ©author Anil К. Vijendran
* /
public abstract class HttpJspBase extends HttpServlet
implements HttpJspPage {
protected PageContext pageContext;
protected HttpJspBaseO {
}
public final void init(ServletConfig config)
throws ServletException {
super.init(config);
jsplnit();
}
public String getServletlnfoO {
return Constants.getString(”jsp.engine.info’’);
}
public final void destroyO {
jspDestroyO;
}
/**
* Точка входа в службу
★ I
public final void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
_jspService(request, response);
}
public void jspInitO {
}
public void jspDestroyO {
)
public abstract void _jspService(HttpServletRequest request,
284
Глава 8
HttpServletResponse response)
throws ServletException, lOException;
}
Возвращаясь к сгенерированному классу сервлета JSP, вспомним, что ис-
ходный код страницы JSP — это три строки:
<%
out.printlnC’JSP is easy’’);
%>
Здесь в исходном коде нет методов jspInit и jspDestroy, поэтому нет реализа-
ций этих двух методов в коде создаваемого сервлета. Однако три строки кода
транслируются в метод JspService. Для удобства изложения воспроизведем
метод. Отметим, что для ясности комментарии удалены.
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, ServletException {
JspFactory JspFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String _value = null;
try {
if (_jspx_inited == false) {
synchronized (this) {
if (_jspx_inited == false) {
_jspx_init();
_jspx_inited = true;
}
}
}
JspxFactory = jspxFactory.getDefaultFactory();
response.setContentType("text/html;charset=IS0-8859-1”);
pageContext = jspxFactory.getPageContext(this,
request, response, true, 8192, true);
application = pageContext. getServletContextO;
Основы JSP
285
config = pageContext. getServletConfig();
session = pageContext. getSession();
out = pageContext. getOutO;
out. printing JSP is easy”);
out.write("\r\n");
}
catch (Throwable t) {
if (out != null && out.getBufferSizeO != 0)
out.clearBuffer();
if (pageContext != null)
pageContext.handlePageException(t);
}
finally {
if (_jspxFactory != null)
JspxFactory. releasePageContext(pageContext); 4
}
}
Неявные объекты
Сгенерированный исходный код сервлета JSP содержит несколько объявле-
ний объектов в методе _jspService. Обратимся еще раз к следующей части
кода:
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String _value = null;
try {
JspxFactory = jspFactory.getDefaultFactoryO;
response.setContentType(”text/html;charset=IS0-8859-1”);
286
Глава 8
pageContext = jspxFactory.getPageContext(this,
request, response, true, 8192, true);
application = pageContext. getServletContextO;
config = pageContext. getServletConfigO;
session = pageContext.getSession();
out = pageContext. getOutO;
Здесь имеются объектные ссылки: pageContext, session, application, config,
out и т.д. Эти ссылки создаются при их использовании внутри страницы. Они
автоматически доступны автору страницы JSP. Эти объекты называются неяв-
ными объектами. Все они представлены в таблице 8.1.
Таблица 8.1. Неявные объекты JSP
Объект
request
response
out
session
application
config
pageContext
page
exception
Тип данных
javax.servlet.http. HttpServletRequest
javax.servlet.http.httpServletResponse
javax.servlet.jsp.Jsp Writer
javax.servlet.http. HttpSession
javax.servlet.ServletContext
javax.servletServletConfig
javax.servlet.jsp. PageContext
javax.servlet.jsp. HttpJspPage
javaJang.Throwable
Теперь вам понятно, почему можно послать строку текста, записав:
<%
out.printin("JSP is easy”);
%>
Здесь используется следующий неявный объект out, который представляет
javax.servlet.jsp.JspWriter. Все неявные объекты кратко обсуждаются ниже.
Основы JSP
287
Неявные объекты request и response
В сервлетах оба объекта передаются контейнером сервлетов методу service
класса javax.servlet.http.HttpServlet. Его сигнатура:
protected void service(HttpServletRequest request,
HttpServletResponse)
throws ServletException, lOException
В сервлете перед отправкой выходных данных требуется вызвать
setContentType из HttpServletResponse, чтобы сообщить браузеру тип содер-
жимого:
• response.setContentType("text/html");
В JSP это делается автоматически в сгенерированном классе сервлета JSP:
response.setContentType("text/html;charset=IS0-8859-1");
Имея HttpServletRequest и HttpServletResponse, можно делать все, что угод-
но, так же как и в сервлете. Например, следующая страница JSP извлекает
значение параметра с именем firstName и выводит его в браузере:
>
<HTML>
<HEAD>
<TITLE>Simple Page</TITLE>
</HEAD>
</BODY>
<%
String firstName = request.getParameter("firstName");
out.println(”First name: ” + firstName);
%>
</BODY>
</HTML>
Следующий пример использует метод sendRedirect из
javax.servlet.http.HttpServletResponse для переадресации пользователя на
другой URL:
<%
response.sendRedi rect("http://www.newriders.com”);
%>
288
Глава 8
Неявный объект out
Это, вероятно, наиболее часто используемый неявный объект. Вызывают
либо его метод print, либо метод println для отправки текста или других дан-
ных в браузер клиента. В сервлете всегда необходимо вызывать метод getWriter
интерфейса javaxc.servlet.http.HttpServletResponse, чтобы получить PrintWriter,
прежде чем можно будет вывести что-нибудь в браузере:
PrintWriter out = response. getWriterO;
В JSP этого делать не нужно, так как уже имеется out, который представля-
ет объект javax.servlet.jsp Jsp Writer.
Неявный объект session
Неявный объект session представляет объект HttpSession, который можно
извлечь в сервлете, вызвав следующий метод getSession интерфейса
javax.servlet.http.HttpServletRequest:
request.getSession();
Следующий код является страницей JSP, которая использует объект session
для реализации счетчика:
<HTML>
<HEAD>
<TITLE>Counter</TITLE>
</HEAD>
<BODY>
<%
String counterAttribute = (String) session.getAttribute(’’counter’’);
int count = 0;
try {
count = Integer.parselnt(counterAttribute);
}
catch (Exception e) {
}
count++;
session.setAttribute("counter”, integer.toString(count));
out.println(’’This is the ” + count + ”th time you visited this page in
this
session.”);
%>
</B0DY>
</HTML>
Основы JSP
289
Обратите внимание, как легко получить доступ к session.
Отметим, что объект session доступен только на
странице JSP, которая участвует в управлении сеансом
(см. главу 5).
application
Неявный объект application представляет объект javax.servlet.ServletContext.
В HttpServlet можно извлечь объект ServletContext, используя метод
getServl et Context.
config
Неявный объект config представляет объект javax.servlet.ServletConfig,
который в сервлете можно получить с помощью метода getServletConfig.
pageContext
Неявный объектpageContext представляет объект javax.servlet.jsp. PageContext,
рассмотренный выше.
раде
Неявный объект page представляет интерфейс javax.servlet.jsp.HttpJspPage,
рассмотренный выше.
exception
Объект exception доступен только на страницах, которые были определены
как страницы ошибок.
Заключение
Эта глава является введением в технологию JSP. Можно создать страницу
JSP, не имея представления о базовом API. Однако знакомство с классами и
интерфейсами пакета javax.servlet.jsp и понимание того, как JSP расширяет
технологию сервлетов, необходимы для написания более мощного и эффек-
тивного кода.
292
Глава 9
Таблица 9.1. Атрибуты директивы page
Атрибут Тип значения Значение по умолчанию
language Название языка сценариев "java"
info String Зависит от контейнера JSP
contentType Тип MIME, набор символов "text/html ;charset= I SO-8859-Г
extends Имя класса Нет
import Полностью квалифицированное Нет
имя класса или имя пакета
buffer Размер буфера или false 8192
auto Flush Boolean "true"
session Boolean "true”
isThreadSafe Boolean "true"
errorPage URL Нет
is Error Page Boolean "false"
Приведем пример использования директивы page:
< %© page buffer=”16384" session=”false" %>
Можно определить несколько директив page на странице JSP:
< %@ page buffer=”16384" %>
< %© раде session=”false” %>
Однако, за исключением атрибута import, JSP не позволяет повторять один
и тот же атрибут в директиве page или в нескольких директивах на одной и той
же странице.
Следующий фрагмент недопустим, так как атрибут info появляется более
одного раза на странице:
< %@ page info="Example Раде” %>
< %@ раде buffег=”16384” %>
< %@ раде info=”Unrestricted Access” %>
Следующая запись тоже неверна, так как атрибут buffer появляется более
одного раза в одной и той же директиве page:
< %@ page buffer=”16384” info="Example Раде” buffеr="8192” %>
Однако этот код корректен, поскольку атрибут import может появляться не-
сколько раз:
Синтаксис JSP
293
< %@ page import="java.io.*" info="Example Page" %>
< %© page import="java.util.Enumeration" %>
< %@ page import="com.brainysoftware.web.FileUpload" %>
Импортируемые библиотеки могут быть перечислены в одном атрибуте import
через запятую:
< %@ page import="java.io.*, java.util.Enumeration" %>
Обсудим атрибуты более подробно и исследуем их воздействие на создава-
емый сервлет.
Атрибут language
Атрибут language определяет язык сценариев, используемый на странице JSP.
Значением по умолчанию является «java», и все контейнеры JSP должны под-
держивать Java как язык сценариев. В Tomcat это единственный допустимый
язык. Однако другие контейнеры JSP могут поддерживать и другие языки.
Определение этого атрибута не оказывает никакого влияния на создавае-
мый сервлет:
< %@ page language="java" %>
<%©
out.println("JSP is easy");
%>
Фактически этот атрибут полезен только в контейнере JSP, который под-
держивает в качестве языка сценариев язык, отличный от Java.
Атрибут info
Атрибут info позволяет вставить любую строку, которая позже может быть
извлечена с помощью метода getServletlnfo. Например, можно записать сле-
дующую директиву page с атрибутом info:
< %@ page info="Written by Bulbul" %>
Контейнер JSP создаст открытый метод getServletlnfo в генерируемом серв-
лете. Этот метод будет возвращать значение атрибута info, определенного в
page. Для приведенной выше директивы page метод getServletlnfo будет запи-
сан следующим образом:
public String getServlet!nfo() {
return "Written by Bulbul";
На той же странице JSP можно извлечь значение атрибута info, вызвав метод
getServletlnfo. Например, следующая страница JSP будет возвращать «Written by
Bulbul» клиентскому браузеру:
296
Глава 9
Атрибут buffer
По умолчанию содержимое страницы JSP буферизуется для увеличения про-
изводительности. Используемый по умолчанию размер буфера равен 8 Кбайт
или 8192 символа.
Рассмотрим следующий пример, который определяет атрибут buffer с раз-
мером 16 Кбайт:
< %@ page buffer=”16Kb" %>
Этот атрибут используется в методе _jspService в генерируемом классе сер-
влета.
Размер буфера передается в метод getPageContext класса javax.servlet.jsp.
JspFactory при создании объекта PageContext.
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, Servlet Exception {
try {
JspFactory = JspFactory. getDefaultFactoryO;
response.setContentType("text/html;charset=IS0-8859-1");
// 16384 в следующей строке представляет размер буфера
pageContext = jspxFactory.getPageContext(this, request,
response, "", true, 16384, true);
}
catch (Throwable t) {
}
finally {
Синтаксис JSP
297
С помощью атрибута buffer директивы page можно сделать две вещи:
• Отказаться от буфера, определив значение «попе»:
<%@ page buffer='’none:' %>
Если решено не использовать буфер, то метод getPageContext будет вызы-
ваться с 0 в качестве размера буфера:
pageContext = JspxFactory. getPageContext(
this, request, response, true, 0, true);
• Изменить размер буфера, присвоив атрибуту некоторое число. Значение
атрибута представляет число килобайтов. Поэтому «16» означает 16 Кбайт
или 16 384 символа. Можно записать только число либо число и «КЬ». На-
пример, «16» и «16КЬ» означают одно и то же.
В следующем примере показаны две директивы page в двух различных
страницах JSP. Первая директива отменяет использование буфера, а вторая
изменяет размер буфера на 12 Кбайт.
< %@ page import=”java.io.*” buffer=”none” %>
< %@ page import=”java.io.*" buffer=”12” %>
Отметим, что для улучшения производительности
контейнер JSP может по своему усмотрению
использовать размер буфера, больший заданного.
Атрибут autoFlush
Атрибут autoRush связан с буфером страницы. Если значение атрибута равно
true, контейнер JSP будет автоматически сбрасывать буфер по его заполнении.
Если же значение равно false, автор JSP должен сбрасывать буфер вручную с по-
мощью метода flush объекта JspWriter следующим образом:
out.flush();
Например, следующий код определяет значение false для атрибута auto Rush:
< %@ page autoFlush=’’false" %>
Атрибут session
По умолчанию страница JSP участвует в управлении сеансом контейнера JSP.
Это указывается объявлением объектной ссылки javax.servlet.http.HttpSession
и ее созданием с помощью методаgetSession из javax.servlet.jsp.PageContext, как
иллюстрирует следующий код:
300
Глава 9
При вызове этой страницы будет выводиться страница ошибки. На стра-
нице ошибки должен быть определен атрибут isErrorPage в директиве page, и
значение этого атрибута должно быть true.
Ниже показан атрибут errorPage, которому присваивается URL страницы
ошибки:
<%@ page errorPage="myJspApp/ErrorPage.jsp' %>
Атрибут isErrorPage
Атрибут isErrorPage может получать значение true или false, значением по
умолчанию является false. Атрибут указывает, является или нет текущая
страница страницей ошибки, т.е. страницей, которая будет выводиться при
возникновении непредвиденного исключения на другой странице JSP. Если
текущая страница является страницей ошибки, то она имеет доступ к неявному
объекту исключения (см. главу 8).
Директива include
Директива include является вторым типом директивных элементов JSP. Эта
директива позволяет автору страницы JSP включать содержимое других фай-
лов в текущую страницу JSP.
Директива include полезна в том случае, если имеется общий источник,
который будет применяться более чем одной страницей JSP. Вместо повто-
рения одного и того же кода на каждой странице JSP, что создает проблемы
сопровождения, можно поместить общий код в отдельный файл и использо-
вать директиву include на каждой странице JSP.
Включаемая страница может быть статической, например файлом HTML,
или динамической, например страницей JSP. Включаемая страница сама мо-
жет включать другой файл. Поэтому в JSP разрешены вложенные директивы
include.
Директива include имеет следующий синтаксис:
<%@ include file="relativeURL" %>
Приведем пример включения файлов HTML:
<%@ include file=”includes/header.html” %>
<%
out.р гi nt1n(”<BODY>”);
// другое содержимое
%>
<%@ include file=’’includes/footer. html” %->
Синтаксис JSP
301
Если часть relativeURL начинается с символа слэша (/), то она
интерпретируется как абсолютный путь доступа на сервере.
В противном случае она интерпретируется относительно
текущей страницы JSR
Теперь рассмотрим, как включаемые файлы транслируются в файл сервле-
та JSP. Следующий пример является простой страницей JSP, которая включа-
ет два файла HTML: Header.html и Footer.html. Оба файла расположены в под-
каталоге includes, который сам находится в каталоге, где размещен текущий
файл JSP. Страница JSP называется SimplePage.jsp и представлена в листинге
9.1. Страница выводит текущее время сервера. Header.html представлен в лис-
тинге 9.2, a Footer.html — в листинге 9.3. Нас интересует создаваемый код сер-
влета.
Листинг 9.1 Простая страница JSP, которая включает в себя два файла
< %@ page session="false" %>
< %@ раде import="java.util.Calendar” %>
< %@ include file="includes/Header.html" %>
< %
out.println(”Current time: ” + Calendar.getInstance().getTime());
%>
< %@ include file=”includes/Footer.html" %>
Листинг 9.2 Файл Header.html
<HTML>
<HEAD>
<TITLE>Welcome</TITLE>
<BODY>
Листинг 9.3 Файл Footer.html
</BODY>
</HTML>
Генерируемый сервлет имеет следующий вид:
package org.apache.jsp;
import java.util.Calendar;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
304
Глава 9
finally {
if (.jspxFactory != null) .jspxFactory.releasePageContext(pageContext);
}
}
}
Строки, выделенные жирным шрифтом, обозначают начало включенного
файла. Так как все включенные файлы помещаются в код до компиляции,
включение файлов не оказывает никакого влияния на производительность
приложения.
Директива taglib
Директиву taglib, или tag library, можно использовать для расширения функ-
циональности JSP. Это обширная тема и ей должна быть выделена отдельная
глава (см. главу 11).
Элементы создания сценариев
Элементы создания сценариев позволяют вставлять код Java в страницы JSP.
Существуют три типа элементов создания сценариев:
• Скриптлеты
• Объявления
• Выражения
Эти типы элементов рассматриваются ниже.
Скриптлеты
Мы уже встречали скриптлеты в примерах. Скриптлеты — это блоки кода
страницы JSP. Скриптлеты начинаются с открывающего тега <% и заканчива-
ются закрывающим тегом %>.
ГПоимечание'! Директивы также начинаются с <% и заканчиваются %>.
Ч и — „ J Однако в директиве вслед за <% идет
Следующая страница JSP является примером использования скриптлетов.
На странице JSP устанавливается соединение с базой данных и извлекаются
все записи из таблицы с именем Users. Среди полей в таблице Users имеются
FirstName, LastName, UserName и Password. При получении объекта ResultSet
все записи выводятся в таблице HTML. Страница JSP представлена в листин-
ге 9.4.
Синтаксис JSP
305
Листинг 9.4 Вывод записей базы данных
<%© page session=’’false” %>
<%@ page import=”java.sql.*” %>
<%
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
System, out.println("JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
%>
<HTML>
<HEAD>
<TITLE>Display All Users</TIT|_E>
</HEAD>
<BODY>
<CENTER>
<BR><H2>Displaying All Users</H2>
<BR>
<BR>
<TABLE>
<TR>
<TH>First Name</TH>
<TH>Last Name</TH>
<TH>User Name</TH>
<TH>Password</TH>
</TR>
<%
String sql = "SELECT FirstName, LastName, UserName, Password" +
" FROM Users";
t ry {
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
out.println("<TR>");
out.println("<TD>" + rs.getString(l) + "</TD>");
out.println("<TD>" + rs.getString(2) + "</TD>");
out.println("<TD>" + rs.getString(3) + "</TD>");
out.printing"<TD>" + rs.getString(4) + "</TD>");
out.println("</TR>");
}
rs.closeO;
s.closeO;
con.close();
}
308
Глава 9
Листинг 9.5 Продолжение
Connection con = DriverManager.getConnection(”jdbc:odbc:JavaWeb”);
Statement s = con.createStatementO;
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
X>
<TR>
< TDXX out.print(rs.getString(l)); %><7TD>
< TDXX out.printers.getString(2)); ХхД0>
< TDXX out.printers.getStringe3)); XX/TD>
< TDXX out.printers.getStringe4)); XX/TD>
</TR>
<%
}
rs.closeO;
s.close();
con.close();
}
catch (SQLException e) {
}
catch (Exception e) {
}
%>
</TABLE>
</CENTER>
</BODY>
</HTML>
Одних скриптлетов недостаточно для написания эффективных страниц JSP.
Как упоминалось ранее, нельзя объявить метод с помощью скриптлетов. Кроме
того, в коде листингов 9.4 и 9.5 страница JSP будет пытаться загрузить драйвер
JDBC каждый раз при обращении к странице. Этого не требуется, поскольку
странице необходимо загрузить его только один раз. Как же выполнить код
инициализации в сервлетах?
Объявления
Объявления позволяют объявлять методы и переменные, которые могут
использоваться в любом месте страницы JSP. Объявления предоставляют так-
же способ создания кода инициализации и очистки с помощью методов jsplnit
и jsp Destroy.
Объявление начинается с тега <%! и заканчивается тегом %> и может на-
ходиться влюбом месте страницы. Например, объявление метода может появиться
Синтаксис JSP
313
String encodeHtmlTag(String tag) {
if (tag==null)
return null;
int length = tag.length();
StringBuffer encodedTag = new StringBuffer(2 * length);
for (int i=0; Klength; i++) {
char c = tag.charAt(i);
if (c==’<’)
encodedTag.append(”<”);
else if (c==’>’)
encodedTag.append(”>");
else if (c==’&’)
encodedTag.append(”& ”);
else if (c==’’”)
encodedTag. append("" ”);
//when trying to output text as tag’s value as in
/1 values=”???”.
else if (c==’ ’)
encodedTag.append(” ’’);
else
encodedTag.append(c);
}
return encodedTag.toStringO;
}
%>
<%@ page session=”false” %>
<%@ page import=”java.sql.*” %>
<HTML>
<HEAD>
<TITLE>Display All Users</TITLE>
</HEAD>
<B0DY>
<CENTER>
<BR><H2>Displaying All Users</H2>
<BR>
<BR>
<TABLE>
<TR>
<TH>First Name</TH>
<TH>Last Name</TH>
<TH>User Name</TH>
<TH>Password</TH>
</TR>
<%
String sql = ’’SELECT FirstName, LastName, UserName, Password’’ +
” FROM Users”;
312
Глава 9
Листинг 9.7 Продолжение
rs.closeO;
s.closeO;
con.closeO;
}
catch (SQLException e) {
}
catch (Exception e) {
}
%>
</TABLE>
</CENTER>
</BODY>
</HTML>
Код инициализации и очистки
Код листингов 9.4, 9.5 и 9.7 содержит ненужные повторения: страница JSP
пытается загрузить драйвер JDBC при каждом вызове страницы. Это лишь
небольшой пример. В реальной жизни могут возникать аналогичные случаи,
когда необходимо выполнять инициализацию и очистку, т.е. требуется, чтобы
часть кода выполнялась только при первой инициализации сервлета JSP или
при его уничтожении.
В главе 8 указывалось, что каждый сервлет, сгенерированный из страницы
JSP, должен прямо или косвенно реализовать интерфейс javax.servlet.jsp.JspPage.
Этот интерфейс имеет два метода: jspInit и jspDestroy. Контейнер JSP вызывает
jsplnit при инициализации сервлета JSP и jspDestroy, когда сервлет JSP должен
быть удален. Эти два метода предоставляют способ инициализации и очистки
кода. С помощью объявлений можно переопределить эти два метода.
Модифицируем код листинга 9,7, переместив фрагмент, который загружа-
ет драйвер JDBC, в метод jsplnit. Код представлен в листинге 9.8.
Листинг 9.8 Использование метода jsplnit
<%!
public void jsplnit() {
try {
Class. forName("sun. jdbc. odbc. JdbcOdbcDriver");
System.out.println("JDBC driver loaded");
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
>
}
Синтаксис JSP
317
Синтаксис элемента действия jsp:include имеет две формы. Для элемента
jsp:include, который не содержит пары имя/значение, синтаксис будет следующим:
<jsp:include page="relativeURL" flush="true" />
Если требуется передать информацию во включаемый ресурс, использует-
ся второй синтаксис:
<jsp:include page="relativeURL" flush="true">
(<jsp:param.../> )*
</jsp:include>
Атрибут page представляет U RL включаемого ресурса на локальном серве-
ре. Атрибут flush указывает, будет ли освобождаться буфер. В JSP 1.2 атрибут
flush должен иметь значение true.
Во второй форме звездочка (*) указывает, что существует нуль или больше
элементов в скобках. Следовательно, эту форму можно использовать, даже если
ь.-лючаемому ресурсу не передается никакая информация.
jsp:forward
Элемент действия jsp:fbrward служит для прекращения выполнения теку-
щей страницы JSP и передачи управления другому ресурсу. Можно передать
управление статическому или динамическому ресурсу.
Синтаксис элемента действия jsprforward имеет две формы. Для элемента
jspiforward, который не содержит пар имя_параметра/значение, синтаксис бу-
дет следующим:
<jsp:forward page=”relativeURL” />
Если необходимо передать информацию во включаемый ресурс, использу-
ется вторая синтаксическая форма:
<jsp: forward page="relativellRL”>
(<jsp:param.../>)*
</jsp:include>
Атрибут page представляет URL включаемого ресурса на локальном сервере.
jsp:plugin
Элемент действия jsp:plugin используется для генерации HTML-тегов
<OBJECT> или <EMBED>, содержащих соответствующую конструкцию для
316
Глава 9
Как можно видеть, выражение короче, поскольку возвращаемое значение
автоматически передается в out.print.
Другой пример, блок while, показанный в листинге 9.8, можно переписать
следующим образом с помощью выражений:
while (rs.next()) {
%>
<TR>
< TD><%= encodeHtmlTag(rs.getString(1)) %></TD>
< TD><%= encodeHtmlTag(rs.getString(2)) %></TD>
< TD><%= encodeHtmlTag(rs.getString(3)) %></TD>
< TD><%= encodeHtmlTag(rs.getString(4)) %></TD>
</TR>
<%
}
Стандартные элементы действий
Стандартные элементы действий являются по сути тегами, которые можно
встраивать в страницу JSP. Во время компиляции они также заменяются
кодом Java, который соответствует предопределенной задаче.
К стандартным элементам действий JSP относятся:
• jsp: use Bean
• jsp.setProperty
• jsp :get Property
• jsp.param
• jsp:include
• jsp:forward
• jsp:plugin
• jsp:params
• jsp:fallback
Элементы jsp:use Bean, jsp:setProperty и jsp:getProperty связаны c Bean и рас-
сматриваются в главе 10. Элементjsp:param используется в элементахjsp:include,
jsp:forward и jsp:plugin для предоставления информации в формате имя/значе-
ние и поэтому обсуждается вместе с этими тремя элементами.
jsp:include
Элемент действия jsp:include используется для включения статических или
динамических ресурсов в текущую страницу. Этот элемент аналогичен ди-
рективе include, но jsp:include предоставляет большую гибкость, так как с
его помощью можно передавать информацию включаемому ресурсу.
i • 'Л . -.-Г' /V, ’• *"
10
Разработка JSP Bean
JE^ главах 8 и 9 было показано, как можно создавать страницы JSP, переме-
шивая теги HTML и код Java. Это упрощает и облегчает написание страниц
JSP, однако такой подход обладает, по крайней мере, двумя недостатками. Пер-
вое, получаемую страницу трудно читать. Второе, не существует разделения
реализации представления и бизнес-правил. Написание страницы JSP таким
образом означает, что автор JSP должен хорошо владеть как Java, так и HTML.
Однако поиск программиста Java, который является также хорошим ди-
зайнером страниц HTML, такая же трудная задача, как и поиск дизайнера,
который достаточно опытен в Java. Более эффективным было бы разделение
труда при создании приложения JSP, чтобы дизайнер работал над дизайном
страницы, а программист Java писал код. В JSP это можно сделать с помощью
компонентов JSP, таких как JavaBeans. Программист Java пишет и компилирует
JavaBeans, которые содержат всю функциональность, необходимую в приложе-
нии. Адизайнер в это же время трудится над дизайном страниц. Когда JavaBeans
будут готовы, дизайнер воспользуется тегами, аналогичными HTML, для вызо-
ва методов и свойств Bean на странице JSP.
Фактически использование Bean является весьма распространенной прак-
тикой при разработке приложений JSP. Этот подход популярен потому, что
JavaBeans обеспечивают повторное использование, т. е. вместо создания соб-
ственного кода можно использовать то, что написано другими. Например,
можно купить Bean для пересылки файлов в удаленные системы и тут же за-
няться отправкой файлов.
Разработка JSP Bean
323
brainysoftware
Рис. 10.1. Структура каталогов для класса Bean
4 . Создать страницу JSP, которая будет вызывать созданный Bean. Для этого
примера используйте код листинга 10.2.
Листинг 10.2 Вызов Bean со страницы JSP
<jsp:useBean id=”theBean” class=,,com.brainysoftware.CalculatorBean”/>
<HTML>
<HEAD>
</HEAD>
<BODY>
<%
int i = 4;
int j = theBean.doublelt(i);
out.print(”2*4=" + j);
%>
</BODY>
</HTML>
5 . Перезапустить Tomcat.
6 . Открыть браузер и направить его по URL страницы JSR, написанной на
шаге 4.
Браузер должен вывести результат, показанный на рис. 10.2.
Если JavaBean поставляется в виде файла .jar, скопируйте
файл .jar в каталог lib в каталоге WEB-INF каталога
приложения, чтобы сделать его доступным страницам JSP
приложения.
Краткая теория JavaBeans
Java Beans описываются в многочисленных книгах по Java и в различных сете-
вых ресурсах. Например, прекрасным ресурсом является web-сайт компании Sun:
http://java.sun.com/docs/books/tutorial/javabeans/index.html. Мы же лишь
кратко рассмотрим эту тему.
320
Глава 9
< jsp:declaration declaration code </jsp:declaration>
Скриптлеты
Синтаксис скриптлета:
< % scriptlet code %>
Это эквивалентно следующему синтаксису XML:
< jsp:scriptlet> scriptlet code </jsp:scriptlet>
Выражения
Синтаксис выражения:
<%= expression %>
Это эквивалентно синтаксису XML:
<jsp:expression> expression </jsp:expression>
Шаблонные данные
XML-тег <jsp:text> используется для размещения шаблонных данных в JSP.
Синтаксис имеет следующий вид:
<jsp:text> text </jsp:text>
Заключение
В этой главе представлены синтаксис и семантика JSP. Рассмотрены три типа
элементов JSP: директивы, элементы создания сценариев и элементы действия.
Мы познакомились также с объявлениями, скриптлетами и выражениями и
научились их использовать. В последнем разделе главы обсуждался синтаксис
XML как альтернатива синтаксису <%...%> и было показано, как преобразо-
вать синтаксис JSP в эквивалентный синтаксис XML.
Разработка JSP Bean
325
public PropertyType getPropertyNameO;
Метод get для свойства с именем operand будет называться getOperand.
Имя свойства начинается с буквы в нижнем регистре, но имя метода
использует после «get» букву в верхнем регистре, поэтому — getOperand.
Методы set и get называют также методами доступа (access). В JSP элемен-
ты действия jsp:getProperty и jspsetProperty используются для вызова соответ-
ственно get- и set-методов. Однако можно вызывать эти методы таким же об-
разом, как и обычные методы.
Обеспечение доступа к Bean
Прежде чем можно будет использовать Bean на странице JSP, необходимо сде-
лать Bean доступным с помощью элемента действия jsp:use Bean. Атрибуты этого
элемента позволяют управлять поведением Bean. Синтаксис элемента
jsp:useBean имеет две формы. Первая форма применяется, когда не требуется
писать никакого кода инициализации, а вторая форма используется, если при
инициализации Bean необходимо выполнить код Java. Код инициализации
обсуждается в следующем разделе.
Формы элемента действия jsp:useBean:
<jsp:useBean (attribute="value")+/>
и
<jsp:useBean (attribute="value")+>
код инициализации
</jsp:useBean>
Запись (attribute—'value")+ означает, что должен присутствовать один или
несколько атрибутов. В элементе действия jsp:useBean могут использоваться
пять атрибутов:
• id
• class
• type
• scope
• beanName
Должен присутствовать атрибут class или type.
id
Атрибут id определяет уникальный идентификатор для Bean. Этот иденти-
фикатор может использоваться на всей странице, и можно рассматривать его
322
Глава 10
Отметим, что при создании JavaBeans для страницы
JSP повторное использование и модульность являются
крайне важными. Поэтому обычно Bean не применяют
для отправки HTML-тегов web-браузеру, так как это
потребует подгонки Bean для конкретной страницы. Для
выполнения этой задачи лучше выбрать
пользовательские теги (см. главу 11).
В этой главе рассказывается об использовании JavaBeans на страницах JSP.
Вызов Bean на странице JSP
В этом разделе дается последовательное руководство по написанию и установке
простого Bean с именем CalculatorBean, который является частью пакета
com.brainysoftware. После развертывания можно будет вызывать Bean на стра-
нице JSP. В целом необходимо выполнить следующие шаги:
1. Написать следующий класс Bean (см. листинг 10.1). Сохранить его как
CalculatorBean.java.
Листинг 10.1 CalculatorBean
package com.brainysoftware;
public class CalculatorBean {
public int doublelt(int number) {
return 2 * number;
}
}
Код имеет один открытый класс с именем double It, который возвращает
целое число в качестве результата умножения аргумента на 2.
2. Откомпилировать Bean, чтобы получить файл класса с именем
CalculatorBean.class.
3. Скопировать файл класса Bean в подкаталог classes в WEB-INF каталога
приложения. При развертывании должно учитываться имя пакета. В
данном случае необходимо создать каталог с именем сот в каталоге
classes. В сот создайте подкаталог с именем brainysoftware. Скопируйте
файл CalculatorBean.class в каталог brainysoftware.
В главе 16 будут рассмотрены другие способы
развертывания Bean в приложении JSP.
1/лпгоплгг\Ц пт/ЯТЯИЯ НЯ ПИС 10.1.
Разработка JSP Bean
327
или jspiinclude. Страница пересылки или включаемая страница может приме-
нять Bean, не имея элемента действия jspiuseBean. Например, внутри страни-
цы пересылки или включаемой страницы можно использовать элементы дей-
ствия jsp:getProperty и jsp:setProperty, которые указывают на Bean, созданный
на исходной странице.
session
Bean с областью действия session помещается в объект сеанса пользователя.
Экземпляр Bean будет существовать до тех пор, пока существует объект сеанса
пользователя. Другими словами, Bean будет доступен на других страницах.
Поскольку экземпляр Bean помещается в объект сеанса, нельзя использо-
вать эту область действия, если страница, на которой создается экземпляр Bean,
не участвует в управлении сеансом контейнера JSP. Например, следующий код
будет порождать ошибку:
<%@ page session="false" %>
<jsp:useBean id="theBean" scope="sessiorT
class="com.brainysoftware.CalculatorBean"/>
application
Bean с областью действия application существует в течение времени жизни
самого контейнера JSP. Он доступен с любой страницы приложения.
beanName
Атрибут beanName представляет имя Bean, которое ожидает получить ме-
тод создания экземпляра из класса java.beans.Beans. Например, следующий
jspiuseBean создает экземпляр Bean с именем com.newriders.HomeLoanBean:
<jsp:useBean id="theBean” class=”com.newriders.HomeLoanBean"/>
Можно импортировать пакет com.newriders, используя директиву page, и
ссылаться на класс с помощью его имени следующим образом:
<%@ page import="com.newriders" %>
<jsp:useBean id="theBean” class="Homel_oanBean" />
---------------x Bean доступен на странице после элемента действия
х р jspiuseBean. До этой точки он недоступен.
324
Глава 10
SM
Рис. 10.2. Результат вызова Bean со страницы JSP
В действительности Bean является классом Java. Не требуется расширять
никакой базовый класс или реализовывать какой-либо интерфейс. Однако
чтобы быть Bean, класс Java должен следовать некоторым правилам, опреде-
ленным спецификациями JavaBeans. В отношении JavaBeans, которые могут
использоваться на странице JSP, это следующие правила:
• Класс Bean должен иметь конструктор без аргументов. В Bean лис-
тинга 10.1 класс вообще не имеет конструктора. Это допустимо, так как
компилятор Java будет автоматически создавать конструктор без аргу-
ментов для любого класса Java, не имеющего конструктора.
• При необходимости Bean может иметь открытый метод, используе-
мый для задания значения свойства. Этот метод не возвращает никакого
значения, и его имя начинается с «set», после чего следует имя свойства.
Метод имеет сигнатуру:
public void setPropertyName (PropertyType value);
Например, set-метод для свойства operand должен называться setOperand.
Отметим, что имя свойства начинается с буквы в нижнем регистре, но
имя set-метода использует букву в верхнем регистре; поэтому —
setOperand.
• При необходимости Bean может иметь открытый метод, вызывае-
мый для получения значения свойства. Возвращаемый этим методом тип
совпадает с типом свойства. Его имя должно начинаться с «get», после
чего следует имя свойства. Метод имеет сигнатуру:
Разработка JSP Bean
329
application = pageContext. getServletContextO;
config = pageContext. getServletConfigO;
session = pageContext.getSession();
out = pageContext.getOut();
// объектная ссылка на Bean
com.brainysoftware.CalculatorBean theBean = null;
boolean _jspx_specialtheBean = false;
synchronized (pageContext) {
theBean = (com.brainysoftware.CalculatorBean)
pageContext.getAttribute("theBean", PageContext.PAGE_SCOPE);
if ( theBean == null ) {
_jspx_specialtheBean = true;
try {
theBean = (com.brainysoftware.CalculatorBean)
java.beans.Beans.instantiate(this.getClass().getClassLoader(),
"com. brainysoftware.CalculatorBean");
}
catch (Exception exc) {
throw new ServletEoeption (
" Cannot create bean of class " +
"com.brainysoftware.CalculatorBean", exc);
}
pageContext.setAttribute("theBean", theBean, PageContext.PAGE_SCOPE);
}
}
if (_jspx_specialtheBean == true) {
}
out.write("\r\n");
int i = 4;
int j = theBean.doublelt(i);
out.println("2*4=" + j);
}
catch (Throwable t) {
if (out != null && out.getBufferSizeO != 0)
out.clearBuffer();
if (pageContext ! = null) pageContext.handlePageException(t);
}
finally {
if (JspxFactory != null) JspxFactоry. releasePageContext(pageContext);
}
}
326
Глава 10
как объектную ссылку на Bean. К значению атрибута id предъявляются те же
самые требования, что и к допустимому имени переменной в текущем языке
создания сценариев.
class
Атрибут class определяет полностью квалифицированное имя класса
JavaBean. Однако полностью квалифицированное имя не требуется, если па-
кет Bean импортирован с помощью директивы page.
type
Если атрибут type присутствует в элементе jspiuseBean, то он определяет
тип класса JavaBean. Это может быть тип самого класса, тип его суперкласса
или интерфейс, который реализует класс Bean. Атрибут type используется не-
часто.
scope
Атрибут scope определяет доступность и область действия Bean. Этот атри-
бут может иметь одно из следующих значений:
• page
• request
• session
• application
Значением по умолчанию атрибута scope является page.
Атрибут scope — мощное средство, которое позволяет управлять тем, как
долго будет существовать Bean.
page
При использовании этого значения Bean будет доступен только на теку-
щей странице, начиная с той точки, где был применен элемент действия
jspiuseBean. Новый экземпляр Bean создается всякий раз, когда запрашивает-
ся страница. Bean будет автоматически уничтожаться после того, как страни-
ца JSP выйдет из области действия; а именно, когда управление перейдет к
другой странице. Если используются теги jspiinclude или jspiforward, Bean бу-
дет недоступен на включаемых страницах или на страницах переадресации.
request
При указании значения request Bean будет доступен на странице пересыл-
ки или включаемой странице, определяемой элементом действия jspiforward
Разработка JSP Bean
331
Во всех формах атрибуту паше присваивается имя экземпляра bean, дос-
тупного на текущей странице JSP.
В первой синтаксической форме атрибуту property присваивается имя
свойства, значение которого будет задаваться, а атрибуту value присваивается
значение свойства.
Следующий пример демонстрирует использование элементов действия
jsp:getProperty и jspisetProperty.
Рассмотрим Bean с именем CalculatorBean, содержащийся в пакете
brainysoftware (см. листинг 10.3). Он имеет закрытую переменную integer с
именем memory. (Отметим, что имя переменной записывается в нижнем реги-
стре.) Он также имеет get-метод с именем getMemory и set-метод с именем
set Memory.
Л исти н г 10.3 CalculatorBean с методами доступа
package com.brainysoftware;
public class CalculatorBean {
private int memory;
public void setMemory(int number) {
memory = number;
}
public int getMemoryO {
return memory;
}
public int doublelt(int number) {
return 2 * number;
}
}
Используя элементы действия jsp:setProperty и jspigetProperty, можно
задать и получить значение memory, как показано на странице JSP в лис-
тинге 10.4.
Л исти нг 10.4 Доступ к свойству с помощью jsp:setProperty и
jsp:getProperty
<jsp:useBean id=”theBean” class="com. brainysoftware.CalculatorBean”/>
<jsp:setProperty name="theBean" property=”memory” value=”169”/>
The value of memory is <jsp:getProperty name="theBean"
property=”memory’7>
При вызове этой страницы в web-браузере вы получите результат, показан-
ный на рис. 10.3.
328
Глава 10
Генерируемый сервлет
Элемент действия jspruseBean заставляет контейнер JSP создать код Java,
который загружает класс Bean. Рассмотрим генерируемый сервлет.
Вновь обратимся к странице JSP, которая использует Bean листинга 10.1:
<jsp:useBean id="theBean" scope="page"
class="com.brainysoftware.CalculatorBean"/^
<%
int i = 4;
int j = theBean.doublelt(i);
out.println("2*4=” + j);
%>
Если открыть файл класса созданного сервлета, то можно увидеть, что в
метод JspService добавлен код, который транслирует элемент действия
jsp:useBean.
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String _value = null;
try {
if (_jspx_inited == false) {
synchronized (this) {
if (_jspx_inited == false) {
jspx_init();
_jspx_inited = true;
}
}
}
JspxFactory = JspFactory. getDefaultFactoryO;
response.setContentType("text/html;charset=IS0-8859-1");
pageContext = _jspxFactory.getPageContext(
this, request, response, "", true, 8192, true)
Разработка JSP Bean
333
The value of memory is <jsp:getProperty name=”theBean"
property="memory'7>
Настройка значения свойства в запросе
Можно использовать более краткий способ установки значения свойства с
помощью элемента действия jsp:setProperty, если значение поступает в пара-
метрах объекта Request. Как указывалось в предыдущем разделе, существуют
такие формы синтаксиса:
<jsp:setProperty name=’’Bean Name” property=”PropertyName”
param="parameterName”/>
<jsp:setProperty name="Bean
Name” property=”PropertyName"/>
<jsp:setProperty name=”Bean Name” property=”*"/>
Первая форма позволяет присвоить свойству значение параметра объекта
Request. Атрибуту param элемента действия jspisetProperty должно быть при-
своено имя параметра объекта Request. Следующий пример демонстрирует
использование этой формы. Пример состоит из двух страниц. Первая страни-
ца — HTML с формой, содержащей текстовое поле, куда пользователь может
ввести число. Код этой страницы показан в листинге 10.7.
Листинг 10.7 Страница, где пользователь может ввести число
<HTML>
<HEAD>
<TITLE>Passing a value</TITLE>
</HEAD>
<BODY>
<CENTER>
Please type in a number in the box
<BR>
<FORM METHOD=POST ACTION=SimplePage.jsp>
<INPUT TYPE=TEXT NAME=memory>
<INPUT TYPE=SUBMIT>
</FORM>
</CENTER>
</BODY>
</HTML>
Отметим, что именем элемента TEXT является memory, и форма передает-
ся на страницу JSP с именем SimplePage.jsp, которая представлена в листинге
10.8.
330
Глава 10
Наиболее важной строкой кода в созданном сервлете является строка, ко-
торая создает экземпляр Bean и преобразует его в тип класса Bean:
theBean = (com.brainysoftware.CalculatorBean)
java.beans.Beans.instantiate(this.getClass().getClassLoader(),
"com.brainysoftware.CalculatorBean");
Можно видеть, как элемент действия jsp:useBean транслируется в метод
создания экземпляра (instantiate) из java.beans.Beans.
Отметим также, что генерируемый сервлет будет слегка отличаться, если
использовать для Bean другую область действия. Методам getAttribute и
setAttribute класса PageContext будет передаваться область действия соглас-
но области действия, применяемой для Bean. Например, если Bean имеет
область действия application, то вместо PAGE_SCOPE будет задаваться
APPLICATION_SCOPE. Если Bean имеет область действия session, будет ис-
пользоваться SESSION_SCOPE.
Доступ к свойствам с помощью
jsp:getProperty и jsp: set Property
Для доступа к свойству Bean можно использовать элементы действия
jsp:getProperty и jspisetProperty. Элемент jsp.get Property служит для получения
значения внутренней переменной, a Bean должен предоставить get-метод.
Элемент jspigetProperty имеет следующий синтаксис:
<jsp:getProperty name="Bean Name” property=”propertyName” />
Атрибуту name должно быть присвоено имя экземпляра Bean, из которого
будет получено значение свойства. Атрибуту property должно быть присвоено
имя свойства.
Элемент jsp.'getProperty возвращает значение свойства, преобразованное в
String. Возвращаемое значение автоматически передается в метод out.print для
вывода на текущей странице JSP.
Элемент действия jsp:setProperty используется для задания значения свой-
ства. Его синтаксис имеет четыре формы:
<jsp:setProperty name=”Bean Name” property=”PropertyName” value=”value"/>
<jsp:setProperty name=”Bean Name" property="PropertyName"/>
<jsp:setProperty name="Bean Name” property=”PropertyName"
param=”parameterName”/>
<jsp:setProperty name="Bean Name" property="*”/>
Разработка JSP Bean
335
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String .value = null;
try {
com.brainysoftware.CalculatorBean theBean = null;
synchronized (pageContext) {
theBean = (com.brainysoftware.CalculatorBean)
pageContext.getAttribute("theBean", PageContext.PAGE_SCOPE);
if (theBean == null) {
_jspx_specialtheBeah = true;
try {
theBean = (com.brainysoftware.CalculatorBean)
java.beans.Beans.instantiate(this.getClass().getClassLoader(),
"com.brainysoftware.CalculatorBean");
}
catch (Exception exc) {
throw new ServletException (”' Cannot create bean of class
+"com.brainysoftware.CalculatorBean", exc);
}
pageContext.setAttribute("theBean”, theBean, !
PageContext.PAGE_SCOPE);
}
}
if(_jspx_specialtheBean == true) {
// здесь идет код инициализации
out.write("\r\nlnintializing the Bean\r\n”);
}
}
catch (Throwable t) {
}
finally {
332
Глава 10
Рис. 10.3. Вызов метода доступа в Bean
Результат jsprgetProperty выводится на текущей странице JSP.
Отметим, что можно также обратиться к методам доступа обычным образом
(см. листинг 10.5).
Л истинг 10.5 Вызов методов доступа как обычных методов
<jsp:useBean id=”theBean” class=”com.brainysoftware.CalculatorBean"/>
<%
theBean.setMemory(987);
%>
The value of memory is <%= theBean.getMemoryO
%>
Во многих случаях используют выражение в качестве значения атрибута
value. Например, код листинга 10.6 передает выражение JSP в атрибут value в
элементе действия jspisetProperty.
Листинг 10.6 Передача выражения JSP в атрибут value
<jsp:useBean id=”theBean" class=”com.brainysoftware.CalculatorBean"/>
<%
int i = 2;
%>
<jsp:setProperty name="theBean" property="memory" value="<%= 100 * i
%>"/>
Разработка JSP Bean
337
return StringUtil.encodeHtmlTag(sql);
}
public void setSql(String. sql) {
if (sql!=null)
this.sql = sql;
}
public void setUserName(String userName) {
if (userName!=null)
this.userName = userName;
}
public String getUserName() {
return StringUtil.encodeHtmlTag(userName);
}
public void setPassword(String password) {
if (password!=null)
this.password = password;
}
public String getPasswordO {
return StringUtil.encodeHtmlTag(password);
}
public void setConnectionUrl(String url) {
connectionUrl = url;
}
public String getResultO {
if (sql==null || sql.equals(”"))
return
StringBuffer result = new StringBuffer(1024);
try {
Connection con = DriverManager. getConnection(connectionUrl,
userName, password);
Statement s = con.createStatementO;
if (sql.toUpperCase().startsWith("SELECT")) {
result.append("<TABLE BORDER=1>");
ResultSet rs = s.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaDataO;
// Запись табличных заголовков
int columnCount = rsmd.getColumnCountO;
result.append("<TR>”);
for (int i=1; i<=columnCount; i++) {
result. append("<TD><B>" + rsmd. getColumnName(i) + "</Bx/TD>\n");
}
result.append("</TR>”);
while (rs.next()) {
result.append("<TR>");
for (int i=1; i<=columnCount; i++) {
result.append("<TD>" + StringUtil.encodeHtmlTag(rs.getString(i)) + "</TD>" );
334
Глава 10
Листинг 10.8 Страница SimplePage.jsp
<jsp:useBean id="theBean" class="com. brainysoftware.CalculatorBean’7>
<jsp:setProperty name=”theBean” property='’memory" param=”memory”/>
The value of memory is <jsp:getProperty name="theBean”
property=”memory”/>
Если имя параметра совпадает с именем свойства Bean, можно опустить
атрибут param:
<jsp:setProperty name="theBean” property=’’memory’7>
Это третья форма синтаксиса jsp:setProperty.
Последняя форма позволяет присваивать значения параметров объекта
Request нескольким свойствам Bean, если имена свойств совпадают с именами
параметров. Вторую строку листинга 10.8 можно заменить следующей:
<jsp:setProperty name="theBean” property=”*'7>
Инициализация кода JavaBeans
Элемент действия jsp:useBean позволяет написать код, который будет выпол-
няться после инициализации Bean. Если областью действия Bean является
страница, код инициализации выполняется каждый раз, когда запрашивается
страница JSP.
Код инициализации может быть совсем простым:
<jsp:useBean id=”theBean’' scope="page"
class=”com.brainysoftware.CalculatorBean”>
Initializing the Bean
</jsp:useBean>
Помните, что любые шаблонные данные будут транслироваться в аргумент
метода out.print. Код инициализации будет добавляться в блок кода, который
создает экземпляр Bean. Рассмотрим метод JspService сервлета, сгенериро-
ванного из предыдущей страницы JSP. Метод приводится в сокращенном виде:
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
Разработка JSP Bean
339
Рис. 10.6. Страница SQLTool.jsp с полученными результатами
int i = s.executeUpdate(sql);
result.append("Record(s) affected: " + i);
}
s.closeO;
con.close();
result.append("</TABLE>");
}
catch (SQLException e) {
result.append("<B>Error</B>");
result.append("<BR>");
result.append(e.toSt ring());
}
catch (Exception e) {
result.append("<B>Error</B>");
result.append("<BR>");
result. append(e. toStringO);
}
return result.toStringO;
}
}
SQLToolBean использует четыре внутренние переменные: connectionUrl,
userName, password и sql. Первые три служат для получения объекта Connection
(соединения) с сервером базы данных, a sql является оператором SQL, который
будет выполняться сервером. userName, password и sql имеют свои собственные
методы get и set, в то время как connectionUrl имеет только set-метод.
336
Глава 10
Во время инициализации можно использовать элемент действия
jsp :set Property для установки значения свойства Bean, как показывает код лис-
тинга 10.9.
Листинг 10.9 Использование jspisetProperty в коде инициализации Bean
<jsp:useBean id=”theBean" class=,'com.brainysoftware.CalculatorBean*'>
<jsp:setProperty name=”theBean” property=”*'7>
</jsp:useBean>
Пример SQLToolBean
Рассмотрим пример Bean, который может использоваться в качестве утилиты
для выполнения оператора SQL на сервере базы данных. Этот код является
модификацией SQLToolServlet, представленного в главе 4. Вместо сервлета
теперь применяется JavaBean.
Пример включает в себя класс Bean и две страницы JSP. Первая страница
JSP, Login.jsp, служит для получения имени пользователя и пароля с целью
аутентификации пользователя базы данных. Она состоит из формы HTML с
двумя элементами TEXT: один для имени пользователя и один для пароля.
После отправки формы имя пользователя и пароль передаются как свойства
в Bean на второй странице JSP, SQLTool.jsp. Значения имени пользователя и па-
роля сохраняются также в двух скрытых полях в форме на странице SQLTool.jsp.
Когда форма отправляется, значения имени пользователя и пароля передаются
на сервер. Таким образом, пользователю нужно будет регистрироваться только
один раз.
Это небольшое JSP-приложение показано на рис. 10.4 — 10.6.
Все бизнес-правила содержатся в JavaBean, показанном в листинге 10.10.
Листинг 10.10 SQLToolBean
package com.brainysoftware.web;
import java.sql.*;
import com. brainysoftware. java.Stringlltil;
public class SQLToolBean {
private String sql =
private String userName = ””;
private String password =
private String connectionllrl;
public String getSqlO {
Разработка JSP Bean
341
encodedTag.append("<");
else if (c==’>’)
encodedTag.append(">");
else if (c=='&')
encodedT ag.append("&");
else if (c=="”)
encodedTag.append(""");
else if (c==‘ ')
encodedTag.append(” ");
else
encodedTag.append(c);
}
return encodedTag.toString();
}
Как говорилось в главе 4, необходимо загрузить драйвер JDBC, который мо-
жет предоставить соединение с базой данных. Драйвер JDBC нужно загрузить
только один раз, и соответствующий код задается как код инициализации Bean.
Страница регистрации для этого приложения показана в листинге 10.12.
Листинг 10.12 Страница Login.jsp
<HTML>
<HEAD>
<TITLE>Login Page</TITLE>
</HEAD>
<BODV>
<CENTER>
<FORM METHOD=POST ACTION=SQLTool.jsp>
<TABLE>
<TR>
<TD>User Name:</TD>
<TD><INPUT TYPE=TEXT NAME=userNameX/TD>
</TR>
<TR>
<TD>Password:</TD>
<TDXINPUT TYPE=PASSWORD NAME=password></TD>
</TR>
<TR>
CTDXINPUT TYPE=RESETx/TD>
CTDXINPUT TYPE=SUBMIT VALUE="Login"X/TD>
</TR>
</TABLE>
</FORM>
C/CENTER>
</BODY>
</HTML>
338
Глава 10
Рис. 10.4. Страница Login.jsp
Рис. 10.5. Страница SQLTool.jsp
Листинг 10.10 Продолжение
}
result.append("</TR>");
}
rs.closeO;
result.append("</TABLE>");
}
else {
Разработка JSP Bean
343
<BR>
<HR>
<BR>
<%^ theBean.getResultO %>
</B0DY>
4 he (tew ft С)
</HTML>
Прежде всего этот код инициализирует SQLToolBean с помощью элемента
действия jspiuseBean. В качестве кода инициализации Bean применяется код,
который загружает драйвер JDBC:
<jsp:useBean id=’,theBean'' class="com.brainysoftware.web.SQLToolBean">
<%
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver”);
}
catch (Exception e) {
out. println(e. toStringO);
}
%>
</jsp:useBean>
Затем задаются свойства Bean:
<jsp:setProperty name="theBean" property="userName”/>
<jsp:setProperty name=”theBean" property="password”/>
<jsp:setProperty name="theBean" property="connectionUrr’
value="j dbc:odbc:JavaWeb"/>
<jsp:setProperty name="theBean" property="sqr7>
Отметим, что за исключением свойства connectionUrl все значения свойств
берутся из объекта запроса.
Эти значения записываются как скрытые значения и извлекаются с помо-
щью элементов действия jsp:getProperty. Ниже представлена форма на странице
SQLTool.jsp:
<FORM METHOD=POST>
<INPUT TYPE=HIDDEN NAME=userName VALUE="<jsp:getProperty name="theBean"
property="userName"/>">
<INPUT TYPE=HIDDEN NAME=password VALUE="<jsp:getProperty name="theBean"
property="password'7>">
CTEXTAREA NAME=sql C0LS=80 R0WS=8>
<jsp:getProperty name="theBean" property=’’sql’7>
</TEXTAREA>
340
Глава 10
public String getSqlO {
return StringUtil.encodeHtmlTag(sql);
}
public void setSql(String sql) {
if (sql!=null)
this.sql = sql;
>
public void setl)serName(String userName) {
if(userName!=null)
this.userName = userName;
}
public String getUserNameO {
return StringUtil.encodingHtmlTag(userName);
}
public void setPassword(String password) {
if(password!=null)
this.password = password;
}
public String getPasswordO {
return StringUtil.encodeHtmlTag(password);
}
public void setConnectionUrl(String url) {
connectionUrl = url;
}
Единственным методом Bean является getResult. Для SQL-оператора select
этот метод возвращает значение String, содержащее таблицу HTML. Для дру-
гих типов операторов SQL метод возвращает число записей, на которые по-
действовал оператор SQL.
В SQLToolBean несколько раз используется метод encodeHtmlTag класса
com.brainysoftware.java.StringUtil. Этот метод кодирует строку String, чтобы
символы, имеющие специальное значение в HTML, преобразовывались в свои
эквиваленты. Этот класс включен в jar-файл, содержащийся на сайте www.lory-
press.ru. Можно скопировать и вставить метод encodeHtmlTag, чтобы не зави-
сеть от jar-файла. Этот метод представлен влистинге 10.11.
Л истинг 10.11 Метод encodeHtmlTag
public static String encodeHtmlTag(String tag) {
if (tag==null)
return null;
int length = tag.length();
StringBuffer encodedTag = new StringBuffer(2 * length);
for (int i=0; i<length; i++) {.
char c = tag.charAt(i);
Применение
пол ьзовател ьских
тегов JSP
помощью JavaBeans можно отделить представление страницы JSP от
реализации бизнес-правил (код Java). Однако для доступа к Bean применяют-
ся только три элемента действия — jsp:useBean, jsp:get Property, jsp:setProperty.
Поэтому в некоторых ситуациях приходится использовать код на странице
JSP. Другими словами, JavaBeans не всегда обеспечивают полное разделение
представления и реализации бизнес-правил.
Возьмем в качестве примера листинг 10.13 из предыдущей главы. Хотя стра-
ница JSP состоит в основном из тегов, потребовалось написать код Java в ка-
честве кода инициализации Bean. Повторим этот фрагмент:
<jsp:useBean id="theBean" class="com.brainysoftware.web.SQLToolBean">
<%
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch (Exception e) {
out. println(e. toStringO);
}
%>
</jsp:useBean>
В других частях листинга 10.13 все представляется тегом, за исключением
случая, когда необходимо вызвать метод getResult из Bean — здесь использует-
ся выражение JSP:
342
Глава 10
Это по сути является статическим HTML, который можно безопасно
преобразовать в страницу HTML, чтобы избежать какой-либо обработки
контейнером JSP.
Когда отправляется форма на странице HTML, запрос передается на стра-
ницу SQLTool.jsp. Эта страница представлена в листинге 10.13.
Л истинг 10.13 Страница SQLTool.jsp
<jsp:useBean id=”theBean" class=”com.brainysoftware.web.SQLToolBean”>
<%
try {
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver”);
}
catch (Exception e) {
out. println (e. toStringO);
}
%>
</jsp:useBean>
<jsp:setProperty name="theBean” property=”userName'7>
<jsp:setProperty name=”theBean” property=”password"/>
<jsp:setProperty name=”theBean" property="connectionUrl"
value="jdbc:odbc:JavaWeb”/>
<jsp:setProperty name=”theBean" property="sqr7>
<HTML>
<HEAD>
<TITLE>SQL Tool</TITLE>
</HEAD>
<BODY>
<BR><H2>SQL Tool</H2>
<BR>Please type your SQL statement in the following box.
<BR>
<BRXF0RM METHOD=POST>
<INPUT TYPE=HIDDEN NAME=userName VALUE=”<jsp:getProperty name=”theBean”
property=”userName'7>”>
<INPUT TYPE=HIDDEN NAME=password VALUE=”<jsp:getProperty name=”theBean”
property=”password"/>">
<TEXTAREA NAME=sql C0LS=80 R0WS=8>
<jsp:getProperty name^’theBean’' property=’’sql’7>
</TEXTAREA>
<BR>
<INPUT TYPE=SUBMIT>
</FORM>
Применение пользовательских тегов JSP
347
Листинг 11.1 ФайлТЬВ
<?xml version="1.О” encoding=”IS0-8859-1" ?>
<!DOCTYPE taglib
PUBLIC ’’-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
”http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<shortname></shortname>
<tag>
<name>myTag</name>
<tagclass>com.brainysoftware.MyCustomTag</tagclass>
</tag>
</taglib>
2. Напишем, скомпилируем и развернем класс Java с именем
MyCustomTag.java, представленный в листинге 11.2. Убедитесь в том, что
файл .class расположен в каталоге brainysoftware, который вложен в ка-
талог WEB-INF/classes/com.
tomcat4
'^1 common
> conf
Ц jasper
src
[+1 в examples
HI manager
-Эту Арр
ЕВ-INF
work
Рис. 11.1. Структура каталогов приложения
344
Глава 10
<BR>
<INPUT TYPE=SUBMIT>
</FORM>
При просмотре исходного кода получающейся страницы HTML можно
увидеть, что имя пользователя и пароль сохранены для повторной передачи
на сервер, которая будет выполняться при отправке формы пользователем:
<HTML>
<НЕА0>
<TITLE>SQL Tool</TITLE>
</HEAD>
<B0DY>
<BR><H2>SQL Tool</H2>
<BR>Please type your SQL statement in the following box.
<BR>
<BRXF0RM METHOD=POST>
<INPUT TYPE=HIDDEN NAME=userName VALUE="sjones">
<INPUT TYPE=HIDDEN NAME=password VALUE="dingoblue">
<TEXTAREA NAME=sql C0LS=80 R0WS=8>
</TEXTAREA>
<BR>
<INPUT TYPE=SUBMIT>
</FORM>
<BR>
<HR>
<BR>
</BODY>
</HTML>
Заключение
В этой главе рассмотрена ориентированная на компоненты архитектура страни-
цы JSP — предпочтительный подход, по сравнению со страницами JSP, которые
смешивают шаблонные данные и элементы. Преимуществами являются
улучшение читаемости, разделение реализации внешнего представления и биз-
нес-правил и повторное использование кода.
Вы узнали, как написать Bean и выполнить его с помощью Tomcat. Были
представлены различные области действия Bean и показано, как задать и по-
лучить значение свойства Bean с помощью элементов действия jsprsetProperty
и jsp :get Property.
Применение пользовательских тегов JSP
349
5. Перезапустим Tomcat.
Откройте web-браузер и введите http://localhost:8080/myJSPApp/
SimplePage.jsp в поле Address. Должно появиться что-то похожее на рис. 11.2.
Рис. 11.2. Простая страница JSP
На рис. 11.3. показано, как работают все части.
Рис. 11.3. Отношения между дескриптором развертывания,
страницей JSP и файлом TLD
Когда пользователь запрашивает страницу JSP, контейнер JSP обнаружи-
вает тег <taglib>. Контейнер JSP знает, что это пользовательский тег. Затем он:
346
Глава 11
<%= theBean.getResultO %>
Кроме того, создание JavaBeans предполагает повторное использование, т. е.
применять Bean для непосредственного вывода тегов HTML не рекомендует-
ся. Вывод тегов HTML из Bean делает Bean применимым только на опреде-
ленной странице, хотя существуют исключения.
Учитывая несовершенство JavaBeans как решения для разделения представле-
ния и реализации бизнес правил, JSP 1.1 определяет новое средство: пользова-
тельские теги, которые могут служить для выполнения действий пользователя.
Пользовательские теги дают некоторые преимущества в сравнении с
JavaBeans. Среди всего прочего, пользовательские теги имеют доступ ко всем
объектам, доступным страницам JSP, и пользовательские теги можно изме-
нять с помощью атрибутов. Однако пользовательские теги не предназначены
для полной замены JavaBeans. JavaBeans также имеют свои достоинства. По-
этому иногда удобнее применять Bean в связи с возможностью их повторного
использования, а иногда лучше задействовать пользовательские теги. В некото-
рых обстоятельствах выбор JavaBeans или пользовательских тегов не является
абсолютно очевидным, и приходится принимать решение на основе личного
опыта.
В этой главе исследуются пользовательские теги. Мы начнем с создания
страницы JSP, которая применяет пользовательские теги, развернем неболь-
шое приложение с помощью Tomcat и выполним его в web-браузере. Затем
рассмотрим детали базового API — классы и интерфейсы пакета
javax.servlet.jsp.tagext, и на их основе создадим примеры.
Создание пользовательского тега
В качестве примера быстро создадим небольшое приложение, развернем его в
Tomcat и вызовем страницу в web-браузере. Для этого примера мы построим
компонент Java, который посылает браузеру строку «Hello from the custom tag».
В этом разделе представлен пошаговый подход к созданию приложения JSP,
которое применяет пользовательские теги.
Прежде чем начать разработку приложения, взгляните на рис. 11.1, кото-
рый показывает структуру каталогов приложения JSP с именем myJSPApp.
Необходимо создать аналогичную структуру каталогов, если это еще не было
сделано в предыдущих главах.
Теперь выполним пять простых шагов.
1. Создадим файл TLD с именем taglib.tld, как показано в листинге 11.1, и
сохраним его в каталоге WEB-INF.
Применение пользовательских тегов JSP
351
}
_jspxFactory = JspFactory. getDefaultFactoryO;
response.setContentType("text/html;charset=IS0-8859-1");
pageContext = _jspxFactory.getPageContext(this, requset,
response, "", true, 8192, true);
application = pageContext.getServletContext();
config = pageContext. getServletConfigO;
session = pageContext.getSession();
out = pageContext.getOut();
out.write("\r\n");
/* — easy:myTa.g -- */
com.brainysoftware.MyCustomTag _jspx_th_easy_myTag_O =
new com.brainysoftware.MyCustomTag();
_jspx_th_easy_myTag_0.setPageContext(pageContext);
_jspx_th_easy_myTag_6.setParent(null);
try {
int _jspx_eval_easy_myTag_O = _jspx_th_easy_myTag_0.doStartTag();
if (_jspx_eval_easy_myTag_O == BodyTag.EVAL_BODY_BUFFERED)
throw new JspTagException("Since tag handler class" +
" com.brainysoftware.MyCustomTag does not implement BodyTag," +
" it can't return BodyTag.EVAL_BODY_TAG");
if (_jspx_eval_easy_myTag_O != Tag.SKIP_B0DY) {
do {
}
while (_jspx_th_easy_myTag_0.doAfterBody() ==
BodyTag.EVAL_BODY_AGAIN) ;
}
if (_jspx_th_easy_myTag_0.doEndTag() == Tag.SKIP_PAGE)
return;
}
finally {
_jspx_th_easy_myTag_O.release();
}
out.write("\r\n");
}
catch (Throwable t) {
if (out != null && out.getBufferSize() != 0)
out.clearBufferO;
if (pageContext != null)
pageContext.handlePageException(t);
}
finally {
if (_jspxFactory != null)
JspxFactо ry.releasePageContext(pageContext);
}
}
}
348
Глава 11
Листинг 11.2 MyCustomTag.java
package com. brainysoftware;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag extends TagSupport {
public int doEndTagO throws JspException {
JspWriter out = pageContext.getOut();
try {
out.println("Hello from the custom tag.”);
}
catch (Exception e) {
}
return super.doEndTagO;
}
}
3. Создадим файл JSP с кодом, приведенным в листинге 11.3. Назовем его
SimplePage.jsp и сохраним в каталоге myJSPApp.
Л исти н г 11.3 Страница SimplePage.jsp
<%@ taglib uri=”/myTLD" prefix=”easy"%>
<easy:myTag/>
4. Отредактируем файл дескриптора развертывания (web.xml). Для приме-
нения пользовательских тегов необходимо определить элемент <taglib>
в файле web.xml. Элемент <taglib> должен появиться после <servlet> и
<servlet-mapping>, если таковые имеются. Пример дескриптора развер-
тывания представлен в листинге 11.4.
Листинг 11.4 Дескриптор развертывания для этого приложения
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>template</display-name>
<taglib>
<taglib-uri>/myTLD</taglib-uri>
<taglib-location>/WEB-INF/taglib.tld</taglib-location>
</taglib>
</web-app>
Применение пользовательских тегов JSP
353
• shortname
• info
• uri
• tag
Из этих шести элементов только элемент tag может иметь атрибуты. В фай-
ле TLD должны присутствовать tlibversion, shortname и tag.
Элемент tlibversion определяет номер версии библиотеки тегов в следую-
щем формате:
([0-9])* ("."[0-9])? (".” [0-9])? ("." [0-9])?
Звездочка говорит о том, что значения в скобках могут повторяться нуль
или большее количество раз, а знак вопроса указывает на то, что значение в
скобках является необязательным. К допустимым значениям элемента
tlibversion относятся 1.1.1.1, 2, 2.3, 3.3.6 и т. д.
Элемент jspversion определяет версию JSP. Формат такой же, как и у эле-
мента tlibversion:
([0-9])* ("."[0-9])? ("." [0-9])? ("." [0-9])?
Элемент shortname определяет краткое имя библиотеки тегов. Значение
этого элемента должно начинаться с буквы и не может содержать пробелов.
Элемент info содержит информацию, используемую в целях документиро-
вания. Значением по умолчанию является пустая строка.
Элемент uri определяет соединение с дополнительным источником доку-
ментации для библиотеки тегов.
Элемент tag является самым важным в библиотеке тегов. Можно опреде-
лить несколько элементов tag в одном файле TLD. Поэтому достаточно иметь
только один файл TLD для каждого приложения JSP.
Приведем пример файла TLD:
<?xml version="1.0" encoding="IS0-8859-1" ?>
<! DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0.123.123.234</tlibversion>
<jspversion>1.2</jspversion>
<shortname></shortname>
<info>Example tags</info>
<urix/uri>
<tag>
350
Глава 11
• Проверяет дескриптор развертывания (web.xml), чтобы найти местополо-
жение taglib, где URI задано как «/myTLD». Дескриптор развертывания
возвращает путьдоступа к файлу TLD. Контейнер}8Рзапоминаетэтот путь.
• Обрабатывает следующую строку и встречает пользовательский тег myTag
с префиксом «easy». Зная имя и расположение файла TLD, контейнер JSP
считывает файл TLD и получает полностью квалифицированное имя клас-
са Java для тега myTag. Он считывает: com.brainysoftware.MyCustomTag.
Контейнер JSP может загрузить класс для тега и начать его обработку.
Генерируемый сервлет не такой сложный, как кажется:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import org.apache.jasper.runtime.*;
public class SimplePage.jsp extends HttpJspBase {
static {
}
public SimplePage_jsp( ) {
}
private static boolean _jspx_inited = false;
public final void _jspx_init() throws org.apache.jasper.JasperException
{
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.lOException, ServletException {
JspFactory .jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
String .value = null;
try {
if (.jspx.inited == false) {
synchronized (this) {
if (.jspx.inited == false) {
_jspx_init();
.jspx.inited = true;
}
}
Применение пользовательских тегов JSP
355
Подэлемент name — это идентификатор атрибута. Он указывает, является
ли атрибут обязательным, и может принимать значение true или false, где false
используется в качестве значения по умолчанию.
Подэлемент rtexprvalue указывает, можно ли определить значение атрибута
во время запроса. Если значение этого подэлемента равно true, то во время
запроса можно присвоить значение, например выражение JSP.
Синтаксис пользовательского тега
Чтобы применить пользовательский тег на странице JSP, необходимо прежде
всего добавить на страницу директиву taglib. Директива taglib имеет следую-
щий синтаксис:
<%@ taglib uri=’’tagLibraryURr prefix="tagPrefix" %>
Атрибут uri определяет абсолютный или относительный URI, который
уникальным образом идентифицирует дескриптор библиотеки тегов, ассо-
циированный с указанным префиксом.
Атрибут prefix определяет строку, которая станет префиксом, служащим для
распознавания пользовательского действия.
Для пользовательского тега, который не имеет тела, применяется формат:
<prefix:tagName/>
Следующий формат применяется для пользовательского тега, который
имеет тело:
<prefix:tagName>body</prefix:tagName>
Можно передать атрибуты обработчику тега, определив атрибуты в пользо-
вательском теге с помощью следующего формата:
attributeName=’’attributevalue"
В следующем примере определен пользовательский тег с префиксом m и
с именем myTag. Этот тег имеет два атрибута: number со значением 12 и power
со значением 13.
<m:myTag number="12" power=”13"/>
Значение атрибута должно быть заключено в кавычки.
352
Глава 11
Выяснив, как действуют пользовательские теги, мы можем подробнее рас-
смотреть процесс создания компонентов с помощью пользовательских тегов.
Начнем с простейшей части — дескриптора развертывания.
Роль дескриптора развертывания
В предыдущем примере было показано, как контейнер JSP узнает имя и мес-
тоположение файла TLD, просматривая дескриптор развертывания.
Это является обычной практикой. Но можно исключить из процесса деск-
риптор развертывания, записав имя и расположение файла TLD прямо на стра-
нице JSP. Поэтому код листинга 11.3 можно заменить кодом листинга 11.5.
Листинг 11.5 Альтернативная страница JSP для приложения
<%@ taglib uri="WEB-INF/taglib.tld" prefix="easy" %>
<easy:myTag/>
Однако прямая ссылка на файл TLD лишена гибкости перемещения и сме-
ны имени файла TLD. При изменении имени файла придется редактировать
все страницы JSP, которые его используют.
Поэтому предпочтительна косвенная ссылка на файл TLD с применением
дескриптора развертывания.
С Примечание^ Отметим, что во всех примерах этой главы атрибуту URI тега
taglib присваивается /myTLD. Предполагается, что файл
web.xml будет отредактирован соответствующим образом.
Дескриптор библиотеки тегов
Файл дескриптора библиотеки тегов (TLD) является документом XML, который
определяет библиотеку тегов и ее теги. Первые строки файла TLD представ-
ляют собой стандартный заголовок ХМL следующего вида:
<?xml version="1.0” encoding="IS0-8859-1" ?>
<! DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
Файл TLD содержит корневой элемент <taglib>. Этот элемент может иметь
следующие подэлементы:
• tlibversion
• jspversion
Применение пользовательских тегов JSP
357
}
public void release() {
}
public Tag getParentO {
return null;
}
public int doStartTagO {
return EVAL_BODV_INCLUDE;
}
public int doEndTagO {
return EVAL_PAGE;
}
}
Отметим, что методы doStartTag и doEndTag возвращают integer. Возвраща-
емым значением является один из четырех static final integer, определенных в
интерфейсе Tag: SKIP_BODY, EVAL_BODY_INCLUDE, SKIP_PAGE и
EVAL_PAGE.
Tag.SKIP_BODY и Tag.EVAL_BODY_INCLUDE являются допустимыми
значениями, возвращаемыми методом doStartTag, a Tag.SKlP_PAGE и
Tag.EVAL PAGE возвращаются методом doEndTag.
Жизненный цикл обработчика тегов
Жизненный цикл обработчика тегов, реализующего интерфейс Tag, контроли-
руется контейнером JSP, который вызывает методы втакой последовательности:
1. Контейнер JSP получает экземпляр обработчика тегов из пула или со-
здает новый. Затем он вызывает setPageContext, передавая объект
PageContext, представляющий страницу JSP, где находится пользователь-
ский тег. Если необходимо обратиться к странице JSP, нужно присвоить
этот объект PageContext объектной ссылке, областью действия которой
является класс. Метод setPageContext имеет сигнатуру:
public void setPageContext(PageContext pageContext)
2. Контейнер JSP вызывает метод setParent. Этот метод передает объект тега,
который представляет ближайший тег, охватывающий обработчик теку-
щего тега. Если охватывающий тег не существует, передается объектная
ссылка null.
Сигнатура метода setParent имеет вид:
public void setParent(Tag parent)
354
Глава 11
<name>myTag</name>
<tagclass>com.brainysoftware.MyCustomTag</tagclass>
<attribute>
<name>number</name>
< requi red>t rue</requi red>
</attribute>
<attribute>
<name>powe r</name>
< requi red>t rue</requi red>
</attribute>
</tag>
</taglib>
Элемент tag
Элемент tag определяет пользовательский тег в библиотеке. Этот элемент
может иметь шесть подэлементов:
• name
• tagclass
• teiclass
• bodycontent
• info
• attribute
Из этих шести подэлементов обязательными являются name и tagclass.
Элемент name определяет полностью квалифицированное имя класса Java,
который управляет обработкой этого тега.
Элемент teiclass определяет для тега вспомогательный класс, если таковой
имеется, tei является сокращением TagExtralnfo, представляющего имя класса
в пакете javax.servlet.jsp.tagext (см. ниже).
Элемент bodycontent определяет тип содержимого тела тега при наличии
такового. Содержимое тела — это то, что записывается между открывающим и
закрывающим тегами. Этот элемент может иметь одно из следующих значе-
ний: empty, JSP или tagdependent. Значение empty указывает, что не существует
содержимого тела, поддерживаемого тегом. Задание «JSP» в качестве значения
bodycontent говорит о том, что значения содержимого тела являются одним
или несколькими элементами JSP. Последнее значение, tagdependent, указы-
вает, что тело тега должно интерпретироваться тегом.
Элемент info элемента tag содержит информационное описание.
Элемент attribute определяет нуль или несколько атрибутов. Этот элемент
может иметь три подэлемента: name, required и rtextprvalue. Только name явля-
ется обязательным подэлементом attribute.
Применение пользовательских тегов JSP
359
пишет, посылается в web-браузер. Обработчик тегов называется MyCustomTag
и приводится в листинге 11.7.
Листинг 11.7 Обработчик тегов подстановки содержимого
package com.brainysoftware;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class MyCustomTag implements Tag {
PageContext pageContext;
public void setParent(Tag t) {
}
public void setPageContext(PageContext p) {
pageContext = p;
}
public void release() {
}
public Tag getParentO {
return null;
}
public int doStartTagO {
try {
JspWriter out = pageContext.getOut();
out.println("Hello from the tag handler.”);
}
catch(Exception e) {
}
return EVAL_BODY_INCLUDE;
}
public int doEndTagO throws JspException {
return EVAL_PAGE;
}
}
Для тестирования обработчика тегов потребуются страница JSP (см. лис-
тинг 11.8) и файл TLD (см. листинг 11.9).
356
Глава 11
API пользовательских тегов JSP
Обработчик тегов является классом Java, который связан с пользовательским
тегом, и вызывается всякий раз, когда контейнер JSP встречает пользовательс-
кий тег. Обработчик тегов называется так потому, что он управляет обработкой
тега. Чтобы быть функциональным, обработчик тегов должен реализовать ин-
терфейс из пакета javax.servlet.jsp.tagext или расширить один из классов того же
пакета.
В JSP 1.2 пакет javax.servlet.jsp.tagext имеет четыре интерфейса и двенад-
цать классов, на четыре члена больше, чем тот же пакет в спецификации JSP
1.1. Некоторые из членов пакета javax.servlet.jsp.tagext рассматриваются ниже.
Полное описание этого пакета дается в приложении Е.
Двумя наиболее важными интерфейсами являются Tag и BodyTag. Они
задают жизненный цикл обработчика тегов. Обработчик тегов должен реа-
лизовать интерфейс Tag или BodyTag прямо или косвенно.
Интерфейс Тад
Обработчики, которые реализуют интерфейс Tag, должны предоставить
реализации для всех методов интерфейса. Это методы:
• doStartTag
• doEndTag
• get Parent
• setParent
• set PageContext
• release
Можно написать обработчик тегов, реализовав интерфейс Tag и предоста-
вив пустые реализации всех его шести методов. Например, код листинга 11.6
является обработчиком тегов с именем BasicTagHandler, который ничего не
делает. А
Листинг 11.6 Простой обработчик тегов, который ничего не делает
package com.brainysoftware;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class BasicTagHandler implements Tag {
public void setParent(Tag t) {
}
public void setPageContext(PageContext p) {
Применение пользовательских тегов JSP
361
public void setParent(Tag t) {
}
public void setPageContext(PageContext p) {
pageContext = p;
public void releaseO {
}
public Tag getParentO {
return null;
}
public int doStartTagO {
try {
JspWriter out = pageContext.getOut();
out.println("Double of " + number + " is ” + (2 * number));
}
catch(Exception e) {
}
return EVAL_BODY_INCLUDE;
}
public int doEndTagO throws JspException {
return EVAL_PAGE;
}
}
Для проверки работы кода потребуются страница JSP (см. листинг 11.11) и
файл TLD (см. листинг 11.12).
Л исти н г 11.11 Страница JSP
<%@ taglib uri="/myTLD" prefix="easy"%>
<easy:myTag number="12’7>
Листинг 11.12 Файл TLD
<?xml version="1.0" encoding="IS0-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
358
Глава 11
3. Контейнер JSP устанавливает все атрибуты в пользовательском теге, если
они есть. Атрибуты обрабатываются подобно свойствам в JavaBean, а
именно с помощью методов get и set. Например, если пользовательский
тег имеет атрибут с именем temperature, get-метод называется
getTemperature, a set-метод — setTemperature. Контейнер JSP вызывает
все доступные set-методы для задания значений атрибутов.
4. Затем контейнер JSP вызывает doStartTag, сигнатура которого имеет вид:
public int doStartTagO throws javax.servlet. jsp.JspException
Метод doStartTag может вернуть значение Tag.SKIP BODY или
Tag.EVAL_BODY_INCLUDE. Если возвращается Tag.SKIP_BODY, то
контейнер не будет обрабатывать содержимое тела тега. Если метод
doStartTag возвращает Tag.EVAL_BODY_INCLUDE, содержимое тела,
при наличии такового, будет обрабатываться обычным образом.
5. Независимо от значения, возвращаемого методом doStartTag, контейнер
затем вызывает метод doEndTag. Этот метод имеет сигнатуру:
public int doEndTagO throws javax.servlet.jsp.JspException
Метод do EndTag возвращает Tag.S КI P_PAG E ил и Tag. EVAL_PAG E. Есл и
возвращается Tag.SKIP_PAGE, контейнер JSP не будет обрабатывать ос-
тавшуюся часть страницы JSP. Если возвращается Tag.EVAL_PAGE, кон-
тейнер JSP обработает остаток страницы JSP обычным образом.
6. Метод release является последним методом, который вызывается кон-
тейнером JSP. Этот метод имеет сигнатуру:
public void releaseO
Необходимо написать какой-либо код очистки в реализации этого метода.
Например, можно закрыть файл, который был открыт в другом методе,
или закрыть соединение с базой данных.
7. Контейнер JSP возвращает экземпляр обработчика тегов в пул для пос-
ледующего использования.
Обработчик тегов подстановки содержимого
В следующем примере создается обработчик тегов, который предоставляет
реализации для методов setPageContext и doStartTag. setPageContext присваи-
вает объект PageContext, передаваемый контейнером JSP, объектной ссылке.
doStartTag получает объект JspWriter с помощью метода getOut объекта
PageContext и записывает в него некоторую информацию. Так как обработчик
тегов записывает прямо в объект JspWriter текущей страницы JSP, все, что он
Применение пользовательских тегов JSP
363
import javax.servlet.jsp.tagext.*;
public class PowerTag implements IterationTag {
PageContext pageContext;
private int number;
private int power;
private int counter;
private int result = 1;
// задание основания для возведения в степень
public void setNumber(int number) {
this.number = number;
}
// задание показателя степени
public void setPower(int power) {
this.power = power;
}
public void setParent(Tag t) {
}
public void setPageContext(PageContext p) {
pageContext = p;
}
I
public void release() {
}
public Tag getParentO {
return null;
}
public int doStartTagO {
return EVAL_BODY-INCLUDE;
}
public int doAfterBodyO {
counter++;
result *= number;
if (counter==power)
return SKIP_BODY;
else
return EVAL_BODY_AGAIN;
}
public int doEndTagO throws JspException {
System.out.println("doEndTag”);
360
Глава 11
Листинг 11.8 Страница JSP
<%@ taglib uri='7myTLD" prefix=’’easy"%>
<easy:myTag/>
Л истинг 11.9 Файл TLD
<?xml version="1.О" encoding="IS0-8859-1" ?>
<!DOCTYPE taglib
PUBLIC ’’-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN”
’’http: //j ava. sun. com/j 2ee/dtds/web-j sptaglibrary_1_1. dtd”>
<taglib>
<tlibversion>1.0</tlibversion>
<shortnamex/shortname>
<tag>
<name>myTag</name>
<tagclass>com.brainysoftware.MyCustomTag</tagclass>
</tag>
</taglib>f
Пример применения атрибутов в
пользовательском теге
В следующем примере показан обработчик тегов с именем DoublerTag, ко-
торый удваивает целое число и выводит результат в web-браузере. Здесь ис-
пользуется атрибут number. Обработчик имеет метод setNumber, который вы-
зывается контейнером JSP для установки значения атрибута. Код обработчика
приводится в листинге 11.10.
Л исти н г 11.10 DoublerTag
package com.brainysoftware;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class DoublerTag implements Tag {
private int number;
public void setNumber(int number) {
this.number = number;
PageContext pageContext;
Применение пользовательских тегов JSP
365
Манипулирование содержимым тела
с помощью BodyTag и BodyContent
Пользовательский тег JSP может иметь тело, например:
<%@ taglib uri=”/myTLD" prefix=”x"%>
<x:theTag>This is the body content</x:theTag>
При использовании интерфейсов Tag и IterationTag нельзя манипулировать
содержимым тела. К нему даже нет доступа.
Если необходимо манипулировать содержимым тела пользовательского
тега, нужно использовать интерфейс BodyTag и класс BodyContent пакета
javax.servlet.jsp.tagext. Рассмотрим эти два элемента и создадим обработчик те-
гов, который манипулирует содержимым тела.
Интерфейс BodyTag
Интерфейс BodyTag расширяет интерфейс IterationTag, добавляя два метода,
dolnitBody и setBodyContent, и два static final integer, EVAL_BODY_BUFFERED
и EVAL_BODY_TAG. В JSP 1.2 не рекомендуется использовать
EVEN_BODY_TAG.
Жизненный цикл обработчика тегов, который реализует интерфейс BodyTag,
такой же, как и у обработчиковтегов, реализующих интерфейс IterationTag. Раз-
личие состоит в том, что метод doStartTag обработчика тегов, реализующего
BodyTag, может вернуть SKIP_BODY, EVAL_BODY_INCLUDE или
EVAL_BODY_BUFFERED. Если метод возвращает EVALBODY1NCLUDE, тело
вычисляется, как в IterationTag. Если метод возвращает EVAL_BODY_BU FFERED,
создается объект BodyContent, который представляет содержи мое тела пользо-
вательского тега.
Контейнер JSP вызывает два дополнительных метода в обработчиках тегов,
реализующих интерфейс BodyTag: setBodyContent и dolnitBody. Эти два метода
имеют следующие сигнатуры:
public void setBodyContent(BodyContent bodyContent)
public void doInitBodyO throws javax.servlet. jsp. JspException
Метод setBodyContent вызывается после doStartTag, а затем следует метод
dolnitBody. Однако setBodyContent вызываться не будет, если справедливо одно
из условий:
• Пользовательский тег не имеет тела.
• Пользовательский тег имеет тело, но метод doStartTag возвращает
SKIP_BODY или EVAL_BODY_INCLUDE.
362
Глава 11
Листинг 11.12 Продолжение
<shortname></shortname>
<tag>
<name>myTag</name>
<tagclass>com.brainysoftware.DoublerTag</tagclass>
<attribute>
<name>number</name>
<requi red>t rue</requi red>
</attribute>
</tag>
</taglib>
Интерфейс IterationTag
Интерфейс IterationTag расширяет интерфейс Tag, добавляя новый метод с
именем doAfterBody и static final integer EVAL_BODY_AGAIN. Этот метод вы-
зывается после метода doStartTag и может вернуть либо Tag.SKIP_BODY, либо
lterationTag.EVAL_BODY_AGAIN. Если возвращается последнее значение,
метод doAfterBody вызывается снова. Если возвращаемым значением являет-
ся Tag.SKIP_BODY, тело будет пропущено и контейнер JSP вызовет метод
doEndTag.
Пример: PowerTag
В следующем примере приводится обработчик тегов, который реализует
интерфейс IterationTag, и демонстрируется использование метода doAfterBody.
Обработчик тегов, называемый PowerTag, возводит число в степень. Затем
он записывает в web-браузере следующее:
number~power=result
Например, если задать основание 2 и показатель степени 3, то обработчик
тегов вернет браузеру строку:
2"3=8
Код обработчика тегов представлен в листинге 11.13.
Листинг 11.13 Обработчик тегов PowerTag
package com.brainysoftware;
import javax.servlet.jsp.*;
Применение пользовательских тегов JSP
367
* ' ’ на " "
* /
private String encodeHtmlTag(String tag) {
if (tag==null)
return null;
int length = tag.length();
StringBuffer encodedTag = new StringBuffer(2 * length);
for (int i=0; Klength; i++) {
char c = tag.charAt(i);
if (c== < )
encodedTag.append("<");
else if (c==’>')
encodedTag.append(">");
else if (c=='&')
encodedTag. append("&'');
else if (c==’”')
encodedTag.append(""");
else if (c==' ')
encodedTag.append(" ");
else
encodedTag.append(c);
)
return encodedTag.toStringO;
}
public void setParent(Tag t) {
)
public void setPageContext(PageContext p) {
pageContext = p;
}
public void releaseO {
)
public Tag getParentO {
return null;
)
public int doStartTagO {
return EVAL_BODY_BUFFERED;
}
public void setBodyContent(BodyContent bodyContent) {
this.bodyContent = bodyContent;
}
public void doInitBodyO (
}
public int doAfterBodyO {
String content = bodyContent.getStringO;
try{
JspWriter out = bodyContent.getEnclosingWriterO;
out.print(encodeHtmlTag(content));
)
364
Глава 11
Листинг 11.13 Продолжение
try {
JspWriter out = pageContext. getOutO;
out. println(number + """ + power + ”=” + result);
}
catch(Exception e) {
}
return EVAL.PAGE;
>
}
Для тестирования кода потребуются страница JSP (см. листинг 11.14) и файл
TLD (см. листинг 11.15).
Л исти н г 11.14 Страница JSP, которая вызывает PowerTag
<%@ taglib uri="/myTLD" prefix="easy”%>
<easy:myTag number=”2” power="3"/>
Листинг 11.15 Файл TLD, используемый в примере
<?xml version="1.О" encoding="IS0-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<shortname></shortname>
<tag>
<name>myTag</name>
<tagclass>com.brainysoftware.PowerTag</tagclass>
<attribute>
<name>number</name>
< requi red>t rue</requi red>
</attribute>
<attribute>
<name>power</name>
< requi red>t rue</requi red>
</attribute>
</tag>
</taglib>
Применение пользовательских тегов JSP
369
Листинг 11.18 Файл TLD для этого примера
<?xml version="1.0" encoding="IS0-8859-r ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
’’http: //j ava.sun.com/j2ee/dtds/web-jsptaglibrary_1_1,dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<shortnamex/shortname>
<tag>
<name>myTag</name>
<tagclass>com.brainysoftware.EncoderTag</tagclass>
<bodycontent>tagdependent</bodycontent>
</tag>
</taglib>
Классы поддержки
Хотя интерфейсы Tag, IterationTag и BodyTag предоставляют прекрасный
способ создания обработчиков тегов, очевиден один недостаток: необходимо
предоставлять реализации всех методов, включая те, которые не используют-
ся. Это, конечно, усложняет код, и требуется больше времени для написания
и отладки класса.
Для решения этой проблемы пакетjavax.servlet.jsp.tagext предоставляет клас-
сы поддержки, которые реализуют эти интерфейсы. Это классы TagSupport и
BodyTagSupport. Вместо реализации интерфейса, можно расширить один из
этих классов.
Класс TagSupport
Класс TagSupport реализует интерфейс IterationTag. Его сигнатура:
public class TagSupport implements IterationTag, java.io.Serializable
Класс TagSupport предназначен для использования в качестве базового клас-
са для обработчиков тегов.
Класс BodyTagSupport
Класс BodyTagSupport реализует интерфейс BodyTag и имеет следующую
сигнатуру:
public class BodyTagSupport extends TagSupport implements BodyTag
Обработчики тегов, которым необходимо реализовать интерфейс BodyTag,
используют этот класс для создания подклассов.
366
Гпава 11
Метод doInitBody можно использовать для подготовки вычисления тела.
Обычно этот метод вызывается контейнером JSP после метода setBodyContent.
Однако этот метод не будет вызываться, если справедливо одно из условий:
• Пользовательский тег не имеет тела.
• Пользовательский тег имеет тело, но метод doStartTag возвращает
SKIP_BODYmiH EVAL_BODYJNCLUDE.
Класс BodyContent
Класс BodyContent является абстрактным классом, который расширяет
класс javax.servlet.jsp.JspWriter. Класс BodyContent представляет содержимое
тела пользовательского тега, если оно есть. Содержимое тела получают из. ме-
тода setBodyContent интерфейса BodyTag.
Пример: манипулирование содержимым тела
В следующем примере показан обработчик тегов, который манипулирует
содержимым тела пользовательского тега JSP. Обработчик тегов кодирует в
HTML содержимое тела и выводит закодированную версию содержимого в
браузере. Обработчик тегов называется EncoderTag и содержит метод
html EncodeTag класса com.brainysoftware.java.StringUtil. Обработчик тегов пред-
ставлен в листинге 11.16.
Л исти н г 11.16 EncoderTag
package com.brainysoftware;
import javax.servlet.jsp.* *;
import j avax.servlet.jsp.tagext.*;
public class EncoderTag implements BodyTag {
PageContext pageContext;
BodyContent bodyContent;
/* *
* Кодирует тег HTML, чтобы он отображался,
* как в браузере.
* В частности, этот метод ищет в
* переданной String и заменяет каждый экземпляр
* следующих символов:
* '<’ на ”<”
* ’>' на ”>”
* на ”&атр;”
* ”” на ”""
Применение пользовательских тегов JSP
371
<tag>
<name>myT ag</name>
<tagclass>com.brainysoftware.CapitalizerTag</tagclass>
<bodycontent>tagdependent</bodycontent>
</tag>
</taglib>
Заключение
В этой главе было показано, что пользовательские теги являются альтернати-
вой JavaBeans. При их применении страницы JSP могут содержать только часть
приложения, отвечающую за представление. Пользовательские теги JSP слу-
жат для вызова ассоциированного обработчика тегов — класса Java, который
управляет обработкой тега.
Обработчики тегов реализуют интерфейс Tag, IterationTag или BodyTag, либо
они расширяют один из двух классов поддержки: TagSupport или
BodyTagSupport.
368
Глава 11
Листинг 11.16 Продолжение
catch(Exception е) {}
return SKIP_BODY;
}
public int doEndTagO throws JspException {
return EVAL_PAGE;
}
}
Метод set Body Content передает объект BodyContent из контейнера JSP в
объектную ссылку bodyContent, областью действия которой является класс:
public void setBodyContent(BodyContent bodyContent) {
this.bodyContent = bodyContent;
}
Нельзя манипулировать содержимым тела в методе setBodyContent. Необ-
ходимо подождать, пока будет вызван метод doInitBody. Чтобы реализовать
это, в метод doAfterBody записывается следующий код:
String content = bodyContent.getString();
try {
JspWriter out = bodyContent.getEnclosingWriter();
out.print(encodeHtmlTag(content));
}
catch(Exception e) {}
return SKIP_BODY;
Прежде всего мы получаем строковое представление содержимого тела с
помощью метода getString класса BodyContent. Затем необходимо воспользо-
ваться методом get Enclosing Writer, чтобы получить неявный объект с текущей
страницы JSP. Наконец, чтобы вывести String в браузере, вызывается метод
print из JspWriter.
Для применения обработчика тегов нужны страница JSP, подобная той, что
представлена в листинге 11.17, ифайлТЬО, показанный в листинге 11.18.
Л исти н г 11.17 Страница JSP, которая использует обработчик тегов
EncoderTag
<%@ taglib uri='7myTLD” prefix="easy”%>
<easy;myTag><BR> means change line</easy:myTag>
Программируемая загрузка файлов
373
В этой главе рассматривается методика, которая позволяет программ-
ным путем посылать файл браузеру. Будем называть ее программируемой
загрузкой файлов. Используя эту методику, мы получаем полный контроль
над загружаемым файлом.
Будет приведен пример того, как выполнить программируемую загрузку
файла со страницы JSP.
Рис. 12.1. Диалоговое окно File Download в Internet Explorer
В качестве альтернативы вы можете использовать Bean загрузки файлов
(com.brainysoftware.web.FileDownload), который находится в каталоге software/
FileDownload на сайте www.lory-press.ru. Скопируйте файл FileDownload.jar
в каталог WEB-INF/lib каталога приложения и перезапустите Tomcat.
Ключевые вопросы программируемой
загрузки файлов
При создании приложения, которое будет посылать файл браузеру, необ-
ходимо рассмотреть ряд вопросов:
• Неавторизованные пользователи не должны иметь возможности загружать
файл путем ввода его URL. Это означает, что файл должен храниться вне
виртуального каталога. Например, если используется виртуальный каталог
C:\Tomcat\webapps\MyVir, то сохранение файла в этом каталоге или в его
подкаталоге делает файл видимым любому в Интернете.
• Независимо от типа файла, браузер должен выводить диалоговое окно
File Download (см. рис. 12.1). Даже если браузер распознает расширение
файла, он не должен автоматически запускать приложение для выпол-
нения файла. (Например, если загружаемый файл является документом
Microsoft Word, браузер не должен запускать Microsoft Word.)
Программная загрузка файлов имеет ряд приложений: приложения управ-
ления документами, защиты файлов и т. д. При создании приложения, пред-
назначенного для программной загрузки файлов:
1. Задайте тип содержимого ответа как APLLICATION/OCTET-STREAM.
Это значение не зависит от регистра символов.
370
Глава 11
Для иллюстрации применения этих классов поддержки приведем пример
обработчика тегов с именем CapitalizerTag, который преобразует содержимое
тела в символы верхнего регистра и выводит эту версию в браузере. Код пред-
ставлен в листинге 11.19.
Листинг 11.19 CapitalizerTag
package com.brainysoftware;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class CapitalizerTag extends BodyTagSupport {
public int doAfterBodyO {
String content = bodyContent.getStringO;
try{
JspWriter out = bodyContent.getEnclosingWriterO;
out.print(content.toUpperCase());
}
catch(Exception e) {}
return SKIP_B0DY;
}
}
Обработчик тегов заметно упростился. Не нужно предоставлять реали-
зации методов, которые не применяются. Для завершения примера ниже
приводятся страница JSP, которая использует обработчик тегов
CapitalizerTag (см. листинг 11.20), и файл TLD (см. листинг 11.21).
Листинг 11.20 Страница JSP, которая использует CapitalizerTag
<%@ taglib uri='7myTLD" prefix=”easy"%>
<easy:myTag>See the big picture?</easy:myTag>
Листинг 11.21 Файл TLD для этого примера
<?xml version=’’1.0” encoding=’’IS0-8859-1’’ ?>
<!DOCTYPE taglib
PUBLIC ’’-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN’’
’’http: //j ava. sun.com/j2ee/dtds/web-jsptaglibrary-1_1 .dtd”>
<taglib>
<tlibversion>1.0</tlibversion>
<shortname></shortname>
Программируемая загрузка файлов
375
int i;
while ((i=fileInputStream.read()) != -1) {
out.write(i);
}
filelnputSt ream.close();
out.close();
%>
Использование Bean загрузки файлов от
компании Brainysoftware.com
Простейшим способом программной загрузки файлов является использова-
ние Bean, находящегося в каталоге software/FileDownload на сайте www.lory-
press.ru. Код листинга 12.2 предлагает пользователю выполнить аутентифи-
кацию, прежде чем отправить файл браузеру. Только если параметры userName
и password содержат соответственно значения «james» и «соок», файл будет
послан.
В Bean буфер очищается перед отправкой файла, поэтому можно писать
директивы обычным образом.
Листинг 12.2
<jsp:useBean id="theBean" scope="page"
class=’'com. brainysoftware.web. FileDownload" />
<%
String userName = request.getParameter("userName");
String password = request.getParameter("password");
// загрузка разрешается, только если пользователь предоставляет
// правильное имя пользователя и пароль
if (userName!=null && password !=null &&
userName.equalsC’james") &&
password.equals("cook")) {
String filepath = "C:\\TV.bmp";
theBean.download(response, filepath);
}
else
out.print("Login incorrect");
%>
Программируемая
загрузка файлов
J В. ля пользователей Интернета загрузка файлов является обычным делом,
выполняемым без особых усилий. Создание web-программы, которая позво-
ляет только авторизованным пользователям загружать определенные файлы,
это совершенно другая задача. Обычно операционная система или система
аутентификации web-контейнера позволяет защищать файлы с помощью па-
роля, так что загрузка файла разрешается, только если пользователь вводит
правильное имя пользователя и пароль. Однако при таком подходе существу-
ют как минимум две проблемы:
• При наличии более одного пользователя пароль будет применяться со-
вместно. Однако предполагается, что пароль должен быть приватным и
конфиденциальным. Чем больше людей знают пароль, тем менее безо-
пасным он является. Более того, если пароль известен многим пользо-
вателям, трудно проследить, кто загружает определенный файл. В ряде
случаев применение операционной системы или системы аутентифика-
ции web-контейнера не предоставляет такой гибкости, как сравнение
полномочий пользователя с таблицей базы данных.
• Если браузеру известен тип файла, он пытается открыть его с помощью
правильного приложения. Иногда это не то, что требуется. Необходи-
мо, чтобы браузер выводил диалоговое окно File Download (Загрузка
файлов), показанное на рис. 12.1.
Пересылка
файлов
-1. -1-ересылка файлов, помимо приложений email, играет существенную роль
в разработках на основе Java. Пересылка файла на сервер — важная функция,
которая предлагается во все большем количестве типов приложений, включая
систему управления документами на основе Web и приложения типа ^защи-
щенная пересылка файлов по HTTP».
В этой главе рассматривается все, что необходимо знать о пересылке фай-
лов. Но прежде чем перейти непосредственно к кодированию, следует изу-
чить теорию: запрос HTTP. Это важно, потому что при пересылке файлов при-
ходится иметь дело с необработанными данными, которые не удается получить
путем простого запроса HTTP-объекта Request.
В последнем разделе этой главы говорится о Bean File Upload от компании
Brainysoftware.com, который можно найти на сайте www.lory-press.ru. Покупка
этой книги предоставляет право на его использование только в учебных це-
лях. Для других целей, коммерческих или некоммерческих, необходимо по-
лучить отдельную лицензию. Дополнительную информацию о лицензионном
соглашении можно найти в каталоге software/fileUpload на сайте www.lory-
press.ru. Начнем с изучения запроса HTTP.
Запрос HTTP
Каждый запрос HTTP, поступающий от web-браузера или другого приложе-
ния web-клиента, состоит из трех частей:
374
Глава 12
2. Добавьте заголовок ответа HTTP с именем Content-Disposition и задайте
ему следующее значение:
attachment; filename=theFileName
где theFileName является именем по умолчанию для файла, которое по-
явится в диалоговом окне File Download. Это обычно то же имя, что и у
файла, но не обязательно.
3. Не посылайте никаких символов, не имеющих отношения к содержимому
файла. Это может произойти случайно. Например, если необходимо по-
местить несколько директив page на странице JSP загрузки файлов, можно
написать директивы обычным образом:
<%@ page import="java. io.FileInputStream”%>
<jsp:useBean id="DBBean!d" scope="page" class="docman.OBBean" />
Символ возврата каретки в конце первой директивы page будет послан бра-
узеру. Запишем эти директивы следующим образом:
<%@ page import=”java.io.FilelnputStream"
%><jsp:useBean id="DBBean!d" scope="page" class-'docman.DBBean" />
Выглядит необычно, но это безопасно.
Запись кода программной загрузки файла в JSP требует использования ме-
тода setContentType для задания типа содержимого ответа как APPLICATION/
OCTET-STREAM.
Метод setHeader добавляет требуемый заголовок. Теперь с помощью класса
java.io.FilelnputStream легко выполнить чтение файла по одному символу за
раз. Код листинга 12.1 дает пользователю возможность программным образом
загрузить файл C:\access.log в операционной системе Windows. Если применя-
ется другая операционная система, нужно изменить значения переменных
filename и filepath.
Листинг 12.1. Программная загрузка файла в JSP
<%
// извлечение файла
String filename = "access.log”;
String filepath ~ "C:\\";
response.setContentType(
"APPLICATION/OCTET-STREAM");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + filename + "\"”);
java.io.FilelnputStream fileinputstream =
new java.io.FileInputStream(filepath + filename);
Пересылка файлов
379
Заголовки запроса HTTP
Второй компонент запроса HTTP состоит из ряда заголовков HTTP. Суще-
ствуют четыре типа заголовков HTTP: general, entity, request и response. Эти
заголовки представлены в таблицах 13.1, 13.2 и 13.3. Заголовки типа response
зависят от запроса HTTP и здесь не рассматриваются.
Таблица 13.1. Заголовки HTTP типа general
Заголовок Описание
Pragma Заголовок Pragma используется для включения
зависящих от реализации директив, которые могут
применяться к любому получателю в цепочке
запрос/ответ. Другими словами, pragma
предписывает серверам, используемым для отправки
запроса, вести себя определенным образом.
Заголовок pragma может содержать несколько
значений. Например, следующая строка кода
говорит всем прокси-серверам, что при передаче
этого запроса нельзя использовать кэшированную
версию объекта, а нужно загружать объект из
указанного места:
Pragma: no-cache
Date Заголовок Date представляет дату и время создания
сообщения.
Таблица 13.2. Заголовки HTTP типа entity
Заголовок Описание
Allow В заголовке Allow перечисляются методы, которые
поддерживаются ресурсом, определенным в URL.
Это поле информирует получателя о допустимых
методах, связанных с ресурсом. Заголовок Allow
не допускается в запросе, использующем метод
POST, и поэтому должен игнорироваться, если он
поступает как часть сущности POST. Например,
следующий заголовок Allow определяет, что GET и
HEAD являются допустимыми методами:
Allow: GET, HEAD
376
Глава 12
Заключение
Программная загрузка файлов является важной функцией, которая может быть
полезна в ряде приложений. В этой главе показано, что необходимо делать
для отправки файла в браузер и как заставить браузер вывести диалоговое окно
File Download. Знание ключевых моментов позволяет написать приложение
программной загрузки файлов в любой технологии. Дополнительные прило-
жения программной загрузки файлов представлены в главе 20.
Пересылка файлов
381
информации сообщают о том, что через
какое-то время ресурс может измениться или
информация может стать неточной. Приложения не
должны кэшировать содержимое после указанной
даты. Присутствие заголовка Expires не
подразумевает, что исходный ресурс изменится или
прекратит свое существование. Однако поставщики
информации должны включать заголовок Expires с
этой датой. Например, следующий заголовок
определяет, что содержимое может потерять силу
в 13:13:13, 6 августа 2002:
Expires: Tue, 6 Aug 2002 13:13:13 GMT
Last-Modified Заголовок Last-Modified указывает дату и время,
когда, по мнению отправителя, ресурс был изменен
последний раз. Точная семантика этого поля
определяется в терминах того, как получатель будет
его интерпретировать: если получатель
имеет копию ресурса, которая старше, чем дата,
заданная в поле Last-Modified, эта копия должна
считаться устаревшей. Например, следующий
заголовок определяет, что ресурс был
последний раз обновлен в 12:12:12, 6 августа 2002 г.:
Last-Modified: Tue, 6 Aug 2002 12:12:12 GMT
Таблица 13.3. Заголовки HTTP типа request
Заголовок Описание
From Заголовок From определяет, кто берет на себя
ответственность за запрос. Это поле содержит адрес
email отправившего запрос пользователя. Например,
следующий заголовок указывает, что email
пользователя rembrant@labsale.com:
From: rembrant@labsale.com
Accept Заголовок Accept содержит список схем
представления MIME, принимаемых клиентом.
Схемы перечисляются через точку с запятой. Сервер
использует эту информацию для определения, какие
типы данных можно посылать клиенту в ответе
HTTP. Поле Accept может содержать несколько
378
Глава 13
• Часть, содержащая метод запроса HTTP, универсальный идентифи-
катор ресурса (URI), протокол и версию протокола
• Заголовки запроса HTTP
• Тело запроса
Метод запроса, URI и протокол
Первая часть запроса HTTP состоит из метода запроса, URI и протокола.
Запрос HTTP версии 1.0 может использовать один из методов: GET, HEAD
или POST. В HTTP 1.1 к этим трем методам добавлены еще четыре: DELETE,
PUT, TRACE и OPTIONS.
Среди этих семи методов наиболее часто применяются GET и POST. Ме-
тод GET используется по умолчанию. Он задействуется, например, при вводе
URL, такого как http://www.brainysoftware.com, в поле Location или Address
браузера для запроса страницы. Метод POST обычно применяется в качестве
значения атрибута METHOD тега <FORM>. При пересылке файла необходи-
мо использовать метод POST.
Второй элемент запроса HTTP, URI, определяет ресурс Интернета. URI
обычно интерпретируется относительно корневого каталога web-сервера и
начинается с прямого слэша (/):
/virt ualRoot/pageName
Например, в типичном приложении JavaServer-страниц URI может быть
следующим:
/eshop/login. jsp
f Примечание М Дополнительную информацию об URI можно найти по адресу
's http://rfc.net/rfc2396.html.
Третьим элементом первой части являются протокол и версия протокола,
воспринимаемая запрашивающей стороной (браузером). Должен использоваться
протокол HTTP версии 1.0 или 1.1. Большинство web-серверов сегодня пони-
мают обе версии протокола HTTP; такой web-сервер способен обслуживать зап-
росы HTTP обеих версий. При применении старого web-сервера HTTP версии
1.0 могут возникать проблемы, если пользователи работают с современными
браузерами, которые посылают запросы с помощью протокола HTTP 1.1.
Итак, первая часть запроса HTTP выглядит следующим образом:
POST /virtualRoot/pageName HTTP/version
Например, типичная первая часть запроса HTTP:
POST /eshop/login.jsp HTTP/1.1
Пересылка файлов
383
Referer Заголовок Referer определяет URI, который содержал URI в заголовке запроса. В HTML это адрес страницы, на которой имеется ссылка на запрошенный объект. Подобно заголовку User-Agent, этот заголовок не требуется, он предназначен в основном для целей серверной статистики и отслеживания. Например, следующий заголовок определяет, что текущий запрос посылается в результате того, что пользователь щелкнул мышью на ссылке http://localhost:8080/MyApp/Page1.jsp. Refеrer: http://localhost:8080/MyApp/Page1.jsp.
Authorization Заголовок Authorization содержит информацию авторизации. Первое слово в этом заголовке определяет тип используемой системы авторизации. Затем после пробела должна следовать информация авторизации, такая как имя пользователя, пароль и т. д. Например, типичный заголовок Authorization: Authorization: user ken:dragonlancer
If-Modified-Since Заголовок If-Modified-Since используется с методом GET для указания условия. По сути, если объект не изменился с даты и времени, определенных в этом заголовке, то объект не посылается. Вместо этого используется локальная кэшированная копия объекта. Например: If-Modified-Since: Thu, 6 Aug 2002 11:12:29 GMT
Тело запроса
Тело является содержимым запроса HTTP.
Пример запроса HTTP
Приведем пример заголовка HTTP:
Accept: application/vnd.ms-excel, application/msword, */*
Accept-Language: en-au
Connection: Keep-Alive
Host: localhost
380
Глава 13
Таблица 13.2. Продолжение
Заголовок Описание
Content-Encoding Заголовок Content-Encoding служит для описания типа кодирования, применяемого к содержимому. Его значение определяет механизм декодирования, который должен использоваться для получения типа носителя, указанного заголовком Content-Type. Например, следующий заголовок определяет, что x-gzip является механизмом декодирования, который необходимо применить для получения типа носителя, заданного заголовком Content-Type: Content-Encoding: x-gzip
Content-Length Заголовок Content-Length указывает размер (десятичное число октетов) тела, посланного получателю. Если же применяется метод HEAD, то Content-Length определяет размер тела, которое было бы послано в запросе GET. Приложения должны использовать это поле для указания размера пересылаемого тела, независимо от типа держимого. Действительное значение поля Content-Length требуется во всех сообщениях апросов НТТР/1.0, содержащих тело. Любой заголовок Content-Length, больший или авный 0, является действительным значением. Например, следующий заголовок определяет, что длина содержимого равна 123 452 байтам: Content-Length: 123452
Content-Type Заголовок Content-Type указывает тип содержимого, посланного получателю. Если же применяется метод HEAD, то Content-Type определяет тип содержимого, которое было бы послано в запросе GET. Например, следующий заголовок определяет, что содержимое ответа HTTP имеет тип text/html: Content-Type: text/html
Expires Заголовок Expires задает дату и время, по истечении которого содержимое должно считаться недействительным. Таким образом, поставщики
Пересылка файлов
385
ких агентов HTML при интерпретации формы с ENCTYPE-’multipart/form-
data" и/или тегом CINPUT type=”file">.
Например, автор формы HTML, который хочет запросить у пользователя
один или несколько файлов, должен написать следующее:
<FORM ACTI0N=Jsp1.jsp ENCTYPE="MULTIPART/FORM-DATA" METH0D=P0ST>
File to Upload: CINPUT NAME=filename TYPE=FILE>
<BR>
CINPUT TYPE=SUBMIT VALUE="Upload">
</FORM>
Встретив тег INPUT типа FILE, браузер может вывести имена выбранных
ранее файлов и кнопку Browse или Метод выбора. Щелчок мыши на кнопке
Browse заставит браузер войти в режим выбора файлов, соответствующий плат-
форме. Например, браузеры на основе Windows могут вывести окно выбора
файла. В этом диалоговом окне пользователь сможет заменить текущий вы-
бор, добавить новый файл и т. д.
HTTP-запрос пересылки файла
Для начала познакомимся с HTTP-запросом пересылки файла. Следующее
небольшое приложение не предназначено для пересылки файла; однако оно
демонстрирует, как подготовить страницу HTTP к пересылке файла и запи-
сать HTTP-запрос в файл для последующего изучения. Можно просмотреть
созданный файл в текстовом редакторе, чтобы узнать имя файла, содержимое
файла и получить другую полезную информацию. Это небольшое приложе-
ние состоит из файла HTML с именем main.html, файла JSPc именем Jspl.jsp
и JavaBean с именем SimpleBean.
Файл main.html может использоваться клиентом для выбора файла, пере-
сылаемого на сервер. Листинг 13.1 представляет его код.
Листинг 13.1 Файл main.html
<HTML>
<HEAD>
<TITLE>File Upload</TITLE>
</HEAD>
<BOOY>
CFORM ACTI0N=Jsp1.jsp ENCTYPE="MULTIPART/FORM-DATA" METHOD=POST>
Author: CINPUT TYPE=TEXT Name=Author>
<BR>
Company: CINPUT TYPE=TEXT Name=Company>
<BR>
382
Глава 13
Таблица 13.3.
Заголовок
Accept-Encoding
Accept- Language
User-Agent
Продолжение
Описание
заголовке запроса, то по умолчанию предполагаются
типы text/plain и text/html. Например,
следующий заголовок определяет несколько схем
MIME, приемлемых для клиента:
Accept: text/plain;
text/htmlAccept: image/gif;
image/jpeg
Заголовок Accept-Encoding аналогичен по синтаксису
заголовку Accept; однако он определяет схемы
кодирования содержимого, которые приемлемы в
ответе. Например, следующий заголовок указывает,
что x-compress и x-zip являются схемами
кодирования содержимого, которые может
принимать ответ:
Accept-Encoding: x-compress; x-zip
Заголовок Accept-Language также аналогичен
заголовку Accept. Он определяет предпочтительный
язык ответа. Следующий пример задает English в
качестве допустимого языка:
Accept-Language: en
Заголовок User-Agent, если присутствует, определяет
имя клиентского браузера. Первое слово должно
быть названием программного обеспечения, за ним
идут слэш и необязательный номер версии. Могут
также включаться любые другие имена продуктов,
являющихся частью программного пакета. Каждая
пара имя/версия должна отделяться пробелом. Это
поле применяется в основном для целей статистики
и позволяет серверам отслеживать использование
программного обеспечения и нарушение протокола.
Например, следующий заголовок определяет, что
клиент использует Internet Explorer 4.01
в операционной системе Windows 98:
User-Agent: Mozilla/4.0
(compatible; MSIE 4.01; Windows 98)
Пересылка файлов
387
}
pw.close();
}
}
Интерфейс пользователя задается файлом main.html и показан на рис. 13.1.
Для этого примера вводятся некоторые значения элементов Author и Company
и выбирается файл с именем abisco.html. Для пересылки выбран файл HTML:
так как файл HTML является по сути текстовым файлом, его содержимое легко
вывести.
Содержимое файла abisco.html:
<HTML>
<HEAD>
<TITLE>Abisco</TITLE>
</HEAD>
</HTML>
Рис. 13.1. Пользовательский интерфейс примера
После отправки формы (файла main.html) на страницу Jspl.jsp страница
вызывает SimpleBean для записи всего содержимого объекта HttpServletRequest
в файл с именем Demo.out.
В файл Demo.out будет записано следующее:
--------------7615340138
Content-Disposition: form-data; name="Author"
384
Глава 13
Referer: http://local.host/exaniples/jsp/num/demo. jsp
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
LastName=t ruman&Fi rstName=daniel
Первая строка заголовка сообщает, что пославший этот запрос браузер мо-
жет принимать ряд форматов файлов, включая Microsoft Excel и Microsoft Word.
За этой информацией следуют используемый язык (в данном случае Australian
English), тип соединения (keep-alive) и имя хоста (localhost). Заголовок также
сообщает серверу, что запрос послан с demo.jsp, которая расположена в ка-
талоге http://localhost/examples/jsp/num/. В записи User-Agent запрос по-
казывает, что пользователь применяет Microsoft Internet Explorer версии
4.01, который совместим с Netscape Navigator 4.0. Операционная система
пользователя — Windows 98 (см. таблицы 13.1, 13.2 и 13.3).
За заголовком следуют две пары символов возврата каретки и перевода стро-
ки. Длина этого разделителя равна 4 байтам, поскольку каждая пара символов
возврата каретки и перевода строки состоит из символов ASCII с номерами 13
и 10. Тело запроса HTTP содержит код:
LastName=truman&FirstName=daniel
Заголовок HTTP, очевидно, поступает из формы с двумя элементами ввода:
элемент LastName имеет значение truman, а элемент FirstName — значение
daniel.
Отметим, что длина LastName=truman&FirstName=daniel равна 32, как ука-
зывает заголовок Content-Length.
Проанализировав запрос HTTP, воспользуемся этой информацией для
кодирования. Чтобы написать приложение пересылки файла, необходимо
знать как серверную, так и клиентскую сторону. Следующий раздел посвящен
клиентской стороне.
Клиентский HTML
До появления стандарта RFC 1867 атрибут TYPE элемента INPUT мог прини-
мать восемьзначений: CHECKBOX, HIDDEN, IMAGE, PASSWORD, RADIO,
RESET, SUBMIT и TEXT Эти элементы формы нашли свое применение во
многих приложениях, где нужно было пересылать ввод пользователя на сер-
вер. Однако ни один из них не способен участвовать в пересылке текстового
или двоичного файла. В связи с этим для атрибута TYPE элемента Input было
введено еще одно возможное значение: FILE. Кроме того, RFC описывает
новый MIME-тип multipart/form-data и определяет поведение пользовательс-
Пересылка файлов
389
Content-Disposition: form-data; name="Filename";
filename="C:\123data\abisco.html"
Эта строка утверждает, что элемент ввода FILE называется Filename и путь
доступа к файлу — C:\123data\abisco.html.
Вторая строка указывает тип содержимого файла. Это значение зависит от
пересылаемого файла. В данном примере значением является text/html:
Content-Type: text/html
Подобно нефайловому элементу ввода, содержимое начинается после двух
комбинаций символов возврата каретки и перевода строки.
Значение файлового разделителя зависит от
операционной системы клиента. Предыдущий код
иллюстрирует выходные данные на машине Windows.
Linux/Unix использует прямой слэш (/) в качестве
файлового разделителя, a Macintosh — двоеточие (:).
Пересылка файла
Теперь можно написать страницу JSP, выполняющую пересылку файла. Эта
JSP представлена в листинге 13.4.
Листинг 13.4 Страница JSP пересылки файла
<%@ page import="java.io.*" %>
<%
// каталог, где будет сохраняться пересылаемый файл
String savePath = ”C:\\l23data\\";
String filename =
ServletlnputStream in = request. getlnputStreamO;
byte[] line = new byte[128];
int i = in. readl_ine(line, 0, 128);
int boundaryLength = i - 2;
String boundary = new String(line, 0, boundaryLength);
//-2 отбрасывает символ новой строки
while (i != -1) {
String newLine = new String(line, 0, i);
if (newLine.startsWith("Content-Disposition: form-data; name=\’”’)) {
String s = new String(line, 0, i-2);
386
Глава 13
Листинг 13.1 Продолжение
Select file to upload <INPUT TYPE=FILE Name=Filename>
<BR>
CINPUT TYPE=SUBMIT VALUE="Upload">
</FORM>
</BODY>
</HTML>
В теге <FORM> используется атрибут ENCTYPE, и его значением являет-
ся MULTIPART/FORM-DATA. Определены четыре элемента ввода, включая
кнопку Submit. Первые два являются обычными элементами TEXT с именами
Author и Company. Третий элемент имеет тип FILE; это элемент ввода, кото-
рый используется для выбора файла.
Атрибут ACTION формы имеет значение Jspl.jsp. Это означает, что запрос
(а также передаваемый файл) посылается в файл Jspl .jsp.
Файл Jspl.jsp вызывает Bean с именем SimpleBean (см. листинг 13.2).
Листинг 13.2 Файл Jspl.jsp
cjsp: useBean id=’TheBean" scope=''page" class="SimpleBean
<%
TheBean.doUpload(request);
%>
Листинг 13.3 представляет код SimpleBean.
Листинг 13.3 SimpleBean
import java. io. *;
import javax.servlet.http.HttpServletRequest;
import javax. servlet.http.HttpServletResponse;
import javax.servlet.ServletlnputStream;
public class FileUploadBean {
public void do(Jpload(HttpServletRequest request) throws
lOException {
Printwriter pw = new PrintWriter(
new BufferedWriter(new FileWriter("Demo.out")));
ServletlnputStream in = request.getInputStream();
int i = in.read();
while (i != -1) {
pw.print((char) i);
i = in.read();
Пересылка файлов
391
Кодлистинга 13.4 начинается с получения ссылки на объект ServletInputStream
запроса HTTP. В ServletInputStream записывается содержимое пересланного
файла:
ServletInputStream in = request.getInputStream();
Начало пересланного файла отделяется границей и последовательностью
символов возврата каретки и перевода строки. Поэтому можно прочитать со-
держи мое объекта HttpServletRequest строка за строкой. Следующая строка кода
определяет байтовый массив с именем line:
byte[] line = new byte[128];
Затем используется метод readLine объекта ServletInputStream для считыва-
ния первой строки содержимого объекта HttpServletRequest:
int i = in. readl_ine(line, 0, 128);
Первая строка задает границу и ее длину. Граница завершается последова-
тельностью символов возврата каретки/перевода строки; поэтому реальная
длина на 2 меньше числа байтов, возвращаемых методом readLine:
int boundaryLength = i - 2;
Граница извлекается из строки байтового массива, при этом отбрасывают-
ся последние два символа возврата каретки и перевода строки:
String boundary = new String(line, 0, boundaryLength);
После извлечения границы можно начать извлечение значения элемента
формы, считывая содержимое HttpServletRequest строка за строкой в цикле
while, пока не будет достигнут конец, при этом метод readLine вернет -1:
while (i != -1) {
String newLine = new String(line, 0, i);
}
В операционных системах Windows имя пересылаемого файла передается
вместе с путем доступа к файлу. В других операционных системах никакой
информации о пути доступа к файлу не посылается.
388
Глава 13
A. Christie
--------------7d15340138
Content-Disposition: form-data; name=”Company”
Abisco
--------------7d15340138
Content-Disposition: form-data; name=”Filename”
filename=”C:\123data\abisco.html”
Content-Type: text/html
<HTML>
<HEAD>
<TITLE>Abisco</TITLE>
</HEAD>
</HTML>
--------------7d15340138-
Тело запроса HTTP содержит все входные данные формы, включая пере-
сланный файл. Введенные значения отделяются друг от друга разделителем.
Иногда называемый границей, этот разделитель состоит из нескольких десят-
ков символов тире и случайного числа. В приведенном примере ограничите-
лем является строка:
--------------7d15340138
Тот же ограничитель действует как разделитель между двумя значениями
элементов формы. Последний ограничитель заканчивает тело двумя допол-
нительными тире:
--------------7d15340138-
Для входного значения, которое поступает из нефайлового элемента, за
ограничителем следует строка:
Content-Disposition: form-data; name=inputName
Здесь inputName — это имя элемента формы. Например:
Content-Disposition: form-data; name=’'Author”
За строкой следуют две комбинации символов возврата каретки/перевода
строки и значение элемента.
Для файл а за ограничителем следу ют две строки. Первая содержит имя вход-
ного элемента FILE и путь доступа к файлу на компьютере пользователя. В
приведенном запросе HTTP эта строка имеет вид:
Пересылка файлов
393
Граница сигнализирует о конце переданного файла. Последний шаг состоит
в сохранении буфера в файле:
try {
// сохраняем пересланный файл
RandomAccessFile f = new RandomAccessFile(
savePath + filename, "rw");
byte[] bytes = buffer. toByteArrayO;
f.write(bytes, 0, bytes.length - 2);
f.close();
}
catch (Exception e) {}
Чтобы использовать файл FileUpload.jsp (см. листинг 13.4), нужна другая
страница, где выводится форма пересылки. Ее пример представлен в листин-
ге 13.5.
Л истинг 13.5 Форма для выбора пересылаемого файла
<HTML>
<HEAD>
<TITLE>File Upload</TITLE>
</HEAD>
<B0DY>
<FORM ACTION=http://localhost:8080/myApp/servlet/FileUpload.jsp
ENCTYPE=”MULTIPART/FORM-DATA" METH0D=P0ST>
Select file to upload <INPUT TYPE=FILE Name=Filename>
<BR>
CINPUT TYPE=SUBMIT VALUE=”Upload”>
</FORM>
</BODY>
</HTML>
FileUpload Bean
Код листинга 13.4 позволяет переслать файл и сохранить его в специальном ка-
талоге на сервере. Однако часто требуется послать также другие элементы ин-
формации вместе с файлом. FileUpload Bean, находящийся на сайте www.lory-
press.ru, позволяет передавать файл и неограниченное число значений полей.
Этот компонент имеет форму класса с именем com.brainysoftware.web. FileUpload,
и его можно найти в каталоге software/FileUpload/ на www.lory-press.ru.
Ниже представлен список методов класса FileUploadBean:
390
Глава 13
Листинг 13.4 Продолжение
int pos = s. indexOf(”filename=\'”');
if (pos != -1) {
String filepath = s.substring(pos+10, s.length()-1);
// Браузеры Windows добавляют полный путь доступа на стороне /
// клиента.
// Но браузеры Linux/Unix и Мас посылают только имя файла.
// Проверим, что это браузер Windows.
pos = filepath. lastlndexOfC’W);
if (pos != -1)
filename = filepath.substring(pos + 1);
else
filename = filepath;
}
//это содержимое файла
i = in.readLine(line, 0, 128);
i = in.readLine(line, 0, 128);
// пустая строка
i = in. readl_ine(line, 0, 128);
ByteArrayOutputStream buffer = new ByteArrayOutputStreamO;
newLine = new String(line, 0, i);
while (i != -1 && !newLine.startsWith(boundary)) {
// Проблему представляет последняя строка файла,
// содержащая символ новой строки.
// Поэтому необходимо проверить, что текущая строка
// является последней строкой.
buffer.write(line, 0, i);
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
try {
// сохраняем пересланный файл
RandomAccessFile f = new RandomAccessFile(
savePath + filename, "rw”);
byte[] bytes = buffer.toByteArrayO;
f.write(bytes, 0, bytes.length - 2);
f.close();
}
catch (Exception e) {}
}
i = in.readLine(line, 0, 128);
} // конец цикла while
%>
Пересылка файлов
395
</HEAD>
<BODY>
<FORM ACTI0N=Jsp1. jsp ENCTYPE="MULTIPART/FORM-DATA'’ METHOD=POST>
Author: <INPUT TYPE=TEXT Name=Author>
<BR>
Company: <INPUT TYPE=TEXT Name=Company>
<BR>
Comment: <TEXTAREA NAME=Comment></TEXTAREA>
<BR>
Select file to upload <INPUT TYPE=FILE Name=Filename>
<BR>
Description: <INPUT TYPE=TEXT Name=Description>
<BR>
Листинг 13.6 Файл HTML для пересылки файла
<INPUT TYPE=SUBMIT VALUE="Upload">
</FORM>
</BODY>
</HTML>
Когда пользователь отправляет форму, запрос HTTP передается в Jspl.jsp,
которая использует Bean FileUpload для обработки запроса. Jspl.jsp представ-
лена в листинге 13.7.
Листинг 13.7 Файл Jspl.jsp
<jsp:useBean id=”theBean" scope="page"
class="com.brainysoftware.web.FileUploadBean" />
<%
//в Windows
theBean.setSavePath(”C:\\123data\\");
//в Linux/Unix может быть что-то типа
//theBean.setSavePath(”/home/budi/");
*
theBean.doUpload(request);
theBean.save();
out. printlriC’Filename:" + theBean. getFilenameO);
out.println(”<BR>Content Type:" + theBean.getContentTypeO);
out.println("<BR>Author:" + theBean.getFieldValue("Author"));
out.println("<BR>Company:" + theBean. getFieldValue("Company"));
out. println("<BR>Comment:" + theBean. getFieldValue("Comment"));
%>
392
Глава 13
Теперь можно получить имя файла из прочитанной строки:
String s = new String(line, 0, i-2);
int pos = s. indexOf(”filename=\”’’);
if (pos ’ = -1) {
String filepath = s.substring(pos+10, s.length()-1);
// Браузеры Windows добавляют полный путь доступа на стороне клиента.
// Но браузеры Linux/Unix и Мас посылают только имя файла.
// Проверим, что это браузер Windows.
pos = filepath.lastIndexOf("\\");
if (pos != -1)
filename = filepath.substring(pos +1);
else
filename = filepath;
}
Перед началом содержимого пересланного файла можно обнаружить две
пары символов возврата каретки и перевода строки.
Поэтому метод read Line вызывается дважды:
i = in.readLine(line, 0, 128);
i = in. readl_ine(line, 0, 128);
Затем идет содержи мое файла. Оно буферизируется в ByteArrayOutputStream;
поэтому создается экземпляр этого объекта с помощью оператора:
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Чтение строки продолжается, пока не будет найдена другая граница:
newline = new String(line, 0, i);
while (i != -1 && ?newLine.startsWith(boundary)) {
// Проблему представляет последняя строка файла,
// содержащая символ новой строки.
// Поэтому необходимо проверить, что текущая строка
// является последней строкой.
buffer.write(line, 0, i);
i = in.readLine(line, 0, 128);
newLine = new String(line, 0, i);
}
Конфигурирование
системы безопасности
предыдущих главах говорилось о том, как реализовать страницу Login с
помощью сервлетов и JSP для ограничения доступа к определенному ресурсу.
Обычная практика состоит в написании кода, который получает имя пользова-
теля и пароль и сопоставляет их со значениями, хранящимися в базе данных.
Это прекрасно работает, однако существует другой подход к защите прило-
жения, не требующий написания кода, — нужно сконфигурировать дескриптор
развертывания web-приложения, которое необходимо защитить.
В этой главе рассматривается конфигурирование системы безопасности в
дескрипторе развертывания и различные методы аутентификации. Будет так-
же показано, как использовать элементы security-consraint и login-config для
конфигурирования безопасности приложения.
Наложение ограничений безопасности
Конфигурируя дескриптор развертывания, можно различными способами огра-
ничить доступ к некоторым ресурсам в приложении. Например, можно потребо-
вать от пользователя регистрации для просмотра одного конкретного ресурса, но
не других ресурсов. Можно также ограничить доступ к ресурсам, если запрос по-
слан с помощью определенного метода HTTP. Например, ресурс будет доступен
для просмотра, если в запросе пользователя применяется метод POST.
394
Глава 13
public String getFilepath()
Этот метод возвращает путь доступа к файлу, где сохранен пересланный
файл.
public String getFilenameO
Возвращает имя пересланного файла.
public String getContentTypeO
Возвращает тип содержимого пересланного файла.
public String getFieldValue(St ring fieldName)
Возвращает значение элемента формы HTML, имя которой определено в
fieldName.
public void setSavePath(String savePath)
Определяет имя каталога на сервере, где должен храниться пересланный
файл.
public void doUpload(HttpServletRequest request) throws lOException
Наиболее важный метод в классе FileUploadBean. Этот метод извлекает
имена и значения полей формы HTML, обрабатывает и сохраняет переслан-
ный файл.
Использование FileUpload Bean
Скопируйте файл BrainySoftwareFileUpload.jar из каталога software/
FileUpload (см. www.lory-press.ru) в каталог WEB-INF/lib каталога приложе-
ния и перезапустите Tomcat.
В следующих листингах приводятся файл HTML и файл JSP, которые ил-
люстрируют использование Bean. Файл HTML содержит форму с нескольки-
ми элементами. Файл HTML представлен в листинге 13.6, а файл JSP — в
листинге 13.7.
Листинг 13.6 Файл HTML для пересылки файла
<HTML>
<HEAD>
<TITLE>File Upload</TITLE>
Конфигурирование системы безопасности
399
Файл говорит о пользователях следующее:
• Первый пользователь называется «tomcat». Он может исполнять роль
«tomcat».
• Второй пользователь имеет имя «rolel» и может исполнять роль «rolel».
• Третий пользователь — «both», он может играть обе роли: «tomcat» и
«rolel».
Это, вероятно, не очень хороший пример для объяснения понятия пользо-
вателей и ролей, поэтому обратимся к следующему примеру:
<tomcat-users>
<user name="Joe" password="pwd" roles="marketingManager" />
<user name="Peter" password="0192" roles="HRManager" />
<user name="MrDirector” password="humblepine"
roles="marketingManager,HRManager" />
</tomcat-users>
Этот пример также работает с тремя пользователями; их имена Joe, Peter и
MrDirector. Joe является в организации управляющим маркетингом; поэтому
он может исполнять роль marketingManager. Peter — управляющий отдела кад-
ров, он исполняет роль HRManager. MrDirector — директор компании. Он
занимает самое высокое положение в компании и может играть обе роли:
marketingManager и Н RManager.
Каждый ресурс связан с ролью, которая может иметь к нему доступ. Серв-
лет, который выводит, например, платежную ведомость, скорее всего будет
связансролью HRManager. Вэтом случае Peter может указать свое имя пользо-
вателя и пароль для доступа к ведомости. У Joe также допустимые имя и па-
роль. Однако должность Joe не обеспечивает доступ к платежной ведомости.
Если он когда-либо попытается зарегистрироваться и получить доступ к серв-
лету с помощью своего имени и пароля, ему будет отказано.
С другой стороны, MrDirector может играть обе роли. Если однажды Peter
будет отсутствовать по причине болезни, MrDirector сможет воспользоваться
своим именем и паролем для доступа к сервлету, который выводит платежную
ведомость.
В файл tomcat-users.xml можно добавить сколько угодно пользователей.
Использование элемента security-constraint
Для ограничения доступа к ресурсу достаточно сконфигурировать дескрип-
тор развертывания. Для этого можно использовать несколько элементов. Наи-
более часто применяются элементы security-constraint и login-config.
Элемент security-config служит для ограничения доступа к одному или не-
скольким ресурсам. Он может появляться несколько раз в дескрипторе раз-
вертывания.
396
Глава 13
Пересылка нескольких файлов
Пересылка нескольких файлов возможна при использовании профессиональ-
ной версии Bean FileUpload (http://www.brainysoftware.com).
Заключение
Пересылка файлов является важной функцией, которая используется во мно-
гих приложениях. Такие приложения включают в себя email на основе Web
(для пересылки приложений), управление документами и FTP поверх HTTP.
В этой главе вы узнали, как переслать файл, и познакомились с заголовком
запроса HTTP. Говорилось также об обработке пересланного файла на сервере
и об использовании Bean Upload от Brainysoftware.com.
Конфигурирование системы безопасности
401
• transport-guarantee. Этот элемент может иметь одно из следующих зна-
чений: NONE, INTEGRAL или CONFIDENTIAL. NONE означает, что
приложение не требует никакой транспортной гарантии. INTEGRAL
указывает, что данные между сервером и клиентом должны посылать-
ся таким образом, чтобы они не могли быть изменены при передаче.
CONFIDENTIAL означает, что пересылаемые данные должны шиф-
роваться. В большинстве случаев для INTEGRAL и CONFIDENTIAL
используется SSL. Этот элемент является обязательным.
Приведем пример дескриптора развертывания (см. листинг 14.1).
Л и сти н г 14.1 Дескриптор развертывания
<?xml version="1.О" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
" htt p: //j ava. sun. com/j 2ee/dtds/web-app_2_3. dtd ’’>
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
</web-app>
Наличие такого дескриптора развертывания заставляет web-контейнер бло-
кировать любой запрос, который соответствует шаблону URL и приходит не
от пользователя, играющего роль менеджера. Элемент auth-constraint внутри
элемента security-constraint указывает на то, что ресурс может быть доступен
только пользователям в роли менеджера. На рис. 14.1 показан результат, фор-
мируемый в том случае, когда доступ к ресурсу пытается получить лицо, не
являющееся менеджером.
398
Глава 14
Обычно при ограничении доступа к ресурсу от пользователя требуется ре-
гистрация, т. е. ввод имени пользователя и пароля. Если пользователь предос-
тавляет правильные имя и пароль, доступ к странице разрешается. Конечно,
можно ограничить доступ к ресурсу, даже не разрешив пользователю ввести
свои полномочия. Однако если речь идет о том, чтобы сделать ресурс недо-
ступным, зачем он вообще нужен?
Можно использовать автоматический вывод страницы регистрации. В этом
случае браузер будет выводить свое диалоговое окно регистрации. Альтерна-
тивно вы можете разработать собственное диалоговое окно регистрации.
При наложении ограничений безопасности необходимо выбрать, какие ре-
сурсы защищать, задав шаблон URL. Если URL запроса HTTP соответствует
этому шаблону, доступ к ресурсу ограничивается; т. е. от пользователя будет тре-
боваться регистрация. Так, если задан шаблон /servlet/FirstServlet, контейнер Web
будет ограничивать любой запрос, URL которого содержит этот шаблон, на-
пример http://domain/myApp/servlet/FirstServlet. Можно использовать в шабло-
не URL групповой символ * для представления любого множества символов.
Например, шаблон /servlet/* будет ограничивать доступ к любому URL, кото-
рый содержит «/servlet/», в том числе к http://domain/myApp/servlet/Testing и
http://domain/myApp/servlet/Filter.
После определения требующих защиты ресурсов необходимо решить, кто
может иметь к ним доступ. Можно программным путем жестко закодировать
множество имен пользователей и паролей или проверять имя пользователя и
пароль по таблице в базе данных. Однако при использовании дескриптора
развертывания концепция немного отличается; в этом случае применяются
принципалы и роли.
Принципалы и роли
Концепция принципалов проста. Можно рассматривать их как пользова-
телей. Принципал является сущностью. Это может быть индивидуум, корпо-
рация или другой тип сущности. «Принципал» и «пользователь» используют-
ся взаимозаменяемо, когда говорят об идентификационной информации.
Роль является абстрактным объединением пользователей. Она относится
скорее к позиции, а не к индивидуальности. Можно рассматривать роль как
группу. В этом виде пользователь может исполнять одну или несколько ролей.
Как определяется список пользователей и ролей? Tomcat содержит файл с
именем tomcat-users.xml в каталоге conf в %CATALINA_HOME%. По умолча-
нию он объявляет трех пользователей следующим образом:
<tomcat-users>
<user name="tomcat" password=”tomcat" roles="tomcat" />
<user name="role1” password="tomcat” roles=”role1” />
<user name="both” password=’'tomcat" roles=,'tomcat, rolel” /> ..
</tomcat-users>
Конфигурирование системы безопасности
403
Элемент login-config может содержать следующие необязательные элементы:
• auth-method. Определяет методы аутентификации и может иметь одно
из следующих значений: BASIC, DIGEST, FORM или CLIENT-CERT
(см. ниже).
• realm-name. Описательное имя, которое будет выводиться в стандар-
тном диалоговом окне Login, если элемент auth-method установлен в
значение BASIC.
• form-login-config. Определяет страницы Login и error, которые будут
использоваться, если значением элемента auth-method является
FORM (см. ниже).
Методы аутентификации
Когда речь идет о защите web-приложения на основе Java, можно выбрать
один из четырех методов аутентификации:
Базовая аутентификация
Аутентификация на основе форм
Аутентификация на основе дайджеста
Уровень защищенного сокета (SSL) и аутентификация с помощью
клиентского сертификата
При использовании базовой аутентификации, когда пользователь пытается
получить доступ к ограниченному ресурсу, web-контейнер просит браузер
вывести стандартное диалоговое окно Login, содержащее два поля: одно для
ввода имени пользователя, а другое для пароля. Если пользователь вводит
правильные имя и пароль, сервер предоставляет запрошенный ресурс. Иначе
диалоговое окно Login выводится заново, чтобы пользователь мог попробовать
еще раз. Сервер даст пользователю три попытки для регистрации, после чего
пошлет сообщение об ошибке. Недостаток этого метода состоит в том, что имя
пользователя и пароль пересылаются на сервер с помощью кодирования
base64, которое является очень слабой схемой шифрования.
Аутентификация на основе форм аналогична базовой аутентификации; только
при этом сервер выводит пользовательскую страницу Login, которая должна
быть подготовлена разработчиком. Кроме того, будет выводиться
пользовательская страница Error, написанная разработчиком, в случае отказа
при попытке регистрации.
Методы аутентификации обеспечивают только вывод формы регистрации.
Если не применяется SSL, пароль посылается простым текстом и
оказывается незащищенным. При использовании SSL пароль шифруется.
Базовая аутентификация и аутентификация на основе форм посылают
информацию регистрации обычным текстом, однако можно использовать SSL
для шифрования данных.
Аутентификация на основе дайджеста работает подобно базовой
аутентификации; однако информация регистрации не посылается. Вместо
400
Глава 14
(^Примечанием Дополнительная информация об элементах, которые можно
использовать в дескрипторе развертывания, представлена в
главе 16.
Элемент security-constraint может содержать следующие элементы:
• display-name. Необязательный элемент, содержащий описательное
имя, которое будет выводиться утилитой манипулирования XML.
• web-resource-collection. Идентифицирует подмножество ресурсов,
доступ к которым должен быть ограничен. В web-resource-collection
можно определить шаблоны URL и метод HTTP. Если метод HTTP
неуказан, ограничения безопасности применяются ко всем методам.
• auth-constraint. Определяет роли пользователей, которые должны
иметь доступ к заданной совокупности ресурсов. Если элемент auth-
constraint не определен, ограничения безопасности применяются ко
всем ролям.
• user-data-constraint. Служит для указания, как должны быть защи-
щены данные, посылаемые клиенту и web-контейнеру (и наоборот).
Элемент web-resource-collection может иметь следующие обязательные и
необязательные подэлементы:
• web-resource-name. Имя, идентифицирующее ресурс. Это обязатель-
ный элемент.
• description. Необязательный элемент, содержащий описание совокуп-
ности ресурсов.
• url-pattern. Необязательный элемент, определяющий шаблон URL,
для которого должно применяться ограничение. Можетбыть нуль или
большее количество элементов url-pattern в элементе web-resource-
collection.
• http-method. Необязательный элемент, определяющий ограничивае-
мый метод. Можетбыть нуль или большее количество элементов http-
method в элементе web-resource-collection.
Элемент auth-constraint может иметь следующие подэлементы:
• description. Необязательный элемент, описывающий элемент auth-
constraint.
• role-name. Роли, которые имеют доступ к ограничиваемому ресурсу.
Это элемент является обязательным.
Элемент user-data-constraint может содержать элементы:
• description. Необязательный элемент, описывающий элемент user-
data-constraint.
Конфигурирование системы безопасности
405
Если пользователь зарегистрируется неудачно, диалоговое окно появится
снова. Если пользователь не сможет зарегистрироваться три раза, браузер вы-
ведет сообщение, показанное на рис. 14.3.
При успешной регистрации выводится ресурс и в ответе посылается мар-
кер аутентификации. После успешной регистрации запросы другого ресурса,
соответствующего шаблону, не будут требовать регистрации, так как пользо-
ватель уже имеет маркер аутентификации.
Если пользователь регистрируется правильно с помощью имени и пароля дру-
гой роли, браузер выведет другую страницу (см. рис. 14.4). Например, пользова-
тель может ввести «tomcat» в качестве имени и «tomcat» в качестве пароля (что
соответствует роли tomcat в файле tomcat-users.xml).
Рис. 14.2. Диалоговое окно Login
Рис. 14.3. НТТР-статус 401
402
Глава 14
Использование элемента login-config
Рассмотрим второй элемент конфигурации безопасности — login-config.
Прежде всего добавим еще одного пользователя в файл tomcat-users.xml.
Этот пользователь имеет имя «GM», пароль «lontong» и роль «manager». Файл
tomcat-users.xm1 представлен в листинге 14.2.
ht№//lxano*:ttM)mrAoo/Mrvlat/Tastina5*vtot
HTTP Status 500 - Configuration error: Cannot
perform access control without an authenticated
principal
*$
£
>1
<1
*3
Рис. 14.1. Неавторизованный пользователь пытается получить доступ
к ограничиваемой области
Листинг 14.2. Добавление нового пользователя в файл tomcat-users.xml
<tomcat-users>
<user name=,'GM" password="lontong” roles=”manager” />
<user name=”tomcat" password=’’tomcat" roles=”tomcat” />
<user name="role1” password=”tomcat’’ roles=”role1" />
<user name=,'both" password=”tomcat" roles=”tomcat, rolel" />
t </tomcat-users>
Как с помощью определенной роли пользователь получает доступ к ре-
сурсу? Каждый пользователь обязан зарегистрироваться, указав имя пользо-
вателя и пароль, присвоенный роли.
Каким образом обеспечивается регистрация пользователя? С помощью эле-
мента login-config.
('Примечание'} Когда пользователю предлагают ввести полномочия (имя
х । пользователя и пароль), выполняется аутентификация
пользователя. Методы аутентификации рассматриваются
ниже.
Конфигурирование системы безопасности
407
</auth-constraint>
</secu rity-const raint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>User Basic Authentication</realm-name>
</login-config>
</web-app>
Соответствующий файл tomcat-users.xml приведен в листинге 14.5.
Листинг 14.5 Файл tomcat-users.xml для этого примера
<tomcat-users>
<user name=”GM” password=”lontong" roles=”manager'’ />
<user name="tomcat'’ password="tomcat" roles="tomcat” />
<user name="role1" password=’’tomcat" roles=”role1” />
<user name=’’both’’ password=’’tomcat" roles="tomcatI rolel” />
</tomcat-users>
Теперь можно применять обе роли, manager и tomcat, для доступа к одному
ре :урсу. Отметим, что роль tomcat появляется во втором и четвертом пользо-
вательских элементах. Это означает, что можно также указывать имя «both» и
пароль «tomcat» для доступа к ресурсу.
Аутентификация на основе формы
Элемент auth-method в элементе login-config может принимать значение
FORM. Задание FORM в качестве значения auth-method говорит о том, что вы
не хотите использовать стандартную страницу Login браузера. Web-контейне-
ру сообщается, что будет применяться собственная пользовательская страни-
ца. Это называется аутентификацией на основе формы.
Для применения аутентификации на основе формы необходимо подгото-
вить две страницы: Login и Error. Страница Login может быть статическим
файлом или динамическим ресурсом. Она выводится, когда пользователь пы-
тается получить доступ к ограниченному ресурсу. Будетли страница Login про-
стой или сложной, решает разработчик. Требования состоят в том, страница
Login должна содержать форму HTML со следующими реквизитами:
• Методом формы должен быть POST.
• Атрибут ACTION должен иметь значение «j_security_check».
• Форма должна содержать два элемента ввода с именами j_usemame и
j_password. В этих элементах пользователь вводит имя и пароль.
404
Глава 14
этого передаются хэш-значения паролей. Это защищает информацию от
злонамеренного подслушивания.
Методы базовой аутентификации и аутентификации на основе дайджеста
определены в RFC2617, который можно найти по адресу ftp://ttp.isi.edu/in-
notes/rfc2617.txt.
Если пересылаемые данные являются конфиденциальными, для их
шифрования необходимо использовать SSL. Дополнительную информацию о
SSL можно найти по адресу http://home.netscape.com/eng/ss13/3-SPEC.HTM.
Рассмотрим дескриптор развертывания, который требует регистрации
пользователя (см. листинг 14.3).
Листинг 14.3 Дескриптор развертывания, требующий регистрации
пользователя
<?xml version="1.0" encoding='TS0-8859-1 ”?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j 2ee/dtds/web-app_2_3.dtd">
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>User Basic Authentication</realm-name>
</login-config>
</web-app>
Здесь показан принцип действия элемента login-config. Используется ме
тод аутентификации BASIC. Теперь каждый раз, когда пользователь обраща
ется к ресурсу с помощью URL, соответствующего шаблону, web-браузер вы
водит диалоговое окно, приведенное на рис. 14.2.
Пользователь может ввести имя и пароль для роли manager. Имя пользова
теля должно быть «GM», а пароль — «lontong».
Конфигурирование системы безопасности
409
Листинг 14.7 Login.html
<HTML>
<HEAD>
cTITLE>Login Pagec/TITLE>
</HEAD>
<BODY> *
<CENTER>
cH2>Please enter your user name and passwordc/H2>
<FORM ACTION=”j_security_check" METH0D=”P0ST”>
<TABLE>
<TR>
<TD>User name:</TD>
CTDXINPUT TYPE=TEXT NAME=”j_username”x/TD>
</TR>
<TR>
<TD>Password:</TD>
CTDXINPUT TYPE=PASSWORD NAME="j j)assword"X/TD>
c/TR>
CTR>
CTDXINPUT TYPE=RESETX/TD>
CTDXINPUT TYPE=SUBMIT VALUE=,'Login”>c/TD>
c/TR>
c/TABLE>
c/F0RM>
c/B0DY>
c/HTML>
Листинг 14.8 Error.html
CHTML>
CHEAD>
cTITLE>Error Pagec/TITLE>
C/HEAD>
CBODY>
Login failed. Click ca HREF=”Login.htmr’>herec/A> to try again.
c/B0DY>
c/HTML>
Когда пользователь пытается получить доступ к ресурсу, URL которого со-
ответствует шаблону, определенному в дескрипторе развертывания, браузер
выводит страницу Login, показанную на рис. 14.5.
406
Глава 14
th* credentials you supplied.
If you believe you should b* *bl* to view this directory or pogo,
please try to contact the Web site by using any **maH address or
phon* number that may bo listed on th* IxiialliiSShome
page.
You con dick Scorch to look for information on the Internet.
HTTP error 403 - Forbidden
Internet Explorer
Рис. 14.4. Использование неверной роли
Использование нескольких ролей
Можно определить более одной роли для доступа к ресурсу. Например, листинг
14.4 задает дескриптор развертывания, который предоставляет двум ролям дос-
туп к определенному ресурсу.
Л истин г 14.4 Предоставление доступа к ресурсу нескольким ролям
<?xml version="1.0" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd”>
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
<role-name>tomcat</role-name>
Конфигурирование системы безопасности
411
дайджеста состоит в том, что при использовании метода аутентификации
DIGEST не определяется область (realm).
Следующий пример демонстрирует применение аутентификации на осно-
ве дайджеста. Дескриптор развертывания представлен в листинге 14.9.
Листинг 14.9 Дескриптор развертывания, который использует метод
аутентификации на основе дайджеста
<?xml version=’’1.0" encoding=’TS0-8859-T’?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
”http: //j ava. sun. com/j 2ee/dtds/web-app_2_3. dtd’’>
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
<role-name>tomcat</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>DIGEST</auth-method>
</login-config>
</web-app>
Когда пользователь пытается получить доступ к ограниченному ресурсу,
выводится диалоговое окно, показанное на рис. 14.7.
Отметим, что это диалоговое окно очень похоже на используемое в методе
базовой аутентификации.
Методы, связанные с системой
безопасности
Хотя конфигурирование дескриптора развертывания и определение ролей в
файле tomcat-users.xml означают, что не требуется ничего программировать, все
же некоторое кодирование иногда является неизбежным. Например, может
понадобиться записать всех пользователей, которые будут регистрироваться.
408
Глава 14
Кроме того, требуется страница Error, которая также может быть статичес-
кой страницей или динамическим ресурсом. Эта страница выводится в случае
неудачной регистрации пользователя. Отметим, что web-контейнер будет вы-
водить страницу Error при первой неудачной попытке регистрации.
Приведем пример аутентификации на основе формы. Дескриптор развер-
тывания показан в листинге 14.6.
Листинг 14.6 Дескриптор развертывания для аутентификации на основе
формы
<?xml version=”1.О" encoding=”IS0-8859-1”?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
’’http: //j ava. sun. com/j 2ee/dtds/web-app_2_3. dtd ’’>
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
<role-name>tomcat</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/Login.html</form-login-page>
<form-error-page>/Error.html</form-error-page>
</form-login-config>
</login-config>
</web-app>
Элемент form-login-config определяет URL страницы Login и страницы
Error. Дескриптор развертывания устанавливает, что страница Login называ-
ется Login.html и находится в каталоге application. Дескриптор также сообща-
ет web-контейнеру, что страница Error для этой аутентификации на основе
формы называется Error.html и также находится в каталоге application.
Листинги 14.7 и 14.8 представляют страницы Login и Error соответственно.
Конфигурирование системы безопасности
413
При выполнении этого кода должно появиться что-то похожее на рис. 14.8.
Рис. 14.7. Диалоговое окно, выводимое при аутентификации
на основе дайджеста
Рис. 14.8. Определение, кем является пользователь
Ограничение отдельных методов
Предыдущие примеры ограничивают доступ к ресурсу независимо от того,
какой метод HTTP используется для запроса ресурса. Можно выбрать методы
HTTP, которые необходимо ограничить. Например, может понадобиться ог-
раничить доступ для запроса, который отправлен с помощью метода GET, но
разрешить доступ, если пользователь применяет метод POST.
Дескриптор развертывания влистинге 14.11 ограничивает метод GET. Если
страница запрашивается с помощью других методов, таких как POST, истре-
буется никакая аутентификация.
410
Глава 14
Рис. 14.5. Страница Login
Если регистрация оказывается неуспешной, выводится страница Error (см.
рис. 14.6).
Рис. 14.6. Страница Error
В случае успешной регистрации выводится запрошенная страница.
Аутентификация на основе дайджеста
Применение аутентификации на основе дайджеста не требует больших измене-
ний в дескрипторе развертывания. Основное различие между базовой аутенти-
фикацией, аутентификацией на основе формы и аутентификацией на основе
15
Кэширование
бычно данные хранятся в реляционной базе данных. Однако соедине-
ние с базой данных является одной из наиболее дорогих операций, выполня-
емых в web-приложении. Поэтому, если можно сократить количество соеди-
нений с базой данных, то приложение будет более быстрым и масштабируемым.
Многие стараются избежать применения базы данных, сохраняя свои данные
в текстовых файлах или в файлах XML, но это решение не всегда приемлемо.
Кроме того, если приложение использует базу данных, то применение XML
потребует изменения в структуре данных и соответственно в архитектуре при-
ложения.
Эта глава представляет два решения проблемы доступа к базе данных: кэ-
ширование данных в текстовом файле и кэширование данных в оперативной
памяти.
Первое решение записывает часто используемые, но редко изменяющиеся
данные, в текстовые файлы. Пространство жесткого диска сегодня очень де-
шево, и наличие легко читаемой копии данных не создает никаких проблем.
Когда приложению требуются данные из базы данных, то вместо обращения к
серверу базы данных приложение может использовать текстовый файл. Если
только сервер базы данных не кэширует данные в своей оперативной памяти,
то встраивание текстового файла является операцией, которая, вероятно, в
тысячу раз быстрее, чем установка соединения с базой данных.
При вставке текстового файла можно использовать средство include web-
сервера, которое обычно доступно при применении файла .shtml или .shtm.
412
Глава 14
Интерфейс javax.servlet.http.HttpServletRequest предоставляет несколько мето-
дов, которые позволяют получить доступ к отдельным фрагментам информа-
ции регистрации пользователей. Это методы getAuthType, isUserlnRole,
getPrincipal и get Remote User.
public String getAuthType()
Этот метод возвращает название используемой для защиты сервлета схемы
аутентификации. Возвращаемым значением может быть: BAS1C_AUTH,
FORM_AUTH,CLIENT_CERT_AUTH или DIGEST_AUTH. Метод возвраща-
ет null, если запрос не был аутентифицирован.
public boolean isUser!nRole(String role)
Этот метод сообщает, включен ли аутентифицированный пользователь в
указанную логическую роль. Если пользователь не был аутентифицирован,
метод возвращает false.
public java.security.Principal getUserPrincipalO
Этот метод возвращает объект java.security. Principal, содержащий имя теку-
щего аутентифицированного пользователя. Если пользователь не был аутен-
тифицирован, метод возвращает null.
public String getRemoteUserO
Возвращает login пользователя, делающего запрос, если пользователь был
аутентифицирован, и null, если пользователь не аутентифицирован. Будет ли
имя пользователя посылаться с каждым последующим запросом, зависит от
браузера и типа аутентификации.
Код JSP листинга 14.10 демонстрирует использование различных методов.
Л исти н г 14.10 Применение методов HttpServletRequest для получения
имени пользователя
<%
out.printin(”Auth Type:” + request.getAuthTypeO);
out.println("<BR>User Principal:” + request.getUserPrincipalO);
out.println(”<BR>Remote User:” + request. getRemoteUserO);
if (request.isUser!nRole("tomcat”))
out.println(”<BR>User in role");
else
out.println("<BR>User not in role”);
%>
Кэширование
417
</SELECT>
<INPUT TYPE=SUBMIT VALUE=Search>
</FORM>
В web-браузере код листинга 15.1 покажет картинку, аналогичную рис. 15.1.
Рис. 15.1. Средство поиска в онлайновом магазине
В случае применения JSP на серверной стороне будет использоваться код,
представленный в листинге 15.2.
Листинг 15.2 Извлечение категорий без кэширования
<%@ page import="java.sql.*" %>
<%!
public void jspInitO {
try {
Class.forName("sun.j dbc.odbc.JdbcOdbcDriver");
}
catch (ClassNotFoundException e) {
}
>
%>
<FORM METHOD=POST ACTION=Search.jsp>
Keyword: <INPUT TYPE=TEXT Name=Keyword><BR>
Search In: <SELECT Name=Category>
<%
//Открываем таблицу Categories
414
Глава 14
Л исти нг 14.11 Ограничение определенного метода
<?xml version=’’1.0’’ encoding=”IS0-8859-1”?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
’’http: //j ava. sun. com/j 2ee/dtds/web-app_2_3. dtd ’’>
<web-app>
<security-constraint>
<web-resource-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
<role-name>tomcat</role-name>
</auth-constraint>
</secu rity-const raint>
<login-config>
<auth-method>DIGEST</auth-method>
</login-config>
</web-app>
Заключение
В этой главе говорилось о том, как сконфигурировать дескриптор развертыва-
ния, чтобы web-контейнер ограничил доступ к некоторым или ко всем ресурсам.
Конфигурирование означает, что необходимо только изменить файл дескрипто-
ра развертывания — никакое программирование не требуется.
Если необходим доступ к информации регистрации пользователя, можно
применить следующие методы интерфейса javax.servlet.http. HttpServletRequest:
get Remote User, get Principal, getAuthType и isUserlnRole.
Кэширование
419
<%@ include file="Categories.txt" %>
</SELECT>
CINPUT TYPE=SUBMIT VALUE=Search>
</FORM>
Более того, можно вообще не применять страницу JSP. Можно воспользо-
ваться для этой страницы расширением .shtml, если web-сервер поддерживает
это. Однако при этом приходится полагаться на сам web-сервер, и необходимо
обратиться к документации, которая поставляется с web-сервером для получе-
ния дополнительной информации.
В случае кэширования данных о продуктах в текстовых файлах разумным
подходом является копирование данных о каждом продукте в текстовый файл и
использование идентификатора продукта в качестве имени файла. Но как быть,
если имеются тысячи или миллионы продуктов? Количество не имеет здесь зна-
чения, так как если клиенты имеют миллионы продуктов для продажи, то, ско-
рее всего, они могут позволить себе купить высокоскоростной жесткий диск.
Недостатком этого метода является проблема поддержки. Элементы дан-
ных, кэшированные в текстовых файлах, изменяются редко, но все же изме-
няются. Поэтому необходим способ обновления копии в текстовом файле.
Кроме того, когда кэшированные данные удаляются, нужно также удалить
копию в текстовом файле.
Решение проблемы сопровождения состоит в модификации администра-
тивных страниц таким образом, чтобы при изменении в базе данных копия в
текстовом файле также изменялась. Например, если категория продукта кэ-
ширована в файле, то при добавлении в базу данных новой категории продук-
та файл categories.txt должен быть обновлен.
Код листинга 15.5 представляет страницу Category Admin, которая изменяет
копию категорий продуктов в текстовом файле. Страница показана на рис. 15.2.
Л истин г 15.5 Страница администрирования категорий
<%@ page import=”java.sql.*" %>
<%@ раде import="java.io.*" %>
<%!
public void jspInitO {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch (ClassNotFoundException e) {
}
}
%>
<HTML>
<HEAD>
416
Глава 15
Эта техника является, конечно, значительно быстрее, чем обработка ана-
логичного файла JSP, так как при использовании файлов JSP требуется как
минимум один дополнительный шаг — обработка передачи от web-сервера в
контейнер JSP.
Однако это решение имеет свои проблемы: сопровождение. Когда данные в
базе данных изменяются, их копия в текстовом файле также должна меняться.
Второй метод, кэширование данных в оперативной памяти, еще быстрее.
Однако сервер обычно имеет ограниченный объем оперативной памяти, по-
этому в памяти кэшируются только избранные данные.
В настоящей главе показано, как использовать эти два метода для улучше-
ния производительности приложения.
Кэширование данных в текстовом файле
Какие данные могут кэшироваться втекстовом файле? Практически любые —
если они удовлетворяют следующим критериям:
• Часто используются.
• Не являются конфиденциальными, как имя пользователя и пароль.
• Изменяются редко.
Можно определить большой объем данных. Элементами данных могут быть
категории продуктов в онлайновом магазине, данные по каждому продукту,
предметы онлайнового аукциона и т. д.
Рассмотрим пример онлайнового магазина. Категории продуктов изменя-
ются редко — может быть, раз в год. Однако категории продуктов извлекаются
из базы данных всякий раз, когда пользователь посещает онлайновый магазин,
так как необходимо перечислить все категории в окне выбора Search In. (Пред-
полагается, что на сайте имеется средство поиска.) Сколько раз приложение
должно обращаться к базе данных для извлечения одних и тех же данных?
Код листинга 15.1 является фрагментом типичного кода HTML, обеспечи-
вающего поиск в онлайновом магазине компьютерных книг.
Л исти н г 15.1 Код HTML для средства поиска в онлайновом магазине
<FORM METHOD=POST ACTION=Search.jsp>
Keyword: <INPUT TYPE=TEXT Name=Keyword><BR>
Search In:
<SELECT Name=Category>
COPTION VALUE=1>Programmingc/0PTI0N>
COPTION VALUE=2>0perating Systemc/0PTI0N>
COPTION VALUE=3>Databasec/OPTION>
COPTION VALUE=4>Certificationc/0PTI0N>
Кэширование
421
s.executeUpdate(sql);
}
sql = "SELECT CategorylD, CategoryName FROM Categories ORDER BY CategoryName
ASC";
rs = s.executeQuery(sql);
// Новый список всех категорий плюс кнопки DELETE и UPDATE
out.р ri nt1п("<TABLE>");
while (rs.nextO) {
id = rs.getString(l);
category = rs.getString(2);
%>
<TR>
<TD>
<FORM METH0D=P0ST>
<INPUT TYPE=HIDDEN NAME=id VALUE="<%=id%>">
<INPUT TYPE=HIDDEN NAME=action VALUE="update">
<INPUT TYPE=TEXT NAME=category VALUE="<%=category%>">
<INPUT TYPE=SUBMIT VALUE=Update>
</FORM>
</TD>
<TD>
<FORM METH0D=P0ST>
<INPUJ TYPE=HIDDEN NAME=id VALUE="<%=id%>">
<INPUT TYPE=HIDDEN NAME=action VALUE="delete”>
<INPUT TYPE=SUBMIT VALUE=Delete>
</FORM>
</TD>
</TR>
<%
categories.append("<0PTI0N VALUE=").append(id).append(">");
categories.append(category).append("</OPTION>\n");
}
out.println("</TABLE>");
s.close();
con.close();
//Создать текстовый файл с именем Categories.txt в указанной папке
String path = "C:\\123data\\";
FileWriter fw = new FileWriter(path + "categories.txt”);
fw.write(categories.toString());
fw.closeO;
}
418
Глава 15
Листинг 15.2 Продолжение
try {
String sql = "SELECT * FROM Categories";
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
out.print("<OPTION VALUE-');
out.print(rs.getString(l));
out.print(">");
out.print(rs.getString(2));
out.print("</OPTION>\n");
}
s.close();
con.closeO;
}
catch (SQLException e) {
out. println(e. toStringO);
}
catch (Exception e) {
out. println(e. toStringO);
}
%>
</SELECT>
CINPUT TYPE=SUBMIT VALUE=Search>
</FORM>
Если поместить категории в текстовый файл с именем Categories.txt (см.
листинг 15.3), то не нужно будет обращаться к базе данных для вывода катего-
рий в окне выбора Search In.
Листинг 15.3 Файл Categories.txt
<OPTION VALUE=1>Programming</0PTI0N>
COPTION VALUE=2>0perating System</0PTI0N>
cQPTION VALUE=3>Database</0PTI0N>
cQPTION VALUE=4>Certification</0PTI0N>
Код JSP значительно проще (см. листинг 15.4).
Листинг 15.4 Код JSP при использовании текстового файла
CFORM METHOD=POST ACTION=Search.jsp>
Keyword: CINPUT TYPE=TEXT Name=Keyword><BR>
Search In: <SELECT Name=Category>
Кэширование
423
Код листинга 15.6 демонстрирует кэширование в оперативной памяти дан-
ных о продуктах из таблицы Products. Все продукты, помеченные как «hot»,
извлекаются из базы данных во время инициализации и сохраняются в объек-
те java.util.HashMap.
Листинг 15.6 Кэширование данных в памяти
<%@ page import="java.sql.*" %>
<%@ page import="java.util.HashMap" %>
<%!
HashMap products = new HashMap(50);
public void jsplnit() {
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver”);
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb”);
Statement s = con.createStatementO;
String sql = "SELECT Productld, ProductName, Description, Price" +
" FROM Products" + '
" WHERE Hot=True";
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
Product product = new ProductO;
String productld = rs.getStringC’Productld");
product.productld = productld;
product.productName = rs.getString("ProductName");
product.description = rs.getString("Description");
product.price = rs.getFloat("Price");
products.put(productld, product);
}
con.closeO;
}
catch (ClassNotFoundException e) {
}
catch (Exception e) {
}
}
class Product {
String productld;
String productName;
String description;
float price;
}
%>
420
Глава 15
Листинг 15.5 Продолжение
<TITLE>Product Category Admin Page</TITLE>
</HEAD>
<BODY>
<H1>Add, Update, Delete Categories</H1>
<BR><BR>
<B>Add New Category</B>
<BR>
<FORM METHOD=POST>
<INPUT TYPE=HIDDEN NAME=action VALUE=add>
<INPUT TYPE=TEXT NAME=category SIZE=14>
<INPUT TYPE=SUBMIT VALUE=Add>
</FORM>
<HR>
<BR>
<%
String id = request.getParameter("id");
String category = request.getParameter("category");
String action = request.getParameter("action");
String sql;
StringBuffer categories. = new StringBuffer(2048);
//Открыть таблицу Categories
try {
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
ResultSet rs;
if (action!=null && action.equals("add")) {
sql = "SELECT CategoryName FROM Categories WHERE CategoryName=’” +
category + ...;
rs = s.executeQuery(sql);
if (!rs.next()) {
sql = "INSERT INTO Categories (CategoryName) VALUES ('" + category + "’)".
s.executeUpdate(sql);
}
else
out.println(category + ” is already in the database");
}
else if (action!=null && action.equals("delete")) {
sql = "DELETE FROM Categories WHERE CategoryID=" + id;
s. executeUpdate(sql);
}
else if (action!=null && action.equals("update")) {
sql = "UPDATE Categories SET CategoryName=’" + category + ... +
" WHERE Categoryld=" + id;
Кэширование
425
catch (SQLException e) {
out. println(e. toStringO);
}
catch (Exception e) {
out. println(e. toStringO);
}
}
%>
<BR>Product Id: <%=productld%>
<BR>Product Name: <%=productName%>
<BR>Description: <%=description%>
<BR>Price: <%=price%>
<BR>
<%
long t2 = System.currentTimeMillisO;
out.println(”Processing time: ” + (t2 - t1));
%>
</B0DY>
</HTML>
Каждый продукт, данные о котором необходимо кэшировать, представлен
классом Product:
class Product {
String productld;
String productName;
String description;
float price;
}
Код инициализации написан в методе jsplnit страницы JSP. Этот код выпол-
няется только один раз, когда сервлет JSP загружается в оперативную память.
Метод jsplnit() загружает драйвер JDBC, открывает объект соединения и
извлекает все продукты, помеченные как «hot», из таблицы Products:
public void jsplnit() {
try {
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT Productld, ProductName, Description, Price" +
” FROM Products" +
" WHERE Hot=True";
ResultSet rs = s.executeQuery(sql);
422
Глава 15
Листинг 15.5 Продолжение
catch (SQLException е) {
out.println(e.toSt ring());
}
catch (Exception e) {
out. println(e. toStringO);
}
%>
</BODY>
</HTML>
Файл categories.txt обновляется каждый раз, когда администратор добавля-
ет, удаляет или обновляет категорию. Таким образом решается проблема под-
держки кэшированных в текстовом файле данных.
Переменной path должно присваиваться реальное
местоположение файла category.txt.
iQ^.K.SyWm МИМИ
Programming.ЯВИВкВВВ
|Web DesignМИИИИВШЯ
Рис. 15.2. Административная страница для добавления, обновления и
удаления категорий
Кэширование в оперативной памяти
Кэширование избранных данных в оперативной памяти предпочтительно, если
известно, какие данные запрашиваются наиболее часто. Эти данные загружа-
ются в память лишь однажды во время инициализации сервлета. Последую-
щие запросы кэшированных данных будут обрабатываться быстрее.
Кэширование
427
Connection con = DriverManager.getConnection("jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT ProductName, Description, Price" +
" FROM Products" +
WHERE Productld=" + productld;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
productName = rs.getString("ProductName");
description = rs.getString("Description");
price = rs.getFloat("Price");
}
rs.closeO;
s.closeO;
con.closeO;
}
catch (SQLException e) {
out. println(e. toStringO);
}
catch (Exception e) {
out. println(e. toStringO);
}
}
Страница JSP показана на рис. 15.3.
Страница JSP определяет также две переменные — Ни t2. Они служат для
измерения времени, потребовавшегося для обработки страницы JSP. tl запи-
сывается в начале страницы, a t2 — по окончании страницы.
Пример показывает существенное улучшение производительности. Для
запроса кэшированного продукта t2 - tl равно 0. Это, конечно, означает
только, что время обработки слишком маленькое, чтобы его можно было
измерить.
Для запросов продуктов, не находящихся в кэше, t2 - tl находится в интер-
вале между 110 и 250 миллисекунд.
Кэширование данных в памяти действительно сокращает время ответа.
424
Глава 15
Листинг 15.6 Продолжение
<%
long t1 = System. currentTimeMillisO;
%>
<HTML>
<HEAD>
<TITLE>Product Details</TITLE>
</HEAD>
<BODY>
<%
String productld = request.getParameter("id");
//productld="2";
if (productld==null) {
out.println(’’The request did not carry a product identifier”);
return;
}
String productName = null;
String description = null;
float price = 0;
Product product = (Product) products.get(productld);
if (product!=null) {
productName = product.productName;
description = product.description;
price = product.price;
}
else {
// не найдено в кэше, извлекается из базы данных
try {
Connection con = DriverManager.getConnection(”jdbc:odbc:JavaWeb");
Statement s = con.createStatementO;
String sql = "SELECT ProductName, Description, Price" +
" FROM Products" +
" WHERE Productld=" + productld;
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
productName = rs.getString(”ProductName");
description = rs.getString("Description");
price = rs.getFloat("Price");
}
rs.closeO;
s.closeO;
con.close();
}
16
Развертывание
приложений
этой главе рассматривается процесс развертывания сервлета и прило-
жения JSP. Чтобы понять, как правильно разместить web-приложение, необ-
ходимо изучить структуру каталога приложения. Поэтому глава начинается с
обзора структуры каталога. Следующей темой является дескриптор разверты-
вания, где можно сконфигурировать каждое приложение. Наконец, вы узнае-
те, как создать псевдоним для сервлета и страницы JSP, а затем отобразить его
нс новый URL. Создав псевдоним, вы сможете вызывать приложение с помо-
щью более короткого или более предпочтительного URL.
Структура каталога приложения
При начальной установке Tomcat в его каталоге создается несколько подката-
логов (см. рис. 16.1).
В данном случае Tomcat установлен в каталоге с именем tomcat4. Этот ката-
лог называется также %CATALINA_HOME%. В приложении А можно найти
дополнительную информацию о функциях каждого подкаталога.
В этой главе нас будет интересовать один из подкаталогов, который созда-
ется при установке Tomcat: webapps. Каталог webapps является родительским
ка алогом каждого web-приложения, которое будет выполняться eTomcat. При
первоначальной установке Tomcat создается также ряд образцов приложений.
Одним из таких приложений является examples. На рис. 16.2 показан ката-
лог webapps, который включает в себя ряд приложений.
426
Глава 15
con.close();
}
catch (ClassNotFoundException e) {
}
catch (Exception e) {
}
}
После получения объекта ResultSet из метода executeQuery объекта Statement
создается экземпляр объекта Product, присваиваются значения каждому полю
в объекте Product, и объект Product добавляется в объект Hash Мар следующим
образом:
while (rs.nextO) {
Product product = new ProductO;
String productld = rs.getStringC’Productld’’);
product.productld = productld;
product.productName = rs.getString(”ProductName”);
product, description = rs.getString("Description’’);
product, price = rs.getFloat(”Price'’);
products.put(productld, product);
}
Идентификатор продукта используется в качестве ключа для каждого про-
дукта, добавляемого в Hash Мар.
Когда запрашивается продукт, страница JSP проверяет сначала, доступен
ли он в Hash Мар. Если да, то продукт извлекается из Hash Мар:
Product product = (Product) products.get(productld);
if (product!=null) {
productName = product.productName;
description = product.description;
price = product.price;
}
Если запрашиваемый продукт недоступен, необходимо установить соеди-
нение с базой данных и извлечь продукт из таблицы Products:
// не найдено в кэше, извлекается из базы данных
try {
Развертывание приложений
431
необходимо создать структуру каталогов, соответствующую пакету Java. На рис.
16,2 показана структура каталогов в каталоге classes для сервлетов, которые
принадлежат пакету com.newriders.
Каталог lib в каталоге WEB-INF будет полезен, если используются библио-
течные файлы, такие как файлы .jar. Скопированные сюда библиотечные файлы
будут доступны всем ресурсам в приложении. Если библиотека должна быть
доступна более чем одному приложению, можно поместить ее в каталог lib
каталога %CATALI NА_НОМ Е%.
Если для страниц JSP применяются библиотеки пользовательских тегов,
то файлы TLD также должны помещаться в каталог WEB-INF или в его под-
каталог. На рис. 16.2 создан каталог с именем tld для хранения всех файлов
TLD. JavaBeans и файлы классов компонентов пользовательских тегов хранятся
в каталоге classes внутри каталога WEB-INF.
Дескриптор развертывания
Дескриптор развертывания является документом XML, который содержит ин-
формацию, описывающую определенный сервлет или приложение JSP. Не-
которые из элементов дескриптора развертывания связаны с сервлетами, а
некоторые — с приложениями JSP.
Типичный дескриптор развертывания начинается со следующего заголовка:
<?xml version=”1.0” emcoding=’’IS0-8859-1" ?>
Этот заголовок определяет версию XML и используемую кодировку. Вслед
за заголовком идет объявление DOCTYPE:
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inv.//DTD Web Application 2.3//EN’’
” http: //j ava. sun. com/dtd/web-app_2_3. dtd’’>
Этот код задает определение типа документа (DTD), согласно которому
будет проверяться допустимость документа ХМ L. Элемент <!DOCTYPE> имеет
несколько атрибутов.
Из элемента <!DOCTYPE> можно получить следующую информацию:
• web-app определяет корневой элемент документа (дескриптора раз-
вертывания, а не файла DTD).
• PUBLIC означает, что файл DTD предназначен для публичного ис-
пользования.
• "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" означает,
что DTD поддерживается компанией Sun Microsystems, Inc. Тип до-
кумента определен как DTD Web Application 2.3, и DTD написан на
английском языке.
428
Глава 15
ht»://toc^hort:8QBQ/grjProdunO«tt*r.te>?l^l
Product Id: 1
Product Name: Dairy Queen Chocolate
Detcriptioa: 250g, with rotated almond
Price: 1.35
Proceisng tone: 0
жшемтеа
Рис. 15.3. Кэширование данных в оперативной памяти
Заключение
Кэширование данных является популярным методом, который используется во
многих web-сайтах. Кэширование улучшает масштабируемость web-приложе-
ния, так как сокращает количество трудоемких операций, связанных с базой
данных. В этой главе было показано, как можно кэшировать данные в тексто-
вых файлах и в оперативной памяти.
Развертывание приложений
433
("Примечание^ Подэлементы должны появляться в определенном порядке.
Например, если элемент web-app в дескрипторе
развертывания имеет подэлементы servlet и servlet-mapping,
то подэлемент servlet должен идти перед подэлементом
servlet-mapping.
Дескриптор развертывания обычно выглядит следующим образом:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j 2ee/dtds/web-app_2_3.dtd">
<web-app>
<element-1>
<subelement-1>value of subelement-1 of element-1</subelement-1>
<subelement-2>value of subelement-2 of element-1</subelement-2>
</element-1>
<element-2>
<subelement-1>value of subelement-1 of element-2</subelement-1>
<subelement-2>value of subelement-2 of element-2</subelement-2>
</element-2>
</element-n>
<subelement-1>value of subelement-1 of element-n</subelement-1>
<subelement-2>value of subelement-2 of element-n</subelement-2>
</element-n>
</web-app>
Приведем пример дескриптора развертывания сервлетного приложения:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j 2ee/dtds/web-app_2_3.dtd">
430
Глава 16
Рассмотрим приложение myWebApp, которое представляет собой типич-
ное web-приложение. Каталог myWebApp называется каталогом приложения.
В каталоге приложения содержится каталог с именем WEB-INF, который
имеет специальное значение в web-приложении. Есть еще два каталога в ката-
логе myWebApp: images и jsp. Это необязательные каталоги. В каталоге jsp хра-
нятся все страницы JSP. Можно располагать их непосредственно в каталогах
приложений; однако размещение всех страниц JSP в отдельных каталогах яв-
ляется хорошей практикой. Каталог image используется для хранения файлов
изображений. Этот каталог не и грает существен ной роли, за исключением того,
что делает приложение более организованным.
tomcat4
bin
examples
common
conf
jasper
lib
logs
server
src
webapps
work
Рис. 16.1. Структура каталогов Tomcat
n-
ROOT
Рис. 16.2. Приложение myWebApp
Если web-приложение использует некоторые статические файлы, напри-
мер файлы HTML, их также помещают в каталог приложения. С целью боль-
шей организованности можно создать подкаталог в каталоге приложения для
объединения таких файлов. В реальных приложениях наличие каталога с име-
нем html для всех файлов HTML не является необычным.
Как упоминалось, каталог WEB-INF имеет специальное назначение. Этот
каталог не виден в клиентском web-браузере, и все, что в нем хранится, также
скрыто от клиента. Прежде всего в WEB-INF помещают дескриптор развер-
тывания (файл web.xml). Использование дескриптора развертывания являет-
ся необязательным, но с его помощью можно делать многие полезные вещи.
Если используется какой-либо сервлет, то необходимо создать каталог с
именем classes в каталоге WEB-INF. Сервлеты, которые не принадлежат паке-
ту, должны копироваться в каталог classes. Для сервлетов, входящих в пакет,
Развертывание приложений
435
<?xml vesion="1.0" encoding=’’IS0-8859-1’’?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN’’
’’http://java.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<icon>
<small-icon>/CompanyLogo.jpg</small-icon>
<large-icon>/CompanyBigLogo.jpg</large-icon>
</icon>
</web-app>
display-name
Элемент display-name содержит имя, которое будет выводиться инструмен-
тарием XML, если такой инструментарий используется для редактирования
дескриптора развертывания. Элемент display-name описывается следующим
образом: <!ELEMENT display-name (#PCDATA)>.
Приведем пример дескриптора развертывания, который имеет элементы
icon и display-name:
<?xml vesion="1.0” encoding=’’IS0-8859-1’’?>
<! DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http: //j ava. sun. com/dtd/web-app_2_3. dtd’’>
<web-app>
<icon>
<small-icon>/CompanyLogo.jpg</small-icon>
<large-icon>/CompanyBigLogo.jpg</large-icon>
</icon>
<display-name>Superposition Marketing</display-name>
</web-app>
description
Элемент description служит для предоставления информации о дескрипто-
ре развертывания. Значение элемента description может использоваться инст-
рументарием XML. Элемент description имеет следующее описание:
<! ELEMENT description (#PCDATA)>
432
Глава 16
URL ’’http://java.sun.com/dtd/web-app_2_3.dtd’'> представляет мес-
тоположение файла DTD.
Предупреждение! Отметим, что местоположение файла, указанное в
1 предыдущем списке, является новым адресом. Раньше
для файла DTD использовалось http://java.sun.com/
j2ee/dtds/web-app_2_3.dtd.
Корневым элементом дескриптора развертывания является web-app. Этот
элемент может иметь до 23 видов подэлементов, которые все являются необя-
зательными. Некоторые подэлементы могут появляться только один раз, дру-
гие — несколько раз. Кроме того, некоторые из подэлементов сами могут иметь
подэлементы.
(^Примечанием Для тех, кто не очень хорошо знаком с XML, разъясним
некоторые обозначения, что поможет понять последующее
обсуждение DTD:
х+ один или несколько экземпляров х
х* ни одного или несколько экземпляров х
х? необязательный экземпляр х
х,у х за которым следует у
х|у ХИЛИ у
PCDATA = проанализированные символьные данные.
Проанализированные символьные данные являются текстом,
который не содержит разметки, а только простые
символьные данные.
Согласно файлу DTD, синтаксис подэлементов элемента web-app кратко
описывается следующим образом:
<!ELEMENT web-app (icon?, display-name?, description?, distributable?,
^context-param*, filter*, filter-mapping*, listener*, servlet*,
^servlet-mapping*, session-config?,mime-mapping*, welcome-file-list?,
4>error-page*, taglib*, resource-env-ref*, resource-ref*,
^security-constraint*, login-config?, security-role*,env-entry*,
*>ejb-ref*, ejb-local-ref*)>
Знак вопроса (?) указывает, что подэлемент является необязательным и
может появиться только один раз. Звездочка (♦) используется для определе-
ния подэлементов, которые могут появиться более одного раза в дескрипторе
развертывания. Все подэлементы являются необязательными.
За объявлением этого элемента web-app следуют объявления всех подэле-
ментов. Ниже рассматривается функциональность каждого элемента.
Развертывание приложений
437
<!ELEMENT context-param (param-name, param-value, description?)>
<! ELEMENT param-name (#PCDATA)>
<’ ELEMENT param-value (#PCDATA)>
<!ELEMENT description (#PCDATA)>
Подэлемент param-name содержит имя параметра, а подэлемент param-
value — значение параметра. При желании можно задать подэлемент description
для описания параметра.
Ниже представлен действительный дескриптор развертывания с несколь-
кими элементами context-param:
<?xml vesion="1.0" encoding="ISO-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<context-param>
<param-name>adminName</param-name>
<param-value>Tommy Matena</param-value>
</context-param>
<context-param>
<param-name>initValue</param-name>
<param-value>8086</param-value>
<description>the port number used</description>
</context-param>
</web-app>
filter
Этот элемент определяет фильтр в web-приложении. Фильтр отображается
либо на сервлет, либо на шаблон URL с помощью элемента filter-mapping (см.
ниже). Элемент filter и элемент filter-mapping, который выполняет отображе-
ние фильтра, должны иметь одинаковое имя. Фильтры обсуждаются в главе 7.
Элемент filter имеет следующее описание:
<! ELEMENT filter (icon?, filter-name, display-name?, description?,
filter-class, init-param*)>
<! ELEMENT filter-name (#PCDATA)>
<! ELEMENT filter-class (#PCDATA)>
Элементы icon, display-name и description описаны в предыдущих разделах,
init-param имеет такой же синтаксис, что и context-param.
434
Глава 16
<web-app>
<servlet>
<servlet-name>HttpRequestDemo</servlet-name>
<servlet-class>HttpRequestDemoServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Primitive</servlet-name>
<servlet-class>PrimitiveServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>ConfigDemo</servlet-name>
<servlet-class>ConfigDemoServlet</servlet-class>
<init-param>
<param-name>adminEmail</param-name>
<param-value>admin@rainysoftware.com</param-value>
</init-param>
<init-param>
<param-name>adminContactNumber</param-name>
<param-value>0414371237</param-value>
</init-param>
</servlet>
</web-app>
СПримечание^ Дескриптор развертывания, подобно другим документам
J XML, может содержать комментарии. Комментарий в XML
заключается в теги <!— и —>.
Подэлементы рассматриваются в последующих разделах.
icon
Элемент icon содержит необязательный подэлемент small-icon и необяза-
тельный подэлемент large-icon. Описание элемента имеет следующий вид:
<!ELEMENT icon (small-icon?, large-icon?)>
<! ELEMENT small-icon (#PCDATA)>
<!ELEMENT large-icon (#PCDATA)>
Элемент icon используется для указания имен файлов маленького (16x16) и/
или большого (32x32) значка в формате GIF или JPEG. В качестве имени файла
задается путь доступа относительно корня архива web-приложения (WAR).
Значок может использоваться инструментарием XML, применяемым для
редактирования дескриптора развертывания. Web-контейнер не использует
этот элемент.
Приведем пример дескриптора развертывания с элементом icon. Элемент
icon имеет маленький и большой значки:
Развертывание приложений
439
Дескриптор элемента filter-mapping:
<! ELEMENT filter-mapping (filter-name, (url-pattern | servlet-name))>
<! ELEMENT filter-name (#PCDATA)>
<! ELEMENT url-pattern (#PCDATA)>
<!ELEMENT servlet-name (#PCDATA)>
Значение filter-name должно соответствовать одному из имен фильтров,
объявленных в элементах filter.
Приведем пример дескриптора развертывания, который содержит два эле-
мента filter-mapping:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://java.sun.com/dtd/web-app_2_3.dtd">
4
<web-app>
<•- Определение фильтров, отображаемых на сервлеты и пути доступа ->
<filter>
<filter-name>
Basic Filter
</filter-name>
<filter-class>
BasicFilter
</filter-class>
</filter>
<filter>
<filter-name>
Advanced Filter
</filter-name>
<filter-class>
AdvancedFilter
</filter-class>
</filter>
<!- Определение отображений фильтров ->
<filter-mapping>
<filter-name>
Basic Filter
</filter-name>
<servlet-name>
FilteredServlet
</servlet-name>
</filter-mapping>
<filter-mapping>
436
Глава 16
Приведем пример дескриптора развертывания, использующего элемент
description:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Superposition Marketing</display-name>
<description>This application tracks statistical changes</description>
</web-app>
distributable
Если элемент distributable присутствует в дескрипторе развертывания, то
он указывает, что приложение написано для развертывания в распределенном
web-контейнере.,
Синтаксис этого элемента:
<!ELEMENT distributable EMPTY>
Например, ниже представлен дескриптор развертывания, который содер-
жит элемент distributable:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
’’http://java.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<display-name>Superposition Marketing</display-name>
<distributable/>
</web-app>
context-param
Элемент context-param содержит пару «имя параметра/значение», которая
используется в качестве параметра инициализации контекста сервлета прило-
жения. Имя параметра должно быть уникальным внутри web-приложения.
Синтаксис элемента context-param и его подэлементов:
Развертывание приложений
441
<!ELEMENT servlet-name (#PCDATA)>
<!ELEMENT servlet-class (#PCDATA)>
<! ELEMENT jsp-file (#PCDATA)>
<!ELEMENT init-param (param-name, param-value, description?)>
<!ELEMENT load-on-startup (#PCDATA)>
<!ELEMENT run-as (description?, role-name)>
<! ELEMENT role-name (#PCDATA)>
Элементы icon, display-name и description описаны в предыдущих разделах.
Дескриптор элемента init-param является таким же, что и у context-param.
Элемент servlet должен содержать элемент servlet-name и элемент servlet-
class или элемент servlet-name и элемент jsp-file. Элемент servlet-name определяет
имя сервлета, которое должно быть уникальным в приложении.
Элемент servlet-class указывает полностью квалифицированное имя класса
сервлета.
Элемент jsp-file задает полный путь доступа к файлу JSP в приложении.
Полный путь доступа должен начинаться с символа/.
Подэлемент init-param используется для передачи имени и начального зна-
чения параметра сервлету.
Элемент load-on-startup служит для автоматической загрузки сервлета в
память при запуске web-контейнера. Загрузка сервлета означает создание эк-
земпляра сервлета и вызов его метода init. Элемент load-on-startup позволяет
избежать задержки ответа на первый запрос сервлета, вызываемой загрузкой
сервлета в память. Если присутствует этот элемент и определен элемент jsp-
file, файл JSP предварительно компилируется в сервлет и полученный сервлет
загружается.
Значение load-on-startup может быть либо пустым, либо целым числом.
Значение указывает порядок загрузки в память web-контейнером. Например,
если имеются два элемента servlet и оба они содержат подэлементы load-on-
startup, то первым загружается сервлете меньшим значением подэлемента load-
on-startup. Если значение load-on-startup является пустым или отрицательным,
то решение о том, когда загружать сервлет, принимает web-контейнер. Если
два сервлета имеют одинаковые значения подэлемента load-on-starup, web-
контейнер может сам решить, какой из них загрузить первым.
Если задан элемент run-as, то он переопределяет идентификационные данные
безопасности, используемые для вызова Enterprise Java Bean этим сервлетом в дан-
ном web-приложении, role-name является одной из ролей безопасности, опреде-
ленной для текущего web-приложения.
Элемент security-role-ref определяет соответствие между именем роли, вызван-
ной из сервлета с помощью isUser!nRole(String name), и именем роли безопасно-
сти, определенной для web-приложения. Элемент security-role-ref описывается
следующим образом:
438
Глава 16
Элемент filter-name определяет имя фильтра. Имя фильтра должно быть
уникальным в приложении. Элемент filter-class задает полностью квалифици-
рованное имя класса фильтра.
Ниже следует действительный дескриптор развертывания для web-прило-
жения, которое использует два фильтра: Uppercase Filter и DoubleFilter.
<?xml vesion="1.О" encoding=”IS0-8859-1"?>
<» DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>
Uppercase Filter
</filter-name>
<filter-class>
UpperCaseFilter
</filter-class>
</filter>
<filter-name>
Double Filter
</filter-name>
<filter-class>
DoubleFilter
</filter-class>
<init-param>
<param-name>frequency</param-name>
<param-value>1909</param-value>
</init-param>
</filter>
</web-app>
filter-mapping
Элемент filter-mapping объявляет отображения фильтров в web-приложении.
Фильтр может отображаться либо на сервлет, либо на шаблон URL. Отобра-
жение фильтра на сервлет заставляет фильтр работать на сервлете. При ото-
бражении фильтра на шаблон URL фильтрация будет применяться к любому
ресурсу, URL которого соответствует шаблону URL. Фильтрация выполняет-
ся в том порядке, в каком появляются элементы filter-mapping в дескрипторе
развертывания.
Развертывание приложений
443
</servlet-name>
<servlet-class>
com.newriders.db.JDBCServlet
</servlet-class>
<load-on-startup>
2
</load-on-startup>
</servlet>
</web-app>
servlet-mapping
Элемент servlet-mapping отображает шаблон URL на сервлет. Дескриптор
элемента servlet-mapping имеет вид:
<!ELEMENT servlet-mapping (servlet-name, url-pattern)>
<» ELEMENT servlet-name (#PCDATA)>
<! ELEMENT url-pattern (#PCDATA)>
Ниже приводится дескриптор развертывания, который отображает серв-
лет с помощью шаблона URL.
<?xml vesion="1.0" encoding="IS0-8859-T?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"htt p://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<se rvlet-name>AnAlias</se rvlet-name>
<servlet-class>com.newriders.OtherServlet</servlet-class>
</servlet>
<!- отображение сервлета ->
<servlet-mapping>
<servlet-name>AnAlias</servlet-name>
<url-pattern>newURL<url-pattern>
<servlet-mapping>
</web-app>
session-config
Элемент session-config определяет параметры объектов javax.servlet.http.
HttpSession в web-приложении. Дескриптор элемента имеет вид:
440
Глава 16
<filter-name>
Advanced Filter
</filter-name>
<url-pattern>
/*
</url-pattern>
</filter-mapping>
</web-app>
listener
Элемент listener используется для регистрации класса приемника (listener),
который включен в web-приложение. Элемент listener имеет следующее опи-
сание:
<! ELEMENT listener (listener-class)>
<! ELEMENT listener-class (#PCDATA)>
Ниже показан действительный дескриптор развертывания, содержащий
элемент listener:
<?xml vesion="1.О" encoding=”IS0-8859-1”?>
<! DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<listener>
<listener-class>
AppLifeCycleEvent
</listener-class>
</listener>
</web-app>
servlet
Элемент servlet используется для объявления сервлета. Он имеет следую-
щий дескриптор:
<!ELEMENT servlet (icon?, servlet-name, display-name?, description?,
(servlet-class|jsp-file), init-param*, load-on-startup?, run-as?,
secu rity-role-ref*)>
Развертывание приложений
445
<web-app>
<mime-mapping>
<extension>
txt
</extension>
<mime-type>
text/plain
</mime-type>
</mime-mapping>
</web-app>
welcome-file-list
Элемент welcome-file-list определяет используемый по умолчанию файл,
который будет выводиться на экран в том случае, если введенный пользовате-
лем в браузере URL не содержит имени сервлета или страницы JSP. Предпо-
ложим, что пользователь вводит:
http://www.yourdomain.com/appName/
Если элемент welcome-file-list не определен в дескрипторе развертывания
web-приложения, то пользователь увидит сообщение об ошибке в полномо-
чиях доступа или список файлов и каталогов в каталоге приложения.
welcome-file-list описывается следующим образом:
<!ELEMENT welcome-file-list (welcome-file+)>
<! ELEMENT welcome-file (#PCDATA)>
Подэлемент welcome-file содержит используемое по умолчанию имя файла.
Элемент welcome-file-list может содержать один или несколько подэлементов
welcome-file. Если файл, определенный в первом элементе welcome-file, не
удается найти, web-контейнер будет искать для вывода второй файл, и т. д.
Ниже представлен дескриптор развертывания, который содержит два эле-
мента welcome-file. Первый welcome-file определяет файл index.hrml в катало-
ге приложения; второй определяет файл welcome.html в каталоге src, который
н' ходится в каталоге приложения:
<?xml vesion="1.0" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
”http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
442
Глава 16
<!ELEMENT security-role-ref (description?, role-name, role-link)>
<!ELEMENT description (#PCDATA)>
<! ELEMENT role-name (#PCDATA)>
<! ELEMENT role-link (#PCDATA)>
Элемент role-link используется для связи ссылки на роль безопасности с
определенной ролью безопасности. Элемент role-link должен содержать имя
одной из ролей безопасности, определенных в элементах security-role.
Например, для отображения ссылки «MG» на роль безопасности с именем
«manager» должен использоваться синтаксис:
<secu гitу-role-ref> \
<role-name>MG</role-name>
<role-link>manager</role-link>
</secu rity-role-ref>
В этом случае, если сервлет, вызванный пользователем, принадлежащим
роли безопасности «manager», делает вызов API isUserInRole("MG"), то резуль-
татом будет true. Имя роли «*» не допустимо, так как оно имеет специальное
значение при ограничении авторизации.
Ниже показан дескриптор развертывания, содержащий несколько элемен-
тов servlet:
<?xml vesion=”1.0’’ encoding=”IS0-8859-1’’?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
’’http://java.sun.com/dtd/web-app_2_3.dtd’’>
<web-app>
<servlet>
<servlet-name>
First
</servlet-name>
<servlet-class>
FirstServlet
</servlet-class>
<load-on-startup>
1
</load-on-startup>
</servlet>
<servlet>
<servlet-name>
JDBCServlet
Развертывание приложений
447
</error-code>
<location>
/error.html
</location>
</error-page>
</web-app>
taglib
Элемент taglib описывает библиотеку пользовательских тегов JSP. Синтак-
сис элемента taglib:
<!ELEMENT taglib (taglib-uri, taglib-location)>
<!ELEMENT taglib-uri (#PCDATA)>
<’ELEMENT taglib-location (#PCDATA)>
Элементtaglib-uri является URI библиотеки тегов, используемой в web-при-
ложении. Значение taglib-uri задается относительно местоположения дескрип-
тора развертывания.
taglib-location указывает, где можно найти файл TLB для библиотеки тегов.
Ниже приводится дескриптор развертывания, который содержит элемен-
ты tagib:
<?xml vesion=’’1.0" encoding=’’IS0-8859-1’’?>
<!DOCTYPE taglib
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
’’http://java.sun.com/dtd/web-app_2_3.dtd’’>
<web-app>
<taglib>
<taglib-uri>
http://java.apache.org/tomcat/examples-taglib
</taglib-uri>
</taglib-location>
/WEB-INF/jsp/example-taglib.tld
</taglib-location>
</taglib>
</web-app>
resource-env-ref
Элемент resource-env-ref служит для объявления сервлетной ссылки, ука-
зывающей на администрируемый объект, связанный с ресурсом в среде серв-
лета. Дескриптор элемента resource-env-ref имеет следующий вид:
444
Глава 16
<!ELEMENT session-config (session-timeout?)>
<!ELEMENT session-timeout (#PCDATA)>
Элемент session-timeout задает используемый по умолчанию интервал ожи-
дания сеанса в минутах. Значение должно быть целым числом. Если значение
элемента session-timeout равно нулю или отрицательному числу, то ожидание
сеанса не определяется.
Ниже показан дескриптор развертывания, который делает используемый
по умолчанию объект HttpSession недействительным через 10 минут после
последнего обращения пользователя:
<?xml vesion=”1.0" encoding="IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
’’ http: //j ava. sun. com/dtd/web-app_2_3. dtd">
<web-app>
<session-config>
<session-timeout>
10
</session-timeout>
</session-config>
</web-app>
mime-mapping
Элемент mime-mapping сопоставляет тип mime с расширением файла. Дес-
криптор элемента имеет вид:
< ! ELEMENT mime-mapping (extension, mime-type)>
< ! ELEMENT extension (#PCDATA)>
< ! ELEMENT mime-type (#PCDATA)>
Элемент extension описывает расширение файла, a mime-type является ти-
пом MIME. Например, следующий дескриптор развертывания сопоставляет
расширение "txt” с "text/plain":
<?xml vesion="1.0” encoding="IS0-8859-1"?>
<’DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
Развертывание приложений
449
• res-auth указывает, будет л и код сервлета регистрироваться программ-
ным путем в менеджере ресурсов или web-контейнер будет регистри-
роваться в менеджере ресурсов от имени сервлета. Значение этого
элемента должно быть Application или Container.
• res-sharing-scope указывает, могут ли совместно использоваться со-
единения, полученные по ссылке на фабрику соединений заданного
менеджера ресурсов. Значение этого элемента должно быть либо
Shareable (по умолчанию), либо Unshareable.
Приведем дескриптор развертывания, который содержит элемент resource-ref:
<?xml vesion="1.О" encoding=”IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
”http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<resource-ref>
<description>JDBC Data Source</description>
<res-ref-name>db/JDBCDatabase</res-ref-name>
< res-type>j avax.sql.DataSou rce</res-type>
<res-auth>Application</res-auth>
<res-sharing-scope>Unshareable</res-sharing-scope>
</resource-ref>
</web-app>
security-constraint
Элемент security-constraint в дескрипторе развертывания позволяет огра-
ничить доступ к определенным ресурсам (см. главу 14).
Элемент security-constraint описывается следующим образом:
<!ELEMENT security-constraint (display-name?, web-resource-collection+,
auth-constraint?, user-data-constraint?)>
<! ELEMENT display-name (#PCDATA)>
<!ELEMENT web-resource-collection (web-resource-name, description?,
url-pattern*, http-method*)>
<!ELEMENT auth-constraint (description?, role-name*)>
<!ELEMENT user-data-constraint (description?, transport-guarantee)>
Элемент web-resource-collection идентифицирует подмножество ресурсов,
доступ к которым необходимо ограничить. В web-resource-collection можно
определить шаблоны URL и метод HTTP. Если метод HTTP отсутствует, то
ограничение безопасности применяется ко всем методам.
446
Глава 16
<welcome-file-list>
<welcome-file>
index.html
</welcome-file>
<welcome-file>
src/welcome.html
</welcome-file>
</welcome-file-list>
</web-app>
Если файл index.html отсутствует в каталоге приложения, когда пользователь
вводит URL, который не содержит имя сервлета, страницы JSP или другого ре-
сурса, то будет выводиться файл welcome.html из каталога src.
error-page
Элемент error-page сопоставляет код ошибки или тип исключения с путем
доступа к ресурсу в web-приложении, так что если возникает определенная
ошибка HTTP или некоторое исключение Java, то будет выводиться указан-
ный ресурс. Дескриптор элемента имеет следующий вид:
<!ELEMENT error-page ((error-code | exception-type), location)>
<! ELEMENT error-code (#PCDATA)>
<!ELEMENT exception-type (#PCDATA)>
<!ELEMENT location (#PCDATA)>
Элемент error-code содержит код ошибки HTTP, exception-type является
полностью квалифицированным именем типа исключения Java, и location
это путь доступа к ресурсу в web-приложении относительно каталога прило-
жения. Значение location должно начинаться с символа /.
Например, следующий дескриптор развертывания заставляет web-контей-
нер выводить страницу error.html из каталога приложения всякий раз, когда
возникает ошибка HTTP с кодом 404:
<?xml vesion=”1.0” encoding=”IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<error-page>
<error-code>
404
Развертывание приложений
451
<?xml vesion="1.0" encoding="IS0-8859-1’’?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
”http://j ava.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<security-constraint>
<web-resou rce-collection>
<web-resou rce-name>
Restricted Area
</web-resou rce-name>
<url-pattern>/servlet/*</url-pattern>
</web-resou rce-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</secu rity-const raint>
<login-config>
<auth-method>BASIC<auth-method>
<realm-name>User Basic Authentication</realm-name>
</login-config>
</web-app>
login-config
Элемент login-config определяет используемый метод аутентификации, имя
области действия (realm) и атрибуты, необходимые для механизма регистра-
ции формы. Дескриптор элемента имеет следующий вид:
<!ELEMENT login-config (auth-method?, realm-name?, form-login-config?)>
<! ELEMENT auth-method (#PCDATA)>
<! ELEMENT realm-name (#PCDATA)>
<! ELEMENT form-login-config (form-login-page, form-error-page)>
auth-method определяет метод аутентификации. Его значением может быть:
BASIC, DIGEST, FORM или CLIENT-CERT.
Элемент realm-name определяет имя области действия для использования в
базовой авторизации HTTP.
Элемент form-login-config задает страницы регистрации и ошибки, которые
должны использоваться при регистрации на основе формы. Если аутентифика-
ция на основе формы не применяется, эти элементы игнорируются.
448
Глава 16
<!ELEMENT resource-env-ref (description?, resource-env-ref-name,
resource-env-ref-type)>
<!ELEMENT resource-env-ref-name (#PCDATA)>
<!ELEMENT resource-env-ref-type (#PCDATA)>
Элемент resource-env-ref-name — это имя ссылки на среду ресурса, а значе-
нием ссылки является входное имя среды, используемой в коде сервлета. Имя
задается как имя JNDI (Java Naming and Directory Interface) относительно кон-
текста java:comp/env и должно быть уникальным в web-приложении.
Элемент resource-env-ref-type определяет тип ссылки на среду ресурса. Его
значение должно быть полностью квалифицированным именем класса или
интерфейса Java.
Следующий дескриптор развертывания содержит элемент resource-env-ref:
<?xml vesion="1.О” encoding="IS0-8859-1"?>
< I DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
” http: //j ava. sun. com/dtd/web-app_2_3. dtd’’>
<web-app>
<resource-env-ref>
<resource-env-ref-name>jms/XQueue</resource-env-ref-name>
< resou rce-env-ref-type>j avax.j ms.Queue</resou rce-env-ref-type>
</resource-env-ref>
</web-ref>
i
resource-ref
Элемент resource-ref определяет объявление сервлетной ссылки на внешний
ресурс. Он имеет следующий синтаксис:
<!ELEMENT resource-ref (description?, res-ref-name, res-type, res-auth,
res-sharing-scope?)>
< ! ELEMENT description (#PCDATA)>
< ! ELEMENT res-ref-name (#PCDATA)>
< ! ELEMENT res-type (#PCDATA)>
< ! ELEMENT res-auth (#PCDATA)>
< !ELEMENT res-sharing-scope (#PCDATA)>
Подэлементы resource-ref имеют следующее назначение:
• res-ref-name — имя ссылки фабрики ресурса. Задается как имя JNDI
относительно контекстаjava:comp/env. Имя должно быть уникальным
в web-приложении.
Развертывание приложений
453
<!ELEMENT env-entry-name (#PCDATA)>
<!ELEMENT env-entry-value (#PCDATA)>
<!ELEMENT env-entry-type (#PCDATA)>
Элемент env-entry-name содержит имя точки входа среды web-приложения.
Имя задается как имя JNDI относительно контекста java:comp/env. Имя долж-
но быть уникальным в приложении.
Элемент env-entry-value содержит значение точки входа web-приложения.
Значением может быть строка (String), которая является допустимой для кон-
структора указанного типа, получающего один параметр String. Для
java.lang.Character значением может быть один символ.
Элемент env-entry-type содержит полностью квалифицированный тип Java
значения точки входа среды, который ожидается кодом web-приложения. Этот
элемент может иметь одно из следующих значений:
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.String
java.lang.Short
java.lang.Integer
java.lang.Long
java.lang.Float
java.lang.Double
Элемент ejb-ref определяет ссылку на домашний каталог Enterprise JavaBean.
Его дескриптор:
< ! ELEMENT ejb-ref (description?, ejb-ref-name, ejb-ref-type, home,
remote, ejb-link?)>
< !ELEMENT description (#PCDATA)>
< !ELEMENT ejb-ref-name (#PCDATA)>
< !ELEMENT ejb-ref-type (#PCDATA)>
< ! ELEMENT home (#PCDATA)>
< ! ELEMENT remote (#PCDATA)>
< ! ELEMENT ejb-link (#PCDATA)>
Элемент ejb-ref-name содержит имя ссылки EJB. Ссылка EJB является точ-
кой входа в среду сервлета и задается относительно контекста java:comp/env.
Имя должно быть уникальным в web-приложении. Рекомендуется, чтобы оно
начиналось с ”ejb/”.
Элемент ejb-ref-type содержит ожидаемый тип указанного Enterprise Bean.
Значением элемента ejb-ref-type может быть Entity или Session.
Элемент home задает полностью квалифицированное имя домашнего ин-
терфейса Enterprise Bean.
450
Глава 16
Элемент auth-constraint определяет роли пользователей, имеющие доступ
к данной совокупности ресурсов. Если элемент auth-constraint не определен,
то ограничение безопасности применяется ко всем ролям.
Элемент user-data-constraint используется для указания, как должны быть
защищены данные, передаваемые между клиентом и web-контейнером.
Описание элемента web-resource-collection:
<!ELEMENT web-resource-collection (web-resource-name, description?,
url-pattern*, http-method*)>
<! ELEMENT web-resource-name (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT url-pattern (#PCDATA)>
<!ELEMENT http-method (#PCDATA)>
web-resource-name — имя защищенного ресурса.
Элементу http-method можно присвоить один из методов HTTP, например
GET или POST.
Описание элемента auth-constraint:
<!ELEMENT auth-constraint (description?, role-name*)>
<!ELEMENT description (#PCDATA)>
<! ELEMENT role-name (#PCDATA)>
Элемент role-name содержит имя роли безопасности.
Описание элемента user-data-constraint:
< ! ELEMENT user-data-constraint (description?, transport-guarantee)>
< ! ELEMENT description (#PCDATA)>
< ! ELEMENT transport-guarantee (#PCDATA)>
Элемент transport-guarantee может иметь одно из следующих значений:
NONE, INTEGRAL или CONFIDENTIAL. NONE означает, что приложение
не требует транспортных гарантий. INTEGRAL указывает, что данные между
сервером и клиентом должны посылаться таким образом, чтобы их невозмож-
но было изменить во время передачи. CONFIDENTIAL означает, что пересы-
лаемые данные должны шифроваться. В большинстве случаев для INTEGRAL
и CONFIDENTIAL используется SSL (Secure Sockets Layer).
Ниже приводится дескриптор развертывания, который ограничивает дос-
туп к любому ресурсу, URL которого соответствует шаблону /servlet/*. Доступ
будет разрешен только пользователю в роли менеджера. Элемент login-config
требует от пользователя регистрации, при этом применяется базовый метод
аутентификации. Пользователь должен ввести имя пользователя и парольроли
менеджера:
Развертывание приложений
455
Приведенный URL предполагает, что сервлет не содержится в пакете. Если
сервлет является частью пакета, необходимо сохранить файл класса в структу-
ре каталогов, которая отражает пакет Java. Например, рассмотрим сервлет
OtherServlet, принадлежащий пакету com.newriders.
Прежде всего необходимо создать структуру каталогов, представленную на
рис. 16.4. Файл класса OtherServlet должен храниться в каталоге newriders.
ВМ webapps
i+l-SB images
B”9Bweb-inf
classes
Рис. 16.3. Структура каталогов
для туАрр
Images
Й-jB WEB-,NF
В-Bi classes
р]„ДЦД|сот
newriders
Рис. 16.4. Структура каталогов
для сервлета в пакете
К сервлету можно будет обратиться с помощью URL:
http://domain/myApp/servlet/com.newriders.OtherServlet
Можно видеть, что имя пакета содержится в URL; однако дескриптор раз-
вертывания позволяет вызывать сервлет под другим именем. Это означает, что
для доступа к сервлету можно использовать нестандартный URL.
Например, дескриптор развертывания в листинге 16.1 создает псевдоним
AnAlias для com.newriders.OtherServlet.
Л исти н г 16.1 Дескриптор развертывания, который предоставляет
псевдоним
<?xml version="1.О" encoding=”IS0-8859-r’?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>AnAlias</servlet-name>
<servlet-class>com.newriders.OtherServlet</servlet-class>
</servlet>
</web-app>
452
Глава 16
Элемент foim-login-config определяется следующим образом:
<!ELEMENT form-login-config (form-login-page, form-error-page)>
<! ELEMENT form-login-page (#PCDATA)>
<!ELEMENT form-error-page (#PCDATA)>
Элемент form-login-page определяет путь доступа к ресурсу, который вы-
водит страницу регистрации. Путь доступа должен начинаться с символа / и
задаваться относительно каталога приложения.
Элемент form-error-page определяет путь доступа к ресурсу, который вы-
водит страницу ошибки при неудачной регистрации. Путь доступа должен на-
чинаться с символа / и задаваться относительно каталога приложения.
security-role
Элемент security-role определяет объявление роли безопасности, исполь-
зуемой в security-constraints. Он описывается следующим образом:
<!ELEMENT security-role (description?, role-name)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT role-name (#PCDATA)>
Следующий дескриптор развертывания демонстрирует использование эле-
мента security-role:
<?xml vesion="1.О” encoding="IS0-8859-1"?>
<: DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://j ava.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<security-role>
<role-name>manager</role-name>
</security-role>
</web-app>
env-entry
Элемент env-entry определяет точку входа среды приложения. Этот элемент
описывается следующим образом:
<!ELEMENT env-entry (description?, env-entry-name, env-entry-value?,
env-entry-type)>
<!ELEMENT description (#PCDATA)>
Развертывание приложений
457
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>AnAlias</servlet-name>
<servlet-class>com.newriders.OtherServlet</servlet-class>
</servlet>
<!- mapping ->
<servlet-mapping>
<servlet-name>AnAlias</servlet-name>
<url-pattern>/newURL/level-1/level-2</url-pattern>
</servlet-mapping>
</web-app>
Теперь можно использовать следующий URL для доступа к сервлету:
http://domain/myApp/newURL/level-1/level-2
Отметим также, что можно использовать групповой символ *. Например,
шаблон URL /whatever/* обеспечит доступ к сервлету по любому URL, который
заканчивается на /whatever/:
http://domain/myApp/whatever/noname
http://domain/myApp/whatever/favorite
Псевдонимы JSP и отображение
Для приложения JSP используется структура каталогов, аналогичная структу-
ре для сервлетного приложения. Рассмотрим приложение JSP с именем
myJSPApp. Это приложение имеет структуру каталогов, представленную на
рис. 16.5.
Файлы JSP хранятся в каталоге myJSPApp, а файлы дополнительных клас-
сов — в каталоге WEB-INF/classes. Страница JSP вызывается с помощью URL:
http://domain/appName/pageName
Это означает, что если имя домена — www.blahblahblah.com, имя прило-
жения — myJSPApp, страница называется SimplePage.jsp и приложение заре-
гистрировано в файле server.xml (см. приложение А), то URL для доступа к
странице будет следующим:
http://www.blahblahblah.com/myJSPApp/SimplePage.jsp
454
Глава 16
Элемент remote содержит полностью квалифицированное имя удаленно-
го интерфейса Enterprise Bean.
Элемент ejb-link используется в элементах ejb-ref и ejb-local-ref для указа-
ния, что ссылка EJB связана с другим Enterprise Bean.
Значением элемента ejb-link должно быть ejb-name для Enterprise Bean в
том же приложении J2EE.
Имя в элементе ejb-link может состоять из имени пути, который опреде-
ляет ejb-jar, содержащий указанный Enterprise Bean, ejb-name целевого Bean
добавляется и отделяется от имени пути с помощью символа #. Имя пути дос-
тупа задается относительно WAR, содержащего web-приложение, на которое
ссылается Enterprise Bean. Это позволяет уникально идентифицировать не-
сколько Enterprise Bean с одинаковым ejb-name.
Элемент ejb-local-ref используется для объявления ссылки на локальный
домашний каталог Enterprise Bean. Он описывается следующим образом:
<!ELEMENT ejb-local-ref (description?, ejb-ref-name, ejb-ref-type,
local-home, local, ejb-link?)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT ejb-ref-name (#PCDATA)>
<!ELEMENT ejb-ref-type (#PCDATA)>
<!ELEMENT local-home (#PCDATA)>
<! ELEMENT local (#PCDATA)>
<! ELEMENT ejb-link (#PCDATA)>
Элемент local содержит полностью квалифицированное имя локального
интерфейса Enterprise Bean.
Элемент local-home содержит полностью квалифицированное имя локаль-
ного домашнего интерфейса Enterprise Bean.
Псевдонимы сервлетов и отображение
При использовании Tomcat для сервлетов не требуется дескриптор разверты-
вания. Рассмотрим структуру каталогов, представленную на рис. 16.3. Это
структура каталогов для web-приложения с именем туАрр.
Если поместить файл класса сервлета в каталог classes в туАрр, то сервлет
будет доступен по URL: http://domain/myApp/servlet/ServletClassName.
Например, если сервлет называется TestingServlet, а имя домена
www.blahblahblah.com, то URL сервлета будет следующим:
http://www.blahblahblah.com/myApp/servlet/TestingServlet.
^Примечанием По умолчанию для доступа к сервлету используется порт
8080. В приложении А рассказывается о том, как заставить
сервлет работать на другом порту.
Развертывание приложений
459
Упаковка и развертывание web-приложения
Можно развернуть приложение с помощью структуры каталогов, показанной
выше. Однако существует более элегантный способ развертывания приложе-
ния: можно сначала упаковать приложение в файл web-архива (WAR). Файл
web-архива имеет расширение .war.
По сути, файл WAR является файлом .jar, который создается с помощью
программы jar. В файл WAR упаковываются все файлы приложения. Имя фай-
ла WAR обычно является таким же, как и имя приложения; однако можно
использовать любое имя на выбор.
Файл WAR помещают в каталог webapps. После этого можно обращаться к
приложению так же, как это делается в случае неупакованного приложения.
Для упакованного приложения используется имя файла .war. Например, если
приложение с именем туАрр упаковывается в файл WAR с именем dontcare.war,
то после развертывания именем приложения будет dontcare, а не туАрр. Это
является результатом того, что при архивации приложения его имя не вклю-
чается в файл WAR.
Заключение
В этой главе показано, как можно сконфигурировать и развернуть web-приложе-
ние. Мы говорили о структуре каталогов типичного приложения и о дескрипторе
развертывания.
Готовое приложение можно развернуть, сохранив файлы и структуру катало-
гов приложения. Либо можно упаковать приложение в файл WAR и развернуть
все приложение, используя один файл.
456
Глава 16
В дескрипторе развертывания определяется имя (AnAlias) для сервлета
com.newriders.OtherServlet. Теперь сервлет будет доступен по обоим URL:
http://domain/myApp/servlet/com.newriders.OtherServlet
http://domain/myApp/servlet/AnAlias
В этом примере URL по-прежнему должен содержать слово «servlet». С по-
мощью servlet-mapping можно удалить из URL слово «servlet». Листинг 16.2
показывает, как создать другой URL для сервлета com.newriders.OtherServlet с
помощью дескриптора развертывания.
Листинг 16.2 Дескриптор развертывания, который обеспечивает
отображение
<?xml version="1.0" encoding='TS0-8859-1’’?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>AnAlias</servlet-name>
<servlet-class>com.newriders.OtherServlet</servlet-class>
</servlet>
<!- mapping ->
<servlet-mapping>
<servlet-name>AnAlias</servlet-name>
<url-pattern>/newURL</url-pattern>
</servlet-mapping>
</web-app>
Теперь доступ к сервлету можно получить с помощью трех URL:
http://localhost:8080/myApp/servlet/com.newriders.OtherServlet
http://localhost:8080/myApp/servlet/AnAlias
http://localhost:8080/myApp/newURL
Отметим, что в третьем URL слова «servlet» нет.
Шаблон URL может быть не только простым словом. Например, можно со-
здать впечатление, что файл класса находится в подкаталоге (см. листинг 16.3).
Листинг 16.3 Дескриптор развертывания, который использует более
сложное отображение
<?xml version="1.О" encoding="IS0-8859-1”?>
Проектирование web-приложений на языке Java
461
н .цы, начиная с первой. Если необходимо, то в последовательности вместо
страницы JSP может быть сервлет или страница HTML.
Архитектура Model 1 представлена на рис. 17.1.
Рис. 17.1. Архитектура Model 1
Пример архитектуры Model 1:
приложение Login
В качестве примера рассмотрим приложение Login, которое применяет ар-
хитектуру Model I. Оно состоит из трех страниц JSP и JavaBean. Приложение
предназначено для регистрации пользователей. Последовательность начина-
ется со страницы Login.jsp, которая представлена в листинге 17.1.
Листинг 17.1 Login.jsp
<%@ page session="false" %>
<HTML>
<HEAD>
<TITLE>Login</TITLE>
</HEAD>
<BODY>
<FORM METH0D=”P08T” ACTION=”ProcessLogin.jsp">
<%
if (request.getParameter(”error”)!=null) {
%>
Login failed. Please try again
<BR><HR>
<%
}
%>
<TABLE>
<TR>
<TD>User Name</TD>
<TDxlNPUT TYPE=TEXT NAME=”userName’’x/TD>
</TR>
<TR>
<TD>Password</TD>
<TDXINPUT TYPE=PASSWORD NAME="password”></TD>
</TR>
458
Глава 16
Если у вас нет JavaBean и библиотеки тегов, то вам не требуется файл WEB-
INF. Если вас устраивает URL для доступа к странице JSP, то не требуется и
дескриптор развертывания.
Однако с помощью дескриптора развертывания можно сконфигурировать
приложение. В частности, можно создать псевдоним, который позволит об-
ращаться к странице JSP с помощью другого имени.
Следующий дескриптор развертывания предоставляет псевдоним для стра-
ницы JSP с именем Simple Page .jsp:
<?xml version=”1.0" encoding=”IS0-8859-1"?>
<!DOCTYPE web-app
PUBLIC ’’-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
"http://j ava.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<servlet>
<servlet-name>
AnAlias
</servlet-name>
<jsp-file>
/SimplePage.jsp
</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
</web-app>
Web-приложение может состоять из сервлетов, страниц JSP
и других ресурсов. В этом случае классы сервлета и
страницы JSP могут храниться в своих собственных
каталогах, не влияя друг на друга.
webapps
classes
Рис. 16.5. Структура каталогов для приложения JSP с именем myJSPApp
Проектирование web-приложений на языке Java
463
Эта страница использует JavaBean с именем model 1. Login Bean. Страница
Process Login.jsp вызывает метод login из Bean, передавая параметры userName
и password объекта запроса. Метод login возвращает true, если пользователь
является авторизованным. Иначе метод возвращает false.
При успешной регистрации страница Process Login.jsp выводит страницу
Welcome.
if (loginBean.login(request.getParameter("userName"),
request.getParameter("password")))
request.getRequestDispatcher("Welcome.jsp").forward(request, response);
Отметим, что вместо метода sendRedirect интерфейса javax.servlet.http.
HttpServletResponse используется диспетчер запросов, получаемый из метода
getRequestDispatcher объекта запроса. Метод forward объекта RequestDispatcher
переносит обработку на страницу Welcome.jsp. Метод forward быстрее, чем
sendRedirect, так как не существует обмена с клиентом.
Если регистрация не проходит, пользователь посылается назад на страни-
цу Login.jsp.
response.sendRedi rect("Login.j sp?error=yes”);
Здесь применяется метод sendRedirect интерфейса javax.servlet.http.
HttpServletResponse, так как нужно послать в URL параметр «error». В каче-
стве альтернативы можно использовать атрибут в объекте запроса (см. ниже).
Страница Welcome.jsp представлена в листинге 17.3.
Листинг 17.3 Welcome.jsp
<%@ page session="false" %>
<HTML>
<HEAD>
<TITLE>Welcome</TITLE>
</HEAD>
<BODY>
Welcome. You have successfully logged in.
</BODY>
</HTML>
JavaBean, используемый на странице ProcessLogin.jsp, представлен в лис-
тинге 17.4.
Проектирование
web-приложений
на языке Java
JL А осле знакомства с методами, приемами и особенностями работы с серв-
летами и JSP можно приступить к изучению архитектурного проектирования
web-приложений на языке Java. Обычно используют две модели, которые рас-
сматриваются в этой главе. Будет показано, что в сложных приложениях стра-
ницы JSP и сервлеты являются равноправными участниками.
Термины Model 1 и Model 2 для двух архитектурных проектов впервые по-
явились в ранних набросках спецификаций JSP. Эти два термина не упомина-
ются в современных документах спецификации JSP. Но сами модели широко
используются сегодня. Для иллюстрации различий моделей будут приведены
примеры приложений.
Проект на основе приложения Model 2 представлен в главе 18.
Архитектура Model 1
Архитектура приложения Model 1 строится на основе страниц. Клиентский бра-
узер проходит через ряд страниц JSP, каждая из которых может использовать
JavaBean, выполняющий бизнес-операции. Суть этой архитектуры состоит втом,
что каждая страница JSP обрабатывает свои собственные входные данные.
Приложения, реализующие эту архитектуру, обычно содержат несколько
страниц JSP, и ожидается, что пользователь последовательно проходит стра-
Проектирование web-приложений на языке Java
465
Рис. 17.3. Страница Welcome
Рис. 17.4. Страница Login после отказа в регистрации
Преимущества и недостатки архитектуры Model 1
Преимуществом этой модели является легкость разработки. Такое архитек-
турное решение подходит для небольших проектов.
К недостаткам модели относится следующее:
• Сложно обеспечить разделение труда между разработчиком страниц
и разработчиком Web, так как обычно разработчику Web приходить-
ся принимать участие в создании страницы и бизнес-объектов.
• Архитектуру Model 1 трудно поддерживать, и она не является гибкой.
Это особенно актуально для больших проектов.
462
Глава 17
Листинг 17.1 Продолжение
<TR>
<TD COLSPAN="2"><INPUT TYPE=SUBMIT VALUE="Login"x/TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
Страница Login.jsp является ничем иным как страницей HTML с формой.
Единственный присутствующий код — скриптлет, который идет после тега
<FORM> и проверяет, не содержит ли объект запроса параметр с именем
«error». При наличии такового выводится сообщение.
<%
if (request.getParameter("error")!=null) {
%>
Login failed. Please try again
<BRXHR>
<%
}
%>
Форма страницы Login посылается на вторую страницу JSP —
Process Login.jsp (см. листинг 17.2).
Л истинг 17.2 ProcessLogin.jsp
<%@ page session=”false” %>
<jsp:useBean id=’ToginBean” scope="page" class="model1.LoginBean" />
<%
if (loginBean.login(request.getParameter("userName"),
request.getParameter("password")))
request.getRequestDispatcher("Welcome.jsp").forward(request, response);
else
//Мы должны использовать sendRedirect, так как хотим послать часть
?еггог
//на страницу Login.jsp.
//Поскольку применяется RequestDispatcher. forwardO, URL по-прежнему
// будет текущим URL.
response.sendRedirect("Login.jsp?error=yes");
%>
Проектирование web-приложений на языке Java
467
В центре находится сервлет контроллера, который является единственной
точкой входа приложения. Сервлете именем Model2Servlet представлен в ли-
стинге 17.5.
Листинг 17.5 Model2Servlet
import javax.servlet.*;
public class Model2Servlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws ServletException, java.io.lOException {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (userName==null) {
RequestDispatcher rd = request.getRequestDispatcher("/Login.jsp");
rd.forward(request, response);
}
else {
if (password!=null && userName.equals("aibo")
&& password.equals("kitada")) {
// успешная регистрация
RequestDispatcher rd = request.getRequestDispatcher("/Welcome.jsp");
rd.forward(request, response);
}
else {
// отказ регистрации
request.setAttribute("error", "yes");
RequestDispatcher rd = request.getRequestDispatcher("/Login.jsp");
rd.forward(request, response);
}
}
}
}
Приложение является очень простым, так как для представления существу-
ют только две страницы JSP: страница Login и страница Welcome. Страница
Login служит для регистрации пользователя, а страница Welcome выводится
после успешной регистрации. Поэтому сервлет направляет запрос либо на стра-
ницу Login, либо на страницу Welcome.
Но как сервлет узнает, на какую страницу JSP нужно направить? Ответ зак-
лючается в логике метода service сервлета.
Метод service проверяет параметр с именем userName в объекте
ServletRequest. Отсутствие этого параметра приводит к тому, что сервлет на-
правляет запрос на страницу Login.
464
Глава 17
Листинг 17.4 LoginBean.java
package model1;
public class LoginBean {
public boolean login(String userName, String password) {
if (userName==null || password==null ||
! (userName.equalsC'aibo") && password. equalsC'kitada")))
return false;
else
return true;
}
}
В данном случае регистрация будет успешной, если userName равно «aibo» и
пароль равен «kitada». Метод login намеренно сделан очень простым. В реаль-
ном приложении эти два значения обычно сверяются с таблицей базы данных.
Чтобы протестировать приложение, выполните следующие действия:
1. Воспользуйтесь приложением myJSPApp.
2. Скопируйте все страницы .jsp в каталог myJSPApp каталога webapps.
3. Скомпилируйте LoginBean.java и скопируйте файл .class в каталог WEB-
1N F/classes/model 1 /.
4. Перезапустите Tomcat и загрузите страницу Login с помощью URL:
http://localhost:8080/myJSPApp/Login.jsp.
Это приложение показано на рис. 17.2 — 17.4.
Рис. 17.2. Страница Login
Проектирование web-приложений на языке Java
469
</TR>
<TR>
<TD>Passwo rd</TD>
<TD><INPUT TYPE=PASSWORD NAME="password"x/TD>
</TR>
<TR>
<TD C0LSPAN="2"XINPUT TYPE=SUBMIT VALUE="Login"X/TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
Л истинг 17.7 Страница Welcome
<%@ page session="false" %>
<HTML>
<HEAD>
<TITLE>Welcome</TITLE>
</HEAD>
<BODY>
Welcome. You have successfully logged in.
</BODY>
</HTML>
Чтобы протестировать это приложение, выполните следующие шаги:
1. Скопируйте Login.jsp и Welcome.jsp в каталог myJSPApp.
2. Скомпилируйте сервлет и скопируйте файл .class в каталог WEB-INF/
classes.
3. Перезапустите Tomcat и введите в браузере следующее:
http://localhost:8080/myJSPApp/servlet/Model2Servlet
Заключение
В этой главе кратко рассмотрены две модели создания web-приложения на
языке Java: Model 1 и Model 2. Архитектура Model 1 предоставляет возмож-
ность быстрой разработки приложений и подходит для создания небольших
проектов и прототипов. Model 2 рекомендуется в качестве архитектуры для
проектов среднего и большого размеров. Model 2 сложнее, но она предостав-
ляет больше возможностей в плане сопровождения и расширения.
Глава 18 представляет приложение е-коммерции, которое строится на ос-
нове архитектурного решения Model 2.
466
Глава 17
В связи с недостатками этого архитектурного подхода была введена архи-
тектура Model 2.
Архитектура Model 2
Архитектура Model 2 является по сути архитектурой MVC (Model-View-Controller
— Модель-Представление-Контроллер), которая разделяет генерацию содер-
жимого и представление содержимого. Архитектура Model 2 характеризуется
присутствием сервлета контроллера между клиентским браузером и страница-
ми JSP (или сервлетами, которые представляют содержимое). Сервлет контрол-
лера направляет запросы HTTP на соответствующие страницы JSP представле-
ния, основываясь на URL запроса, параметрах ввода и состоянии приложения.
В этой модели части представления (страницы JSP или сервлеты) изолируются
друг от друга.
Приложения Model 2 являются более гибкими и легкими в сопровождении
и в расширении, так как представления не ссылаются друг на друга напрямую.
Сервлет контроллера Model 2 обеспечивает единственную точку управления
для систем безопасности и регистрации и часто инкапсулирует входящие дан-
ные в форме, удобной для серверной модели MVC.
В случае создания больших приложений выбирайте
Model 2 в качестве архитектурной модели. Справедливо,
что архитектура Model 2 несколько усложняет
приложение. Однако среда MVC может существенно
упростить реализацию приложения Model 2.
Архитектура Model 2 показана на рис. 17.5.
Рис. 17.5. Архитектура Model 2
Пример приложения Model 2
Для иллюстрации применения архитектуры Model 2 перепишем приложе-
ние Login (см. выше).
Разработка приложений е-коммерции
471
Архитектура Model 2 рекомендуется для сложных приложений. Создать при-
ложение с помощью этой модели не так просто; однако она имеет ряд преиму-
ществ в сравнении с первой моделью. В этой главе показано, как Model 2 ис-
пользуется в приложении е-коммерции, которое реализует онлайновый магазин.
Проект называется Burnaby (по имени города к востоку от Ванкувера в Бри-
танской Колумбии). Это онлайновый магазин, в котором продаются продукты
питания различных категорий. Здесь можно найти шоколад, печенье, молоко,
сыр и другие продукты, обычно встречающиеся в супермаркетах. Целью про-
екта является демонстрация того, как можно разрабатывать приложения Model
2. Для краткости и ясности применяется упрощенная обработка ошибок и не
используется никакая оптимизация. Графический дизайн также облегчен. В
проекте используется база данных Microsoft Access. Можно создать собствен-
ную базу данных на основе структуры базы данных, представленной в разделе
«Структура базы данных», на любом сервере базы данных, если имеется соот-
ветствующий драйвер JDBC.
Спецификация проекта
Если вы пользовались электронными магазинами, такими как Amazon.com,
то вам будет понятна спецификация этого проекта.
Создаваемое приложение является онлайновым магазином, в котором
пользователи могут:
• Искать определенные продукты по названию или описанию продукта
• Просматривать список продуктов по категориям
• Просматривать данные о продукте
• Помещать продукт в корзину покупок
• Просматривать и редактировать корзину покупок
• Подтверждать и размещать заказ
/
/
Структура базы данных
В качестве примера используется база данных Microsoft Access. Однако для
развертывания рекомендуется перенести приложение в более масштабируе-
мую и мощную базу данных. Файл базы данных Access расположен в каталоге
\db каталога WEB-INF приложения. Для этого проекта применяются четыре
таблицы: Categories, Products, Orders и Order Details.
Таблица Categories
Таблица Categories используется для хранения категорий продуктов. Это
простая таблица, структура которой представлена в таблице 18.1.
468
Глава 17
if (userName==null) {
RequestDispatcher rd = request.getRequestDispatcher("/Login.jsp");
rd.forward(request, response);
}
Когда пользователь впервые запрашивает страницу, выводится страница
Login, так как в запросе нет параметра userName.
Если параметр userName присутствует, сервлет контроллера понимает, что
пользователь пытается зарегистрироваться. Сервлет проверяет, что вводятся
имя пользователя «aibo» и пароль «kitada». Если проверка проходит успешно,
сервлет направляет запрос на страницу Welcome.jsp.
if (password!=null && userName.equals("aibo")
&& password.equals("kitada")) {
// успешная регистрация
RequestDispatcher rd = request.getRequestDispatcher("/Welcome.jsp”);
rd.forward(request, response);
}
Иначе он устанавливает атрибут с именем error в объекте ServletRequest и
возвращает пользователя на страницу Login. Присутствие этого атрибута зас-
тавляет страницу Login.jsp вывести сообщение об ошибке.
Login.jsp и Welcome.jsp представлены в листингах 17.6 и 17.7 соответственно.
Листинг 17.6 Страница Login
<%@ page session="false" %:>
<НТМ1_>
<HEAD>
<TITLE>Login</TITLE>
</HEAD>
<BODY>
<FORM METH0D=”P0ST”>
<%
if (request.getAttribute("error”)!=null) {
%>
Login failed. Please try again
<BR><HR>
<%
}
%>
<TABLE>
<TR>
<TD>User Name</TD>
CTDXINPUT TYPE=TEXT NAME=”userName"x/TD>
Разработка приложений е-коммерции
473
Таблица OrderDetails
Таблица OrderDetails используется для хранения всех покупаемых элемен-
тов в каждом заказе. Ее структура представлена в таблице 18.4.
Таблица 18.4. Структура таблицы OrderDetails
Имя таблицы
Id
Orderld
Productld
Quantity
Price
Тип данных
AutoN umber
Number
Number
Number
Number
Проектирование страницы
Для обеспечения согласованного внешнего вида приложения все страницы
JSP имеют одну и ту же структуру. Вверху каждой страницы помещается заго-
ловок, а слева выводится меню. Структура страниц:
<НТМ1_>
<BODY>
<TABLE>
<TR>
<!- заголовок ->
</TR>
<TR>
<TD VALIGN="TOP”>
<!- Меню ->
</TD>
<TD VALIGN=”TOP”>
<!- содержимое страницы ->
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Подготовка
Прежде всего необходимо выполнить следующие действия:
Разработка
приложений
е-коммерции
главе 17 говорилось о двух проектных моделях, которые можно приме-
нять при создании web-приложений Java. Основой первой модели, называе-
мой Model 1, являются страницы: приложение состоит из последовательнос-
тей страниц JSP. В этой модели страница JSP вызывает другую страницу JSP.
Разработка приложений на основе этой модели очень проста; однако для боль-
ших приложений эта модель создает значительные трудности при сопровож-
дении. Поэтому Model 1 подходит только для небольших приложений, кото-
рые никогда не будут усложняться.
Вторая модель, Model 2, является архитектурой MVC (Model-View-Controller —
Модель-Представление-Контроллер), которая разделяет генерацию содержи-
мого и представление содержимого. Архитектура Model 2 характеризуется при-
сутствием сервлета контроллера между клиентским браузером и страницами
JSP или сервлетным содержимым. Сервлет контроллера направляет запросы
HTTP на соответствующие страницы JSP представления на основе URL зап-
роса, параметров ввода и состояния приложения. В этой модели части пред-
ставления (страницы JSP или сервлеты) изолированы друг от друга.
Приложения Model 2 являются более гибкими и легкими в сопровождении
и расширении, так как представления не ссылаются друг на друга напрямую.
Сервлет контроллера Model 2 предоставляет единственную точку управления
для системы безопасности и регистрации и часто инкапсулирует входящие
^данные в форме, пригодной для серверной модели MVC.
Разработка приложений е-коммерции
475
Рис. 18.2. Архитектура приложения
На рис. 18.3 — 18.8 показан пользовательский интерфейс проекта Burnaby.
Рис. 18.3. Страница Default
Рис. 18.4. Стран и ца Search Results
472
Глава 18
Таблица 18.1, Структура таблицы Categories
Имя столбца Тип данных
Categoryld AutoNumber
Category Text
Таблица Products
Таблица Products служит для хранения данных о каждом продаваемом про-
дукте (см. таблицу 18.2). Отметим, что таблица использует столбец Categoryld
для распределения продуктов по категориям.
Таблица 18.2. Структура таблицы Products
Имя столбца
Productld
Categoryld
Name
Description
Price
Тип данных
AutoNumber
Number
Text
Text
Number
Таблица Orders
Таблица Orders содержит такую информацию, как адрес доставки, данные
кредитной карты и контактное имя. Уникальный идентификатор использует-
ся для каждого заказа, что обеспечивает связь со всеми элементами таблицы
OrderDetails.
Структура таблицы Orders представлена в таблице 18.3.
Табл и ца 18.3. Структура таблицы Orders
Имя столбца
Orderld
ContactName
DeliveryAddress
CCName
CCN umber
CCExpiryDate
Тип данных
Numbers
Text
Text
Text
Text
Text
Разработка приложений е-коммерции
477
Рис. 18.7. Страница Shopping Cart
Bumabye-МаП
Search
Categories:
Credit Card Number:
Delivery Details
Contact Name:
Delivery Address:
C4
* *»
&
>"«•
Biscuit
Cheese: '
Name on Credit Card:
Chocolate
$
OS
Рис. 18.8. Страница Checkout
474
Глава 18
1. Скопировать каталог Burnaby и его содержимое из каталога software/
bumaby (см. сайт www.lory-press.ru) в %CATALINA_HOME%/webapps/.
Структура каталогов приложения показана на рис. 18.1.
^9 webapps
R classes
Рис. 18.1. Структура каталогов приложения
2. Создать DSN-имя Bumaby для базы данных Microsoft Access. Если ис-
пользуется другая база данных, необходимо проверить, что драй вер JDBC
для этой базы данных может быть найден и загружен JVM.
3. Отредактировать файл server.xml. Это серверный конфигурационный
файл, расположенный в каталоге conf в %CATALINA_HOME%. Открое-
те файл в текстовом редакторе и найдите код:
<Context path=’7examples’’ docBase=”examples" debug="O"
reloadable=”true”>
</Context>
Сразу после закрывающего тега </Context> добавьте строку:
<Context path='7burnaby" docBase="burnaby” debug='’O" reloadable="true">
</Context>
Структура приложения
Приложение содержит сервлет контроллера, который получает все запросы и
пересылает каждый запрос на одну из страниц JSP. Архитектура представлена
на рис. 18.2.
Разработка приложений е-коммерции
479
Листинг 18.1 Продолжение
<param-name>dbUrl</param-name>
<param-value>jdbc:odbc:Burnaby</param-value>
</init-param>
<init-param>
<param-name>dbUserName</param-name>
<param-value></param-value>
</init-param>
<init-param>
<param-name>dbPassword</param-hame>
<param-value></param-value>
</init-param>
</servlet>
</web-app>
Определяются следующие начальные параметры:
• base. URL сервлета контроллера, который будет использоваться в
атрибуте HREF гиперссылки или в атрибуте ACTION формы HTML
на страницах JSR Например, base будет иметь следующее значение,
если используется Tomcat на локальной машине на порту 8080:
http://localhost:8080/burnaby/servlet/ControllerServlet
При развертывании приложения замените ’'localhost:8080" подходящим
именем домена.
• jdbcDriver. Драйвер JDBC, используемый для доступа к базе данных.
• imageUrl. URL, где расположены изображения.
• dbUrl. URL базы данных, используемый при открытии объекта
java.sql.Connection.
• dbUserName. Имя пользователя, применяемое при открытии объекта
java.sql .Connection.
• dbPassword. Пароль, используемый при открытии объекта
java.sql.Connection.
Сервлет контроллера
к
Сервлет контроллера содержится в файле ControllerServlet.java в каталоге
bumaby/src (см. листинг 18.2).
Листинг 18.2 ControllerServlet
import java.sql.*;
import javax.servlet.*;
476
Глава 18
Search
Categories:
- < • < A..
Fruits 7.
Vegetable \
Biscuit > V; ;• V
Cheese
Juice • ?
Yoghurt j''.'-
Chocolate
Name Description Price Details
Dairy Milk Full cream 200g 8.25 Details
Strawberry Forever Strawberry milk 11 7.95 Details
Outlook Strawberry Strawberry milk SOO g 3.85 Details
Romania Sweet Chocolate milk SOO g 4.55 Details
Xtra Slim Milk Low fat chocolate milk 9.95 Details
Рис. 18.5. Страница BrowseCatalog
Рис. 18.6. Страница ProductDetails
Разработка приложений е-коммерции
481
/★★Обработка запроса HTTP Post * */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
String base = "/jsp/”;
String url = base + "Default.jsp";
String action = request.getParameter("action");
if (action!=null) {
if (action.equals("search"))
url = base + "SearchResults.jsp";
else if (action.equals("browseCatalog"))
url = base + "BrowseCatalog.jsp";
else if (action.equalsf'productDetails"))
url = base + "ProductDetails.jsp";
else if (action.equals("productDetails"))
url = base + "ProductDetails.jsp";
else if (action.equals("addShoppingltem") ||
action.equals("updateShoppingItem") ||
action.equals("deleteShoppingItem") ||
action.equals("displayShoppingCart"))
url = base + "ShoppingCart.jsp";
else if (action.equals("checkout"))
url = base + "Checkout.jsp";
else if (action.equals("order"))
url = base + "Order.jsp";
}
RequestDispatcher requestDispatcher =
getServletContextO. getRequestDispatcher(url);
requestDispatcher.forward(request, response);
}
}
Важными методами являются init и doPost. Метод init выполняет следую-
щие действия:
• Считывает начальные параметры из дескриптора развертывания и ини-
циализирует глобальные переменные для всего приложения:
ServletContext context = config.getServletContextO;
context.setAttribute("base", config.get!nitParameter("base"));
context.setAttribute("imageUrl”, config.getInitParameter("imagedrl"));
Создает экземпляр JavaBean с именем DbBean и помещает его в объект кон-
текста сервлета:
478
Глава 18
Создание проекта
В этом разделе приводятся файлы, которые составляют приложение. Подроб-
но описываются страницы JSP и классы.
Дескриптор развертывания
В каталог Burnaby включен дескриптор развертывания (web.xml). Этот файл
можно найти в каталоге WEB-INF. В web.xml определяется несколько началь-
ных параметров, которые станут глобальными переменными в сервлетах/стра-
ницах JSP. Эти глобальные переменные загружаются в объект ServletContext
при инициализации сервлета. Дескриптор развертывания представлен в лис-
тинге 18.1.
Листинг 18.1 Файл web.xml
<?xml version="1.0" encoding="IS0-8859-1"?>
<! DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://j ava.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!- Определение сервлета контроллера ->
<servlet>
<servlet-name>ControllerServlet</servlet-name>
<servlet-class>ControllerServlet</servlet-class>
<!- Определение начальных параметров, которые будут загружаться в
объект ServletContext в сервлете контроллера ->
<init-param>
<param-name>base</param-name>
<param-value>http://localhost:8080/burnaby/servlet/
ControllerServlet</param-value>
</init-param>
<init-param>
<param-name>jdbcDriver</param-name>
<param-value>sun.jdbc.odbc.JdbcOdbcDriver</param-value>
</init-param>
<init-param>
<param-name>imageUrl</param-name>
<param-value>http://localhost:8080/burnaby/images/</param-value>
</init-param>
<init-param>
Разработка приложений е-коммерции
483
Листинг 18.3 Продолжение
public int id;
public String name;
public String description;
public double price;
>
Листинг 18.4 Shoppingltem.java
package com. brainysoftware.burnaby;
public class Shoppingitem {
public int productld;
public String name;
public String description;
public double price;
public int quantity;
}
Включаемые файлы
Стран и цы J S P и меют оди наковую структуру — заголовок именю слева стра-
ницы. Заголовок представляется файлом Header.jsp, а меню—файлом Menu.jsp.
Эти файлы приводятся в листингах 18.5 и 18.6 соответственно.
Листинг 18.5 Header.jsp
<%
String base = (String) application.getAttribute("base");
String imageUrl = (String) application.getAttributeC’imageUrl");
%>
<TABLE WIDTH="740" CELLPADDING=”O"
HEIGHT="75" CELLSPACING="O" BORDER="O">
<TR>
<TD ALIGN="left" BGC0L0R="F6F6F6">
<F0NT FACE="Verdana" SIZE="4">Burnaby e-Mall</FONT>
</TD>
<TD ALIGN="RIGHT” BGC0L0R="F6F6F6">
<A HREF="<%=base%>?action=displayShoppingCart"><IMG
BORDER="0" SRC="<%=(imageUrl + "cart.gif")%>"x/A>
</TD>
</TR>
</TABLE>
480
Глава 18
Листинг 18.2 Продолжение
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.brainysoftware.burnaby.DbBean;
public class ControllerServlet extends HttpServlet {
/** Инициализация глобальных переменных */
public void init(ServletConfig config) throws ServletException {
System.out.println(”initializing controller servlet.”);
ServletContext context = config. getServletContextO;
context.setAttribute("base", config.get!nitParameter("base"));
context.setAttribute("imageUrl", config.getInitParameter("imageUrl"));
// создание экземпляра DbBean
DbBean dbBean = new DbBean();
// инициализация полей DbBean
dbBean.setDbUrl(config.get!nitParameter("dbUrl"));
dbBean.setDbUserName(config.get!nitParameter("dbUserName"));
dbBean.setDbPassword(config.getInitParameter("dbPassword"));
// размещение bean в контексте сервлета
// bean будет доступен со страниц JSP
context.setAttribute("dbBean", dbBean);
try {
// загрузка драйвера JDBC для базы данных
Class, fоrName(config.getlnitParameter("jdbcDriver"));
}
catch (ClassNotFoundException e) {
System, out. println(e. toStringO);
}
super.init(config);
}
/*Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
doPost(request, response);
}
I
Разработка приложений е-коммерции
485
"</A><BR>");
}
%>
</TD>
</TR>
</TABLE>
Страница Menujsp содержит форму, которая позволяет покупателю искать оп-
ределенные продукты. Форма извлекает все записи из таблицы Categories и выво-
дит их на странице BrowseCatalog как ссылки. Извлечение категорий осуществля-
ется с помощью Bean, классом которого является com.brainysoftware.bumaby.DbBean.
Областью действия этого Bean является приложение:
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean’7>
Страница Default
Основной страницей этого приложения является Default.jsp. Запрос, кото-
рый не содержит параметра action, и запрос, значение параметра action кото-
рого является недействительным, будут пересылаться на эту страницу. Стра-
ница выводит приветственное сообщение (см. листинг 18.7).
Л исти н г 18.7 Default.jsp
<HTML>
<HEAD>
<TITLE>Welcome</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2xjsp: include page="Header. jsp" flush=’’true'7></TD>
</TR>
<TR>
<TD><jsp:include page=”Menu.jsp" flush="true"/x/TD>
<TD VALIGN="TOP">
<H2>Welcome to Burnaby E-Mail.</H2>
</TD>
</TR>
</TABLE>
</BODY>
</HTML> •
482
Глава 18
П создание экземпляра DbBean
DbBean dbBean = new DbBean();
// инициализация полей DbBean
dbBean. setDbllrl (config. get!nitParameter( "dbUrl"));
dbBean. setDbllserName(config. getInitParameter(”dbllserName”));
dbBean. setDbPassword(conf ig. get!nitParameter( ’’dbPassword”));
// размещение bean в контексте сервлета
// bean будет доступен со страниц JSP
context.setAttribute('’dbBean”, dbBean);
Для доступа к этому Bean на страницах JSP применяется тег useBean:
<jsp:useBean id=”dbBean” scope=”application”
class=”com.brainysoftware.burnaby.DbBean"/>
• Загружает драйвер JDBC для базы данных:
try {
// загрузка драйвера JDBC для базы данных
Class.forName(config.get!nitParameter(”jdbcDriver”));
}
catch (ClassNotFoundException e) {
System, out. println(e. toStringO);
.}
Метод doPost управляет приложением, пересылая запросы соответствую-
щим страницам JSP на основе значения параметра action. Запросы пересыла-
ются с помощью метода forward объекта RequestDispatcher:
RequestDispatcher requestDispatcher =
getServletContext().getRequestDispatcher(url);
requestDispatcher.forward(request, response);
Поддерживающие классы
В этом проекте используются два поддерживающих класса. Первым из них
является класс Product, который представляет продукт в приложении. Второй
класс — Shoppingitem — представляет покупаемый продукт. Классы идентич-
ны, за исключением того, что класс Product не имеет поля quantity (количество).
Класс Product и класс Shoppingitem представлены в листингах 18.3 и 18.4.
Листинг 18.3 Productjava
package com.brainysoftware.burnaby;
public class Product {
Разработка приложений е-коммерции
487
<TD><FONT FACE="Verdana" SIZE="2"x%=product. price%x/FONTx/TO>
<TDXA
HREF="<%=base%>?action=productDetails&productId=<%=product. id%>">
<FONT FACE="Verdana" SIZE-’2">Details</F0NTx/Ax/TD>
</TR>
<%
}
}
else
out.println("Please enter a search keyword.");
%>
</TD>
</TR>
</TABLE>
</B0DY>
</HTML>
Отметим, что ключевое слово поиска содержится в параметре «keyword».
Страница BrowseCatalog
Когда пользователь щелкает в меню мышью на одной из гиперссылок ка-
тегорий, все продукты этой категории выводятся на странице BrowseCatalog.jsp.
Эта страница аналогична странице SearchResults.jsp, за исключением того, что
она получает параметр «categoryld», а не «keyword».
Страница BrowseCatalog.jsp представлена в листинге 18.9.
Листинг 18.9 BrowseCatalog.jsp
<%@ page import=’’com.brainysoftware.burnaby.Product" %>
<%@ раде import="java.sql.*" %>
<%@ page import="java.util.*" %>
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean"/>
<%
String base = (String) application.getAttribute("base");
%>
<HTML>
<HEAD>
<TITLE>Browse Catalog</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2><jsp:include page="Header.jsp" flush="true'7></TD>
</TR>
<TR>
484
Глава 18
Первые четыре строки кода получают атрибуты basp и imageUrl из объекта
ServletContext, который на странице JSP представляется приложением неявно:
<%
String base =(String) application.getAttribute("base");
String imageUrl = (String) application.getAttribute("imageUrl");
%>
Переменные base и imageUrl используются для создания ссылки на корзи-
ну покупок:
<А HREF="<%=base%>?action=displayShoppingCart"><IMG
BORDER="O" SRC="<%=(imageUrl + "cart.gif")%>"></А>
Листинг 18.6 Menu.jsp
<%@ page import="java.util.*" %>
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean"/>
<%
String base = (String) application.getAttribute("base");
%>
CTABLE CELLSPACING="O" CELLPADDING="5" WIDTHS150" BORDER="O">
<TR>
<TD BGC0L0R="F6F6F6">
CFONT FACE="Verdana">Search</FONT>
<FORM>
CINPUT TYPE="HIDDEN" NAME="action" VALUE="search">
CINPUT TYPE="TEXT” NAME="keyword" SIZE="10">
CINPUT type="SUBMIT" VALUE="Go">
</FORM>
</TD>
</TR>
<TR>
<TD BGC0L0R="F6F6F6"><F0NT FACE="Verdana">Categories:</F0NTx/TD>
</TR>
CTR VALIGN=”TOP”>
<TD BGC0L0R="F6F6F6">
<%
Hashtable categories = dbBean.getCategories();
Enumeration categorylds = categories.keys();
while (categorylds. hasMoreElementsO) {
Object categoryld = categorylds. nextElementO;
out.println("<A HREF=" + base + "?action=browseCatalog&category!d=” +
categoryld. toStringO + ">" +
categories.get(categoryld) +
Разработка приложений е-коммерции
489
продукт в корзину покупок. Страница ProductDetails.jsp представлена в лис-
тинге 18.10.
Л исти н г 18.10 ProductDetails.jsp
<%@ page import=’’com. brainysoftware. burnaby.Product" %>
<%@ page import="java.sql.*" %>
<%@ page import="java.util.★ " %>
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean"/>
<%
String base = (String) application.getAttribute("base");
String imagellrl = (String) application.getAttribute("imageUrl");
%>
<HTML>
<HEAD>
<TITLE>Product Details</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2><jsp:include page="Header. jsp" flush="true"/x/TD>
</TR>
<TR>
<TDxjsp:include page="Menu. jsp" flush="true"/x/TD>
<TD VALIGN="TOP">
<%
try {
int productld = Integer.parselnt(request.getParameter("productld"));
Product product = dbBean.getProductDetails(productld);
if (product!=null) {
%>
<TABLE>
<TR>
<TDXIMG BORDER="O" WIDTH="100" SRC="<%=(imageUrl + product.id)%>.gif"x/TD>
<TDXBx%=product. name%x/BxBR>
<%=product.description%><BR>
Price : $<%=product.price%x/TD>
</TR>
<TR>
<TD C0LSPAN="2" ALIGN="RIGHT">
<A HREF="<%=base%>?action=addShoppingItem&productId=<%=product.id%>">
Add To Cart</A>
</TD>
</TR>
</TABLE>
<%
}
486
Глава 18
Страница SearchResults
Страница Menu.jsp содержит форму поиска и ряд гиперссылок, служащих
для просмотра каталога. При отправке форма поиска будет пересылаться на
страницу SearchResults.jsp, представленную в листинге 18.8.
Листинг 18.8 SearchResults.jsp
<%@ page import="com.brainysoftware.burnaby.Product" %>
<%@ page import="java.sql.*” %>
<%@ page import="java.util.*” %>
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean"/>
<%
String base = (String) application.getAttribute("base");
%>
<HTML>
<HEAD>
<TITLE>Search Results</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2xjsp:include page="Header. jsp" flush="true"/></TD>
</TR>
<TR>
<TDxjsp:include page="Menu. jsp” flush="true"/x/TD>
<TD VALIGN="TOP">
<%
String keyword = request.getParameter("keyword”);
if (keyword!=null && !keyword.trim().equals("")) {
%>
<TABLE>
<TR>
<TD><F0NT FACE="Verdana" SIZE="3"xB>Name</BX/F0NTx/TD>
<TDXFONT FACE="Verdana" SIZE="3"><B>Description</B></F0NTX/TD>
<TDXFONT FACE="Verdana" SIZE="3"><B>Price</B></F0NTx/TD>
<TDXF0NT FACE="Verdana" SIZE="3"><B>Details</BX/F0NT></TD>
</TR>
<%
ArrayList products = dbBean.getSearchResults(keyword);
Iterator iterator = products.iterator();
while (iterator.hasNextO) {
Product product = (Product) iterator.next();
%>
<TR>
<TDXFONT FACE="Verdana" SIZE=”2"x%=product. name%x/FONTx/TD>
<TDXFONT FACE="Verdana" SIZE="2"x%=product. description%x/FONTx/TD>
Разработка приложений е-коммерции
491
item.productld = productld;
item.quantity = 1;
item.price = product.price;
item.name = product.name;
item.description = product.description;
shoppingCart.remove(Integer.toString(productld));
shoppingCart.put(Integer.toString(productld), item);
session.setAttribute("shoppingCart", shoppingCart);
}
catch (Exception e) {
out.println(’’Error adding the selected product to the shopping
cart”);
}
}
if (action!=null && action.equals("updateShoppingltem”)) {
try {
int productld = Integer. parseInt(request.getParameter("productId"));
int quantity = Integer.parselnt(request.getParameter(’’quantity”));
Shoppingitem item = (Shoppingitem)
shoppingCa rt.get(Integer.toSt ring(productld));
if (item!=null) {
item.quantity = quantity;
}
}
catch (Exception e) {
out.println(”Error updating shopping cart”);
}
}
if (action!=null && action.equals("deleteShoppingItem”)) {
try {
int productld = Integer.parselnt(request.getParameter("productId”));
shoppingCart.remove(Integer.toString(productld));
}
catch (Exception e) {
out.println("Error deleting the selected item from the shopping
cart");
}
}
%>
<HTML>
488
Глава 18
Листинг 18.9 Продолжение
<TDxjsp:include page=”Menu.jsp" flush="true"/x/TD>
<TD VALIGN="TOP">
<%
String categoryld = request.getParameter("categoryld”);
if (categoryld!=null && ’categoryld.trim().equals("")) {
%>
<TABLE>
<TR>
<TD><FONT FACE=”Verdana” SIZE=”3"><B>Name</Bx/F0NTx/TD>
<TD><FONT FACE="Verdana" SIZE="3"><B>Description</Bx/F0NTx/TD>
<TDXF0NT FACE="Verdana" SIZE="3"><B>Price</B></F0NTx/TD>
<TDXFONT FACE=”Verdana" SIZE="3"XB>Details</Bx/F0NTx/TD>
</TR>
<%
ArrayList products = dbBean.getProductsInCategory(categoryld);
Iterator iterator = products.iterator();
while (iterator.hasNextO) {
Product product = (Product) iterator.next();
%>
<TR>
<TDXFONT FACE="Verdana" SIZE="2"x%=product. name%x/FONT></TD>
<TD><F0NT FACE="Verdana" SIZE="2"x%=product.description%x/F0NTx/TD>
<TDXFONT FACE="Verdana" SIZE=”2”x%=product.price%x/FONTx/TD>
<TD><A
HREF="<%=base%>?action=productDetails&productId=<%=product.id%>">
<F0NT FACE=”Verdana" SIZE="2">Details</F0NTX/AX/TD>
</TR>
<%
}
else
out.println("Invalid category.");
%>
</TD>
</TR>
</TABLE>
</B0DY>
</HTML>
Страница ProductDetails
На странице Search Results или BrowseCatalog пользователь может щелкнуть
мышью на гиперссылке Details, чтобы получить данные о продукте. Данные о
продукте обрабатываются страницей ProductDetails.jsp. На этой же странице
пользователь может щелкнуть мышью на ссылке Add to Cart, чтобы добавить
Разработка приложений е-коммерции
493
<INPUT TYPE="HIDDEN" NAME="productId" VALUE="<%=item. productld%>">
CTDXINPUT TYPE="SUBMIT" VALUE="Delete"X/TD>
</FORM>
</TR>
<%
}
%>
<TR>
<TD C0LSPAN="7"xA HREF="<%=base%>?action=checkOut">Check Out</Ax/TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Страница Checkout
Завершив выбор товаров, пользователь должен оформить покупку. Это де-
лается подтверждением покупки и заполнением формы HTML на странице
CheckOut.jsp. Страница Checkout представлена в листинге 18.12. Это простая
форма HTML, где пользователь вводит данные о доставке и кредитной карте.
Л исти н г 18.12 CheckOut.jsp
<%
String base = (String) application.getAttribute("base");
%>
<HTML>
<HEAD>
<TITLE>Check Out</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2xjsp:include page="Header. jsp" flush="true"/x/TD>
</TR>
<TR>
<TD><jsp:include page="Menu. jsp" flush="true"/x/TD>
<TD VALIGN="TOP">
<FORM>
CINPUT TYPE="HIDDEN" NAME="action" VALUE="order">
<TABLE>
490
Глава 18
Листинг 18.10 Продолжение
>
catch (Exception е) {
out.println(”Error: Invalid product identifier.");
}
%>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Страница ShoppingCart.jsp
Корзина покупок в этом приложении основана на сеансе. Каждый товар
представляется классом Shoppingitem и хранится в объекте Hashtable с име-
нем shoppingCart. Объект Hashtable содержится в объекте Session каждого кон-
кретного пользователя.
ShoppingCart.jsp представлена в листинге 18.11.
Листинг 18.11 ShoppingCart.jsp
< %@ page import="com.brainysoftware.burnaby.Product” %>
< %@ раде import="com.brainysoftware.burnaby.Shoppingitem" %>
< %@ page import="java.sql.*” %>
< %@ page import="java.util.*" %>
<jsp:useBean id="dbBean" scope="application"
class="com.brainysoftware.burnaby.DbBean"/>
<%
String base = (String) application.getAttribute("base”);
Hashtable shoppingCart = (Hashtable)
session.getAtt ribute("shoppingCart”);
if (shoppingCart==null)
shoppingCart = new Hashtable(IO);
String action = request.getParameter("action");
if (action!=null && action.equals("addShoppingItem")) {
try {
int productld = Integer.parseInt(request.getParameter("productId"));
Product product = dbBean.getProductDetails(productld);
if (product!=null) {
Shoppingitem item = new ShoppingltemO;
Разработка приложений е-коммерции
495
Листинг 18.13 Order.jsp
•
<%© page import='’com.brainysoftware. burnaby.Product” %>
<%@ page import=”java.sql.*" %>
<%© page import=”java.util.*” %>
<jsp:useBean id=”dbBean” scope=”application”
class=”com.brainysoftware.burnaby.DbBean”/>
<HTML> V
<HEAD>
<TITLE>Order</TITLE>
</HEAD>
<B0DY>
<TABLE>
<TR>
<TD C0LSPAN=2><jsp:include page="Header. jsp” flush=',true”/x/TD>
</TR>
<TR>
<TDxjsp: include page=”Menu.jsp” flush="true"/x/TD>
<TD VALIGN=”TOP”>
<%
if (dbBean.insertOrder(request.getParameter("contactName"),
request. getParameter( ’’delive ryAddress”),
request.getParameter("ccName"),
request.getParameter("ccNumber"),
request.getParameter(”ccExpiryDate”),
(Hashtable) session.getAttributeC’shoppingCart”))) {
session.invalidate();
out.println(”Thank you for your purchase”);
}
else
out.println(”Error”);
%>
</TD>
</TR>
</TABLE>
</B0DY>
</HTML>
JavaBean DbBean
Все страницы JSP сохраняются как страницы представления и используют
JavaBean с именем DbBean. Этот Bean представлен влистинге 18.14, он содер-
жит все методы, используемые страницами JSP.
492
Глава 18
Листинг 18.11 Продолжение
<HEAD>
<TITLE>Shopping Cart</TITLE>
</HEAD>
<BODY>
<TABLE>
<TR>
<TD C0LSPAN=2xjsp: include page="Header.jsp" flush="true"/></TD>
c/TR>
<TR>
<TDxjsp:include page="Menu. jsp" flush="true"/>c/TD>
<TD VALIGN="TOP">
<%
%>
<TABLE>
<TR>
cTDxFONT FACE="Verdana" SIZE="3"><B>Name</B></FONTX/TD>
CTDXFONT FACE="Verdana" SIZE="3"xB>Descriptionc/Rx/F0NTx/TD>
CTDXFONT FACE="Verdana" SIZE="3"><B>Price</Bx/F0NTx/TD>
CTDXFONT FACE="Verdana" SIZE="3"xB>Quantityc/Bx/FONT>c/TD>
CTDXFONT FACE="Verdana" SIZE="3"xB>Subtotal</Bx/FONT></TD>
CTDXFONT FACE="Verdana" SIZE="3”XB>UpdateC/B>c/FONTx/TD>
CTDxFONT FACE="Verdana" SIZE="3"xB>Delete</B></FONTX/TD>
</TR>
<%
Enumeration enum = shoppingCart.elementsO;
while (enum.hasMoreElementsO) {
Shoppingitem item = (Shoppingitem) enum.nextElementO;
%>
<TR>
<TDXFONT FACE="Verdana" SIZE="2"x%=item.name%x/F0NTx/TD>
<TDXF0NT FACE=”Verdana" SIZE="2"x%=item.description%x/F0NT></TD>
<TDXFONT FACE="Verdana" SIZE="2"x%=item. price%x/FONT></TD><FORM>
CINPUT TYPE="HIDDEN" NAME="action" VALUE="updateShoppingItem">
CINPUT TYPE="HIDDEN" NAME=”productId" VALUE="c%=item.productld%>”>
CTDXINPUT TYPE="TEXT" Size="2" NAME="quantity"
VALUE="c%=item. quantity%>"x/TD>
CTDXFONT FACE="Verdana" SIZE="2"x%=item. quantity*item. price%>c/FONT>c/TD>
CTDXINPUT TYPE="SUBMIT" VALUE="Update"X/TD>
C/FORM>
CFORM>
CINPUT TYPE="HIDDEN" NAME="action" VALUE="deleteShoppingItem">
Разработка приложений е-коммерции
497
try {
Connection connection = DriverManager.getConnection(dbUrl,
dbllserName, dbPassword);
Statement s = connection.createStatementO;
String sql = "SELECT Productld, Name, Description, Price FROM
Products" +
" WHERE Name LIKE ’%" + keyword.trim() + "%'" +
OR Description LIKE ’%" + keyword.trim() + "%”’;
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
Product product = new ProductO;
product.id = rs.getlnt(l);
product.name = rs.getString(2);
product.description = rs.getString(3);
product.price = rs.getDouble(4);
products.add(product);
}
rs.closeO;
s.closeO;
connection.close();
}
catch (SQLException e) {}
return products;
}
public ArrayList getProductsInCategory(String categoryld) {
ArrayList products = new ArrayListQ;
try {
Connection connection = DriverManager.getConnection(dbUrl,
dbUserName, dbPassword);
Statement s = connection.createStatementO;
String sql = "SELECT Productld, Name, Description, Price FROM
Products” +
" WHERE Categoryld=" + categoryld;
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
Product product = new ProductO;
product.id = rs.getlntO);
product.name = rs.getString(2);
product.description = rs.getString(3);
product.price = rs.getDouble(4);
products.add(product);
}
494
Глава 18
<TR>
<TD C0LSPAN='’2''xixB>Delivery Details</Bx/lx/TD>
c/TR>
<TR>
<TD>Contact Name:C/TD>
CTDXINPUT TYPE="TEXT" NAME=”contactName”x/TD>
</TR>
<TR>
<TD>Delivery Address:</TD>
CTDXINPUT TYPE=”TEXT” NAME="deliveryAddress”c/TD>
</TR>
<TR>
<TD C0LSPAN=,’2’’xl><B>Credit Card Details</Bx/lx/TD>
</TR>
<TR>
<TD>Name on Credit Card:</TD>
CTDXINPUT TYPE=”TEXT” NAME="ccName"c/TD>
</TR>
<TR>
<TD>Credit Card Number:C/TD>
CTDXINPUT TYPE=’’TEXT” NAME=,,ccNumber">c/TD>
c/TR>
CTR>
cTD>Credit Card Expiry Date:C/TD>
CTDXINPUT TYPE=”TEXT” NAME=’,ccExpiryDate”c/TD>
C/TR>
CTR>
cTD> c/TD>
CTDXINPUT TYPE=”SUBMIT’’ VALUE=”Check Out”X/TD>
c/TR>
c/TABLE>
c/FORM>
c/TD>
c/TR>
c/TABLE>
C/BODY>
C/HTML>
»
Страница Order
Когда пользователь отправляет форму на странице Checkout, запрос попа-
дает на страницу Order. Эта страница вставляет запись в таблицу Orders и до-
бавляет каждый купленный товар в таблицу OrderDetails. Страница Order.jsp
представлена в листинге 18.13.
Разработка приложений е-коммерции
499
connection.setAutoCommit(false);
Statement s = connection.createStatementO;
String sql = "INSERT INTO Orders" +
"(Orderld, ContactName, DeliveryAddress, CCName, CCNumber,
CCExpiryDate)" +
" VALUES" +
(" + orderld + + contactName + + deliveryAddress + +
+ ccName + + ccNumber + + ccExpiryDate + "’)’’;
s.executeUpdate(sql);
// теперь вставляем элементы в таблицу OrderDetails
Enumeration enum = shoppingCart.elements();
while (enum.hasMoreElementsO) {
Shoppingitem item = (Shoppingitem) enum.nextElementO;
sql = "INSERT INTO OrderDetails (Orderld, Productld, Quantity, Price)" +
VALUES (" + orderld + "," + item.productld + "," +
item.quantity + "," + item.price + ")";
s.executeUpdate(sql);
}
s.closeO;
connection.commit();
connection. closeO;
returnValue = true;
catch (SQLException e) {
try {
connection.rollback();
connection. closeO;
catch (SQLException se) {}
return returnValue;
Заключение
В этой главе был показан процесс разработки проекта Burnaby, представляю-
щего собой онлайновый магазин. Целью являлась демонстрация того, как
можно проектировать и разрабатывать приложения Model 2.
В следующей главе, «E-Books на основе XML», мы познакомимся с другим
web-проектом.
496
Глава 18
Л исти н г 18.14 DbBean.java
package com. brainysoftware.burnaby;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.sql.*;
public class DbBean {
public String dbUrl =
public String dbUserName =
public String dbPassword =
public void setDbUrl(String url) {
dbUrl = url;
}
public void setDbUserName(String userName) {
dbUserName = userName;
}
public void setDbPassword(String password) {
dbPassword = password;
}
public Hashtable getCategoriesO {
Hashtable categories = new HashtableO;
try {
Connection connection = DriverManager.getConnection(dbUrl,
dbUserName, dbPassword);
Statement s = connection.createStatementO;
String sql = "SELECT Categoryld, Category FROM Categories" +
ResultSet rs = s.executeQuery(sql);
while (rs.nextO) {
categories.put(rs.getString(l), rs.getString(2) );
}
rs.closeO;
s.close();
connection.close();
}
catch (SQLException e) {}
return categories;
}
public ArrayList getSearchResults(String keyword) {
ArrayList products = new ArrayListO;
E-Books на основе XML
501
рассматриваются различные аспекты издания электронной книги. Сначала мы
создадим оглавление, затем реализуем удобную в навигации независимую от
браузера электронную книгу. Этот проект состоит из клиентской части и сер-
верной части. В конце мы реализуем проект с помощью страниц JavaServer.
Проект может быть реализован с помощью любой технологии Интернета.
Рис. 19.1. Визуальное представление структуры книги
Оглавление
Первым вопросом в системе электронной книги является принятие решения
о том, какой будет структура книги. Книга может иметь любое число глав, и
каждая глава может иметь любое число разделов. Эти разделы в свою очередь
могут иметь подразделы с подподразделами и т. д. Другими словами, оглавле-
ние книги представляется в виде дерева глав/разделов. Оно является иерархи-
ческим.
Теперь необходимо выбрать формат для работы с иерархическими данны-
ми. XML является, вероятно, лучшим решением, доступным сегодня. XML
широко распространен, и его легко редактировать, так как по сути ХМ L явля-
ется обычным текстом. Если структура книги хранится в документе XML, то
при ее изменении понадобится только отредактировать документ XML.
Например, оглавление сетевой книги, показанной на рис. 19.1, представлено
влистинге 19.1.
498
Глава 18
Листинг 18.14 Продолжение
rs.closeO;
s. closeO;
connection. closeO;
}
catch (SQLException e) {}
return products;
}
public Product getProductDetails(int productld) {
Product product = null;
try {
Connection connection = DriverManager.getConnection(dbUrl,
dbUserName, dbPassword);
Statement s = connection.createStatementO;
String sql = "SELECT Productld, Name, Description, Price FROM
Products" +
" WHERE Productld=" + Integer.toString(productld);
ResultSet rs = s.executeQuery(sql);
if (rs.nextO) {
product = new ProductO;
product.id = rs.getlnt(l);
product.name = rs.getString(2);
product.description = rs.getString(3);
product.price = rs.getDouble(4);
}
rs.closeO;
s. closeO;
connection. closeO;
}
catch (SQLException e) {}
return product;
}
public boolean insertOrder(String contactName, String deliveryAddress,
String ccName, String ccNumber, String ccExpiryDatb, Hashtable
shoppingCart) {
boolean returnValue = false;
long orderld = System.currentTimeMillisO;
Connection connection = null;
try {
connection = DriverManager.getConnection(dbllrl, dbUserName,
dbPassword);
E-Books на основе XML
503
Как будет показано в главе 26, объектное дерево JavaScript представляет
собой иерархическую структуру с объектами и отношениями предок-потомок
между объектами. Каждый объект является отдельной сущностью, создавае-
мой с помощью массива JavaScript. Для построения этого объектного дерева
требуются две функции: createObject и append. Функция createObject исполь-
зуется для создания объекта в объектном дереве, а функция append — для
создания отношений между двумя объектами.
Объект в дереве представляет книгу, главу или заголовок. Отметим, что под
заголовком понимается любой заголовок: headingl, heading? ... headingn. Каж-
дый объект имеет уникальный идентификатор, используемый для поиска этого
объекта, заголовок, выводимый на экран, и URL содержимого HTML, являю-
щегося содержимым объекта. В дополнение к трем этим элементам объект так-
же имеет состояние: открыт или закрыт. Открытый объект показывает всех сво-
их потомков, если они есть. Рядом с узлом закрытого объекта, который имеет
потомков, изображается знак плюс (+). Чтобы открыть этот объект, нужно
щелкнуть мышью на знаке плюс. Открытый объект имеет знак минус (-). Что-
бы закрыть этот объект, следует щелкнуть мышью на знаке минус. Более тех-
ническими терминами открытие и закрытие объекта называются развертыва-
нием и сворачиванием объекта.
Для создания объекта нужно вызвать функцию createObject, передав ей уни-
кальный идентификатор, заголовок и URL содержимого. Эта функция пред-
ставлена в листинге 19.2.
Листинг 19.2 Функция createObject
function createObject(id, title, url) {
var element = new ArrayO;
element[0] = id;
elemental] = title;
element[2] = url;
element[3] = 0;
return element;
}.
Например, создадим объект, который представляет книгу:
var е1 = create0bject(1, "Fantasies for Dummies", "bookTitle.html");
Этот код создает объект с идентификатором 1, с заголовком «Fantasies for
Dummies» и с «bookTitle.html» в качестве URL. Напомним, что объект в
JavaScript является простым массивом. Идентификатор хранится как первый
элемент массива, заголовок — как второй, URL — как третий. Четвертый эле-
мент (element[3]) хранит состояние объекта: 0 указывает, что объект закрыт; 1 —
что объект открыт. По умолчанию объект создается закрытым.
19
E-Books
на основе XML
каждым днем Интернет становится все более популярной, и все больше
информации доступно в сети. Какая-то информация представляется просто с
помощью HTML, адругая структурируется, становясь похожей на книги. Это,
например, сетевые руководства, электронные книги и тому подобное.
Электронная книга (e-book) так же, как и печатная, имеет оглавление и ряд
глав. Однако электронная книга находится в сети и может обладать свойства-
ми, которые недоступны в печатных книгах. Например, издатель может сде-
лать удобной навигацию по электронной книге, обеспечив постоянный вы-
вод оглавления. Когда читатель захочет перейти к другой главе, он сразу сделает
это, щелкнув мышью на ссылке этой главы.
Выбор формата для электронной книги является важным решением. Наи-
более популярный формат выводит электронное оглавление в окне web-брау-
зера. На рис. 19.1 оглавление отображается в рамке HTML слева. Справа вы-
водятся содержание или название выбранной главы в формате HTML.
E-book имеет и другие преимущества по сравнению с печатной книгой. Во-
первых, если разместить e-book в Интернете, то она будет доступна неограни-
ченному числу людей. Второе, электронную книгу можно обновлять практи-
чески в любое время. Выпуск следующего издания книги не влечет
дополнительных расходов на печать. Основной недостаток состоит в том, что
читателям требуется соединение с Интернетом. Однако доступ в Интернет ста-
новится все более обычным явлением, и этот недостаток постепенно исчез-
нет. Также неактуальным является вопрос об удобстве чтения. В этой главе
E-Books на основе XML
505
Проект
В этой реализации проект состоит из двух файлов: index.jsp и Toe Bean Java.
Кэнечно, требуются также файлы HTML с содержимым; но эти статические
файлы здесь не рассматриваются. Еще необходимы два пустых файла с имена-
ми toc.html и content.html, так как набор рамок должен иметь физические стра-
ницы для заполнения рамок.
Файл index.jsp является основным файлом, который содержит функции
JavaScript для создания и манипулирования объектным деревом. Его код пред-
ставлен в листинге 19.5.
Листинг 19.5 Файл index.jsp
<jsp:useBean id=’TocBeanId” scope="page" class=’TocBean" />
<HTML>
<HEAD>
<TITLE>Table of Contents</TITLE>
<SCRIPT LANGUAGE^’JavaScript">
<!- Скрытый сценарий /
var selectedElementld = 1;
var root;
function createObject(id, title, url) {
var element = new ArrayO;
element[0] = id;
element[1] = title;
element[2] = url;
element[3] = 0;
return element;
}
function append(parent, child) {
parent[parent.length] = child;
}
function redrawTree() {
var doc = top.treeFrame.window.document;
doc.clear();
doc.write(”<HTML><HEAD><ST7LE TYPE=\”text/css\">\n" +
''.normal:link { text-decoration:none; color:black; font-family:verdana;
font-size: 8pt; }\n” +
".selected:link { text-decoration:none; coloured; font-family:verdana;
font-size: 9pt; }\n" +
502
Глава 19
Листинг 19.1 Содержание книги в формате XML
<?xml version=”1.0” encoding=”IS0-8859-1”?>
<book title=”Fantasies for Dummies” url=”bookTitle.html">
<chapter title=”Introduction to Disneyland” url=”ch1.html”>
<heading1 title=”Meet Mickey” url="mickey.html”>
</heading1>
</chapter>
<chapter title=”Going Inside” url=”ch2.html”>
<heading1 title=”Meet Donald” url=”donald.html">
<heading2 title="Kwik” url=”kwik.html”>
<heading3 title=”You can’t escape” url=”noEscape.html” />
</heading2>
<heading2 title=”Kwek” url=”kwek.html">
</heading2>
<heading2 title="Kwak” url=”kwak.html” />
</heading1>
</chapter>
<chapter title=”Alice in Disneyland” url=”Ch3.html” />
</book>
Как можно видеть, корнем оглавления является тег <book>. В связи с труд-
ностями именования множества элементов, в этом примере разделы глав на-
зываются «heading». Поэтому вместо subchapter мы имеем heading!, вместо sub-
subchapter — heading2. heading! может содержать подзаголовок heading3,
heading3 может иметь подзаголовок с именем heading4, и т. д.
Трансляция XML в объектное дерево
Наличие хорошо структурированного содержимого книги в формате XML
только одна сторона проблемы; нужно еще обеспечить ее вывод на экран. Что-
бы книгу можно было читать в максимально возможном числе браузеров, не-
обходимо использовать JavaScript.
О реализации объектного дерева на JavaScript
рассказывается в главе 26.
Поскольку при изменении внешнего вида объектного дерева JavaScript дол-
жны производиться очистка и запись в объект документа, проще работать с
рамками (frame). Набор рамок (frameset), который является основным доку-
ментом, запрашиваемым пользователем, содержит весь код JavaScript и две
рамки. Левая рамка показывает структуру книги, а правая — выбранную стра-
ницу HTML.
E-Books на основе XML
507
if (level>0)
if (lastNode) { // последняя папка в массиве
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
’’<IMG SRC='images/LastNodeMinus.gif+
”BORDER=O WIDTH=16 HEIGHT=22>" + ”</A>");
else
doc.write(nodeLink +
”<IMG SRC=’images/LastNodePlus.gif +
” BORDERS WIDTH=16 HEIGHT=22>” + "W);
}
else
doc.write(’’<IMG SRC='images/LastNode.gif +
” WIDTH=16 HEIGHT=22>”);
leftside += "<IMG SRC=’images/blank.gif+
” WIDTH=16 HEIGHT=22>";
}
else { // не последняя папка
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
"<IMG SRC=’images/NodeMinus.gif +
” BORDER=O WIDTH=16 HEIGHT=22>” + ”</A>”);
else
doc.write(nodeLink +
”<IMG SRC=’images/NodePlus.gif’” +
” BORDER=O WIDTH=16 HEIGHT=22></A>");
}
else
doc.write("<IMG SRC=’images/Node.gif+
" WIDTH=16 HEIGHT=22>");
leftside += "<IMG SRC=’images/VertLine.gif'+
" WIDTH=16 HEIGHT=22>”;
}
doc.write("<TD> ”);
if (id == selectedElementId)
doc.write("<A CLASS=selected’’);
else
doc.write(”<A CLASS=norfliar’);
504
Глава 19
Для создания отношения между двумя объектами используется функция
append (см. листинг 19.3). Функция получает два аргумента: объект, который
будет предком в отношении, и объект, который предназначен быть потомком
в отношении.
Листинг 19.3 Функция append
function append(parent, child) {
parent[parent.length] = child;
Чтобы создать документ XML, приведенный в листинге 19.1, необходимо
последовательно вызывать функции createObject и append, пока не будет по-
лучено все объектное дерево. Часть кода представлена в листинге 19.4.
Л исти нг 19.4 Создание объектного дерева JavaScript на основе
структуры, приведенной в листинге 19.1
var е1 = createObject(1, "Fantasies for Dummies", "bookTitle.html");
var e2 = create0bject(2, "Introduction to Disneyland", "ch1.html");
append(e1 , e2);
var e3 = create0bject(3, "Meet Mickey", "mickey.html");
append(e2 , e3);
var e4 = create0bject(4, "Going Inside", "ch2.html");
append(e1 , e4);
var e5 = create0bject(5, "Meet Donald", "donald.html”);
append(e4 , e5);
var e6 = create0bject(6, "Kwik", "kwik.html");
append(e5 , e6);
var e7 = createObject(7, "You can't escape", "noEscape.html");
append(e6 , e7);
var e8 = create0bject(8, "Kwek", "kwek.html");
append(e5 , e8);
var e9 = create0bject(9, "Kwak", "kwak.html");
append(e5 , e9);
var e10 = createObject(10, "Alice in Disneyland", "Ch3.html”);
append(e1 , elO);
Отметим, что для каждого объекта задается уникальный идентификатор.
Проблемой при создании объектного дерева JavaScript на основе докумен-
та XML является трансляция документа XML (листинг 19.1) в код JavaScript
(листинг 19.4). Если изменяется оглавление, документ XML также меняется.
Это должно отражаться объектами JavaScript и отношениями между объекта-
ми. Следовательно, требуется серверная обработка для чтения файла ХМ L каж-
дый раз, когда запрашивается электронная книга. Необходимо гарантировать,
что оглавление, которое видит пользователь, является текущим.
E-Books на основе XML
509
TocBeanld.getToc(”toc.xml");
out.println(TocBean!d.getString());
%>
// конец сценария ->
</SCRIPT>
</HEAD>
<FRAMESET onLoad="initialize()" cols="225,*">
<FRAME SRC="toc.html” NAME="treeFrame">
<FRAME SRC="content.html" NAME="objectFrame”>
<NOFRAMES>
<BODY>
Unfortunately, your browser cannot render frames.
Please upgrade your browser.
</BODY>
</NOFRAMES>
</FRAMESET>
</HTML>
Воспроизводится следующий файл HTML:
<HTML>
<HEAD>
<TITLE>Table of Contents</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!- Скрытый сценарий
var selectedElementld = 1;
var root;
function createElement(id, title, url) {
var element = new ArrayO;
element[0] = id;
element[1] = title;
element[2] = url;
element[3] = 0;
return element;
}
function append(parent, child) {
parent[parent.length] = child;
}
506
Глава 19
Листинг 19.5 Продолжение
"</STYLE></HEAD>”);
doc. write(”<BODY BGCOLOR= ’ #ffffff
redrawNode(root, doc, 0, 1,
doc.write("</B0DY></HTML>");
doc.close();
}
function closeBook() {
root[3] = 0;
redrawTreeO;
}
function openBook() {
root[3] = 1;
redrawTreeO;
}
function redrawNode(
foldersNode, doc, level, lastNode, leftside) {
var j=0;
var i=0;
var id = foldersNode[0];
var title = foldersNode[1];
var url = foldersNode[2];
var hasSubNode = (foldersNode.length>4);
var expanded = foldersNode[3];
doc.write(”<TABLE BORDER=O CELLSPACING=O" + ’’CELLPADDING^X);
doc.write("<TR><TD VALIGN=middle NOWRAPX);
doc.write(leftSide);
if (id==1) { // это книга
if (root[3]==0)
doc. write("<A HREF=' javascript: top. openBook() ’ xlMG SROimages/
ClosedBook.gif B0RDER=0x/A>”);
else
doc.write("<A HREF=' javascript:top. closeBook() ’ xlMG SRC=images/
OpenBook.gif B0RDER=0></A>”);
}
var nodeLink =
”<A HREF=’javascript:top.clickNode(” + id +
E-Books на основе XML
511
doc. write("<A HREF=’ j avascript: top. openBook() ’XIMG SRC=images/
ClosedBook.gif B0RDER=0x/A>”);
else
doc. write( "<A HREF= ’ javascript:top. closeBook()’ XIMG SRC=images/
OpenBook.gif B0RDER=0x/A>”); >
}
var nodeLink =
"<A HREF=’javascript:top.clickNode(” + id +
if (level>0) /
if (lastNode) { // последняя папка в массиве
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
"<IMG SRC=’images/LastNodeMinus.gif +
•' BORDER=O WIDTH=16 HEIGHT=22>" + "</A>”);
else
dpc.write(nodelink +
”<IMG SRC='images/LastNodePlus.gif +
" BORDER=O WIDTH=16 HEIGHT=22>” + ”</A>");
}
else
doc.write(”<IMG SRC='images/LastNode.gif +
” WIDTH=16 HEIGHT=22>”);
leftside += "<IMG SRC=’images/blank.gif+
" WIDTH=16 HEIGHT=22>’’;
}
else { //не последняя папка
if (hasSubNode) {
if (expanded)
doc. write(nodel_ink +
"<IMG SRC='images/NodeMinus.gif +
" BORDER=O WI0TH=16 HEIGHT=22>” + ”</A>”);
else
doc. write(nodel_ink + i
XIMG SRC=’images/NodePlus.gif+
” BORDER=O WIDTH=16 HEIGHT=22X/A>");
}
else
doc.write("<IMG SRC='images/Node.gif +
” WIDTH=16 HEIGHT=22>‘');
508
Глава 19
Листинг 19.5 Продолжение
doc.write(” HREF='javascript:top.clickElement(”
+ id + ”, \”” + url + "\”)’>” + title + ”</A>”);
doc. wnte( ”</TABLE>”)
if (hasSubNode && expanded) {
level++;
for (i=4; KfoldersNode. length; i++)
if (i==foldersNode.length-1)
redrawNode(
foldersNode[i], doc, level, 1, leftside);
else
redrawNode(
foldersNode[i], doc, level, 0, leftside);
}
}
function clickElement(id, url) {
selectedElementld = id;
redrawTree();
parent.frames[1].location = url;
}
function toggleNode(foldersNode, folderld) {
if (foldersNode[0]==folderId)
foldersNode[3] = 1 - foldersNode[3];
else if (foldersNode[3])
for (var i=4; i< foldersNode.length; i++)
toggleNode(foldersNode[i], folderld);
}
function clickNode(folderld) {
toggleNode(root, folderld);
redrawTree();
}
function initializeO { ।
root = e1;
redrawTree();
}
<%
Books на основе XML
513
function clickNode(folderld) {
toggleNode(root, folderld);
redrawTreeO;
}
function initialize() {
root = el;
redrawTreeO;
}
var e1 = createElementH, "Fantasies for Dummies", "bookTitle.html");
var e2 = createElement(2, "Introduction to Disneyland", "ch1.html");
append(e1 , e2);
var e3 = createElement(3, "Meet Mickey", "mickey.html");
append(e2 , e3);
var e4 = createElement(4, "Going Inside", "ch2.html");
append(e1 , e4);
var e5 = createElement(5, "Meet Donald", "donald.html");
append(e4 , e5);
var e6 = createElement(6, "Kwik", "kwik.html");
append(e5 , e6);
var e7 = createElement(7, "You can’t escape", "noEscape.html");
append(e6 , e7);
var e8 = createElement(8, "Kwek", "kwek.html");
append(e5 , e8);
var e9 = createElement(9, "Kwak", "kwak.html");
append(e5 , e9);
var e10 = createElement(10, "Alice in Disneyland", "Ch3.html");
append(e1 , e10);
// конец сценария ->
</SCRIPT>
</HEAD>
<FRAMESET onLoad="initialize()" cols=”225,*">
<FRAME SRC="toc.html" NAME="treeFrame">
<FRAME SRC="content.html" NAME="objectFrame">
<NOFRAMES>
<B0DY> Unfortunately, your browser cannot render frames.
Please upgrade your browser.
</B0DY>
</NOFRAMES>
</FRAMESET>
</HTML>
510
Глава 19
Листинг 19.5 Продолжение
function redrawTree() {
var doc = top.treeFrame.window.document;
doc.clear();
doc.write(”<HTML><HEAD><STYLE TYPE=Vtext/css\'’>\n’’ +
”. normal-.link { text-decoration: none; color:black; font-family:verdana;
font-size: 8pt; }\n” +
".selected:link { text-decoration:none; color:red; font-family:verdana;
font-size: 9pt; }\n" +
"</STYLEX/HEAD>”);
doc.write("<BODY BGCOLOR=’#ffffff’>”);
redrawNode(root, doc, 0, 1,
doc. write( ”</B0DYX/HTML>");
doc.closeO;
}
function closeBook() {
root[3] = 0;
redrawTreeO;
}
function4 openBook() {
root[3] = 1;
redrawTreeO;
}
function redrawNode(
foldersNode, doc, level, lastNode, leftside) {
var j=0;
var i=0;
var id = foldersNode[0];
var title = foldersNode[1];
var url = foldersNode[2];
var hasSubNode = (foldersNode.length>4);
var expanded = foldersNode[3];
doc.write("<TABLE BORDER=O CELLSPACING=O" +
" CELLPADDING=O>");
doc.write(”<TR><TD VALIGN=middle NOWRAPX);
doc.write(leftSide);
if (id==1) { // это книга
if (root[3]==0)
E-Books на основе XML
515
Затем вызывается функция redrawNodec передачей переменных root и doc:
redrawNode(root, doc, 0, 1,
В doc выводятся закрывающие теги </BODY> и </HTML>:
doc. write( "</B0DYx/HTML>");
И наконец документ закрывается:
doc. closeO;
redrawNode. Это рекурсивная функция, которая перерисовывает структуру
книги.
closeBook. Переводит корневой объект в свернутое состояние:
function closeBookO {
root[3] = 0;
redrawTreeO;
}
openBook. Разворачивает корневой объект:
function openBookO {
root[3] = 1;
redrawTreeO;
}
clickElement. Эта функция вызывается, когда пользователь щелкает мышью
на элементе объектного дерева. Она получает два аргумента: id и url.
Если пользователь щелкает мышью на элементе, элемент с иденти-
фикатором id становится выбранным.
Выбранный элемент изображается другим цветом; следовательно, при
выборе нового элементадерево должно быть перерисовано с помощью фун-
кции:
redrawTreeO;
Эта функция изменяет содержимое второй рамки с помощью
frames[l].location = url. Но поскольку функция clickElement вызывается из
рамки tree Frame, правильным будет код:
parent.frames[1].location = url;
Отметим, что url является вторым аргументом, передаваемым в фун-
кцию.
512
Глава 19
Листинг 19.5 Продолжение
leftside += "<IMG SRC=’images/\/ertLine.gif +
” WIDTH=16 HEIGHT=22>”;
>
doc.write("<TD> ");
if (id == selectedElementld)
doc.write(”<A CLASS=selected”);
else
doc.write("<A CLASS=normal");
doc.write(" HREF= ’ javascript:top. clickElement(”
+ id + ", \"" + url + "\")’>" + title + "</A>”);
doc.write(”</TABLE>")
if (hasSubNode && expanded) {
level++;
for (i=4; KfoldersNode.length;i++)
if (i==foldersNode.length-1)
redrawNode(
foldersNode[i], doc, level, 1, leftside);
else
redrawNode(
foldersNode[i], doc, level, 0, leftside);
}
}
function clickElement(id, url) {
selectedElementld = id;
redrawTreeO;
parent.frames[1].location = url;
}
function toggleNode(foldersNode, folderld) {
if (foldersNode[0]==folderId)
foldersNode[3] = 1 - foldersNode[3];
else if (foldersNode[3])
for (var i=4; i< foldersNode.length; i++)
toggleNode(foldersNode[i], folderld);
}
E-Books на основе XML
517
Листинг 19.6 Файл TocBean.java
import java.io.★;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import com.sun.xml.tree.★ ;
public class TocBean {
DocumentBuilderFactory factory;
Document document;
StringBuffer output = new StringBuffer(2048);
int objectld = 1;
public String getStringO {
return output.toStringO;
}
public void getToc(String tocFilename) {
factory = DocumentBuilderFactory.newlnstance();
try {
DocumentBuilder builder = factory.newDocumentBuilderO;
document = builder.parse( new File(tocFilename) ).;
Node rootNode = document. getFirstChildO;
NamedNodeMap nnm = rootNode.getAttributesO;
if (nnm’= null) {
Node title = nnm.getNamed!tem("title" );
Node url = nnm.getNamedItem("url");
// вывод первого элемента следующей формы
// var е1 = createObject(1, "book title", "book url.html");
output.append("var e1 = createObject(1, V” +
title.getNodeValue() + "\", \"" +
url. getNodeValueO + "\");\n");
}
getChildNode(rootNode, output);
}
catch (Exception e) {
e. toStringO;
}
}
void getChildNode(Node parentNode, StringBuffer s) {
514
Глава 19
Как можно видеть, файл index.jsp содержит две рамки: toc.html и
content.html. Первая рамка называется treeFrame, а вторая — objectFrame:
<FRAMESET onLoad="initialize()" cols="225, *">
<FRAME SRC="toc.html" NAME="treeFrame">
<FRAME SRC="content.html" NAME="objectFrame">
<NOFRAMES>
<BODY>
Unfortunately, your browser cannot render frames.
Please upgrade your browser.
</BODY>
</NOFRAMES>
</FRAMESET>
В рамке treeFrame выводится структура книги. Рамка objectFrame показы-
вает содержимое выбранной страницы. Например, если пользователь щел-
кает мышью на заголовке первой главы, выводится содержимое Chapter 1
(см. рис. 19.1).
Файл index.jsp содержит также следующие функции JavaScript:
createObject. Эта функция создает объект array с четырьмя элементами. Каж-
дый элемент имеет уникальный идентификатор (см. листинг 19.3).
append. Создает отношение предок-потомок между двумя объектами.
redraw’D’ee. Эта функция отвечает за очистку документа treeFrame и за за-
пись в него нового содержимого, когда пользователь взаимодейству-
ет с объектным деревом. В наборе рамок (index.jsp) на документ
treeFrame ссылаются как на tree Frame, window.document. Но поскольку
эта функция будет вызываться со страницы treeFrame, то на документ
ссылаются следующим образом:
var doc = top.treeFrame.window.document;
Переменная doc здесь ссылается на объект document рамки treeFrame.
Функция redrawTree очищает документ и записывает некоторый стати-
ческий HTML в doc следующим образом:
doc.clear();
doc.write("<HTML><HEAD><STYLE TYPE=\"text/css\">\n" +
". normal:link { text-decoration:none; color:black; " +
"font-family:verdana; font-size: 8pt; }\n" +
". selected:link { text-decoration:none; color:red; " +
"font-family:verdana; font-size: 9pt; }\n" +
"</STYLE></HEAD>");
doc.write("<BODY BGCOLOR='#ffffff’>");
E-Books на основе XML
519
getToc()
Метод getToc() получает один аргумент: имя файла документа ХМ L, содер-
жащего оглавление книги. Этот метод извлекает корневой объект (тег <book>)
как объект с идентификатором 1 и добавляет эту информацию в StringBuffer
output. При использовании документа XML, приведенного в листинге 19.1,
output должна быть такой:
var е1 = create0bject(1, "Fantasies for Dummies", "bookTitle.html");
Затем вызывается метод getChildNode для обработки остальных тегов. Отме-
тим, что objectld используется для регистрации объектного идентификатора.
getChildNode()
Этот метод читает тег, имя которого начинается с «chapter» или «heading».
Найденный тег добавляется в StringBuffer.
Предварительное формирование
оглавления
Каждый раз, когда кто-то читает электронную книгу, документ XML, содер-
жащий оглавление, должен преобразовываться в HTML/JavaScript-эквивалент
иерархической структуры. Это требует больше времени, чем простая отправка
статической страницы в браузер. Оглавление вряд ли меняется каждый день,
поэтому можно заранее сформировать его, чтобы оно было доступно как ста-
тическая страница. С этой целью можно использовать методы кэширования
выходных данных (см. главу 15).
Заключение
В этой главе мы создали приложение электронной книги, оглавление которой
основывается на документе XML. При изменении структуры необходимо об-
новить только документ XML.
516
Глдва 19
clickNode. Эта функция вызывается, когда пользователь щелкает мышью
на узле. Она вызывает функцию toggleNode и функцию redrawTree:
toggleNode(root, folderld);
redrawTreeO;
toggleNode. Сворачивает или разворачивает указанный узел. Для этого фун-
кция должна сначала найти узел с правильным идентификатором. За-
тем функция изменяет четвертый элемент массива объекта с 0 на 1
или с 1 на 0:
if (filderNode[0] == folderld)
foldersNode[3] = 1 - foldersNode[3];
Если текущий узел не является выбранным узлом, функция продолжает
поиск, вызывая себя рекурсивно:
else if (foldersNode[3])
for (var i=4; i< foldersNode.length; i++)
toggleNode(folderNode[i], folderld);
index.jsp содержит код, в котором используется TocBean.java:
<%
Т ocBeanld.getTос(”tос.xml”);
out.println(TocBean!d.getString());
%>
Эти две строки используют JavaBean, который транслирует оглавление
XML (см. листинг 19.1) в объектное дерево JavaScript (см. листинг 19.4).
TocBean.java
Как упоминалось ранее, для трансляции документа XML, содержащего ог-
лавление, в код JavaScript требуется серверный код, подобный коду листинга
19.4. TocBean.java является Bean, который отвечает за это (см. листинг 19.6).
Прежде всего требуется синтаксический анализатор XML на Java. Здесь
используется анализатор, предложенный компанией Sun; его можно получить
по адресу http://java.sun.com/xml/index.html. Если будет применяться анали-
затор другого поставщика, можно будет использовать код без каких-либо мо-
дификаций, за исключением оператора import.
Управление документами на основе Web
521
хранение этих файлов в каталоге вне виртуального хоста (виртуального ката-
лога), чтобы они не были видны в Интернете/интранете, когда пользователь
вводит URL. Чтобы загрузить файл, пользователь должен направить браузер
на специальную страницу и ввести имя пользователя и пароль. При успешной
аутентификации web-сервер извлекает файл и посылает его браузеру.
В этой главе рассматривается концепция управления документами. Это
важная тема, которая не часто обсуждается в книгах по программированию
Web или Java для начинающих. Изучение данной темы основывается на docman,
коммерческом проекте по управлению документами в Web от компании
Brainysoftware.com.
В настоящей главе описываются базовые концепции этого проекта. Поняв
их, вы сможете расширить функциональность согласно вашим потребностям.
Четырьмя ключевыми свойствами являются:
• Структура базы данных проекта
• Программная загрузка файла (download)
• Пересылка файла (upload)
• Клиентский инструментарий, который делает перемещение по доку-
менту таким же простым, как применение Windows Explorer. Факти-
чески пользовательский интерфейс будет знаком любому пользова-
телю Windows.
ГПрши1СЧ311ис^ Программная загрузка и пересылка файлов обсуждаются в
J главах 12 и 13 и здесь не повторяются. Работа инструмента
перемещения описывается в главе 26.
Проект docman
Если говорить кратко, то docman является Windows Explorer в Web. Это Ин-
тернет-приложение реализуется с помощью технологии серверных страниц
Java для представления иерархической файловой системы на базе Web анало-
гично тому, как это делается в среде, отличной от Web. Существуют два типа
объектов: файлы и папки/каталоги. Файлом является любой документ, кото-
рый хранится в файловой системе. Папка или каталог — это контейнер, кото-
рый может содержать любое число файлов и других папок. Если папка В рас-
положена в папке А, то папка В называется подпапкой папки А. Папка А
является родительской папкой папки В и любого файла, который содержится
в папке А. Самая верхняя папка в иерархии называется корнем (root). Это пер-
вая папка, которую видит пользователь, когда входит в систему. Очевидно, что
корень не имеет родительской папки.
Docman поддерживает любое число пользователей. Каждый пользователь
имеет свой собственный набор полномочий. Это означает, что можно разре-
шить пользователю просматривать файлы в одном каталоге, но не в другом.
518
Глава 19
Листинг 19.6 Продолжение-
try {
int currentObjectld = objectld;
Node childNode = parentNode.getFirstChildO;
// получить названия и URL глав и разделов
while (childNode != null) {
String nodeName = childNode.getNodeNameO;
if (nodeName’= null && (nodeName.equalsC’chapter") ||
nodeName.startsWith(”heading”)) ) {
objectld++;
NamedNodeMap nnm = childNode.getAttributes();
if (nnm!= null) {
Node title = nnm.getNamedItem(’’title” );
Node url = nnm.getNamedItem("url”);
s.append(’’var e’’ + objectld + ” =
createObject(° + objectld + ”, +
(title==null? ’’null” : + title.getNodeValueO +
”\””) + +
(url==null? "null" : + url.getNodeValueO +
’V”) + ”);\n”);
s.append("append(e” + currentObjectld + ’’ , +
”e” + objectld + ”);\n”);
}
if (childNode.hasChildNodesO)
getChildNode(childNode, s);
}
childNode = childNode. getNextSiblingO;
} // конец цикла while
}
catch (Exception e) {
System.out.println(”Error:” + e.toStringO );
}
}
} // конец класса
JavaBean считывает файл XML, передаваемый ему методом getToc(), и фор-
мирует строку в StringBuffer с именем output. Затем можно прочитать output с
помощью метода getString и послать ее в web-браузер.
Управление документами на основе Web
523
Н иже обсуждаются структура базы данн ых и все составля ющие проект стра-
ницы JSP и Bean. Проект готов к развертыванию, и его можно найти в катало-
ге software/docman на сайте www.lory-press.ru.
Структура базы данных
В этой реализации информация о файле или папке хранится в реляцион-
ной базе данных. Однако все файлы находятся в одной папке вне виртуально-
го корня или виртуального каталога, независимо от их положения в иерархии.
Пока это кажется странным, но позже вы поймете, в чем дело.
Способ хранения файлов не имеет значения, если они не являются напря-
мую доступными пользователю, который вводит URL в web-браузере, и если
информация в структуре файловой системы остается нетронутой. В этой реа-
лизации каждый объект (файл или папка) обладает следующими свойствами:
• Id. Уникальный идентификатор объекта.
• Parent Id. Идентификатор родительской папки.
• ТУре. Тип объекта: папка или файл. Папка имеет тип 0, а файл — тип 1.
* Name. Имя объекта, т. е. имя файла или имя папки.
Особый интерес представляет запись, Id объекта которой равен 1. Она на-
зывается корнем (root). Корень является первым объектом в системе и имеет
Id предка, равный 0; это означает, что он не имеет предка.
Относящаяся к объектам информация хранится в таблице с именем Objects.
Как можно видеть, Id объекта и Id предка каждого объекта дают достаточно
информации об иерархической структуре всех папок и файлов.
Структура табл и цы Objects в базе дан н ых Access представлена в табл и це 20.1.
Таблица 20.1. Структура таблицы Objects
Имя поля
ID
Parent ID
Type
Name
Ъш данных
Number
Number
Number
Text
С Примечание^ Для создания этих таблиц можно использовать любой другой
сервер базы данных. Необходимо только отметить, что сервер
базы данных должен поддерживать подзапросы, так как они
применяются в операторах SQL, служащих для доступа к базе
данных. Большинство популярных серверов баз данных, такие
как Microsoft SQL Server, Oracle, Sybase и IBM DB2,
поддерживают подзапросы.
Управление
документами
на основе Web
защитить файл в Web, чтобы только авторизованные пользователи
могли обращаться к нему? Некоторые просто помещают файл в один из вир-
туальных подкаталогов хоста и не предоставляют никакой ссылки, чтобы толь-
ко знающие полный путь доступа могли его загрузить. Очевидно, что этот ме-
тод не может применяться для защиты корпоративных конфиденциальных
документов.
Часто применяются web-сервер и метод аутентификации операционной
системы, так что при каждой попытке загрузить файл приходится вводить пра-
вильное имя пользователя и пароль. Это хороший подход, но слишком про-
стой. Одна из его проблем состоит в том, что необходимо сообщать пароль
всем, кто должен иметь возможность загружать файл.
Этот подход подобен применению общего имени пользователя и пароля в
NT или UNIX всеми сотрудниками корпорации. Однако люди не используют
совместно пароли по той же причине, по которой они не делятся секретами.
Помимо вопросов секретности и защищенности, этот подход не годится в том
случае, когда требуется дополнительная функциональность, такая как конт-
роль версий документов и запись всей деятельности без использования фай-
лов журналов web-сервера.
Другим решением является сохранение файлов в базе данных и предостав-
ление доступа к ним только после аутентификации пользователя. Однако раз-
мещение блоков больших двоичных объектов (BLOB) в базе данных выполня-
ется очень медленно и требует много ресурсов. Лучшим решением является
Управление документами на основе Web
525
В root есть два каталога: Marketing и Sales. Имеется также файл
README.TXT, предназначенный для первого знакомства. (Этот файл содер-
жит инструкции от системного администратора по использованию системы.)
Сотрудники отдела маркетинга хранят свои папки и файлы в папке Marketing.
Доступ к этой папке имеют только сотрудники отдела маркетинга. Папка Sales
предназначена для отдела продаж, и только сотрудники из отдела продаж мо-
гут к ней обращаться.
Содержимое таблицы Objects показано в таблице 20.4.
С Примечание присваивается объекту во время размещения файла; это
~ означает отсутствие гарантий, что файлы в папке будут иметь
последовательные ID объектов. Однако это не имеет
значения, так как ID объектов и другие метаданные не
предоставляются пользователю. Пользователи видят только
иерархическую файловую систему.
Отметим также, что файлы размещаются в одном единственном
каталоге с помощью своих объектных Id. Это означает, что файл
Strategy.doc хранится как 3. Поскольку для всех файлов заданы в
качестве имен уникальные номера, можно иметь два или три
файла с одним и тем же файловым именем. Когда файл
посылается в качестве приложения, система будет
транслировать объектный ID в исходное имя файла.
Таблица 20.4. Данные в таблице Objects
ID ParentID ТУре Name
1 0 0 root
2 1 0 Marketing
3 2 1 Strategy.doc
4 1 1 READM E.TXT
5 1 0 Sales
6 2 0 Managers
7 6 1 evaluation.doc
8 6 1 remuneration .doc
9 5 1 europe.doc
10 5. 1 asia.doc
11 5 1 america.doc
Для упрощения в примере действуюттолько два пользователя: входное имя
одного — boni, а другого — bulbul. Содержимое таблицы Users показано в таб-
лице 20.5.
522
Глава 20
Это будет полезно в организации с различными отделами. Например, сотруд-
ники отдела маркетинга могут видеть документы, принадлежащие отделу мар-
кетинга, но не файлы, имеющие отношение к отделу информационных тех-
нологий (IT). Сотрудники отдела IT имеют доступ кдокументам IT, но не имеют
доступа к архиву отдела кадров с окладами сотрудников. Пользователи могут
просматривать систему, начиная с корня вниз по иерархии, если у них есть
соответствующие полномочия.
На рис. 20.1 показан пользовательский интерфейс docman. С левой сторо-
ны выводится объектное дерево.
Рис. 20.1. Пользовательский интерфейс проекта docman
Системные требования
Проект docman использует два JavaBean (их можно найти на www.lory-
press.ru):
• Bean BrainySoftware.com File Download для программной загрузки
файлов
• Bean BrainySoftware.com File Upload версия 2.0 для пересылки файлов
Прежде всего необходимо скопировать файлы .jar в каталоги software/
File Download Bean и software/FileUpIoadBean каталога WEB-IN F/lib каталога
приложения и перезапустить Tomcat. Ознакомьтесь с лицензионным согла-
шением двух Bean. Если вы хотите написать свои собственные Bean, главы 12
и 13 предоставляют для этого достаточно информации.
Управление документами на основе Web
527
• DownloadObject.jsp. Файл jsp, который извлекает файл и посылает его в
браузер в виде приложения.
DBBean
D В Bean делает большую часть работы. Этот Bean используется для установ-
ки соединения с базой данных, для получения всех объектов определенной папки
и получения имени файла для загрузки. Ос
важными являются два метода
соединения (connect). Первый метод принимает три параметра: url, userName и
password, где url — это JDBC URL, a userName и password — данные, которые
требуются для установки соединения с базой данных. Эти имя и пароль при-
меняются только системой и обозначаются в Bean как dbUserName и
dbPassword. Конечные пользователи приложения ничего об этом не знают. Не
путайте их с именем пользователя и паролем, которые принадлежат пользова-
телю и служат для получения доступа к определен ному объекту втаблицахбазы
данных.
Второй метод соединения (connect) не имеет параметров. Он применяется, если
надо использовать в Bean значения по умолчанию или если эти три аргумента
были переданы в предыдущих операторах (url, DbUserName и dbPassword зада-
ются с помощью методов setDbUrl, setDbUserName и setDbPassword соответствен-
но). С базой данных связан метод setJDBCDriverName. Он вызывается, если имя
применяемого драйвера отличается от «sun.jdbc.odbc.JdbcOdbcDriver».
В этом примере используется драйвер J DBC-ODBC для доступа к базе дан-
ных Microsoft Access в системе Windows. Необходимо создать имя источника
данных (DSN) docman для работы приложения. Выбор базы данных является
решением программиста. Необходимо только проверить, что применяется
правильный драйвер JDBC. Как говорилось ранее, база данных должна под-
держивать подзапросы.
Приведем код методов соединения:
try {
Class.forName( JDBCDriverName );
connection = DriverManager.getConnection(url,
userName, password);
}
catch (Exception e) {}
Г Примечание^ Код для методов соединения зависит от драйвера JDBC базы
1 1" данных. Подробности можно найти в документации драйвера
JDBC.
DBBean имеет также static final StringcHMeneM DATA_PATH. Она содержит
физический путь доступа к специальному каталогу, в котором будут находиться
все размещенные файлы. Можно изменить значение этой строки, задав
524
Глава 20
В этой реализации применяются также таблицы Users и Permissions. Табли-
ца Users хранит информацию о пользователях, а таблица Permissions — ин-
формацию о том, какие пользователи имеют полномочия доступа к файлам и
папкам. Структуры этих двух таблиц в базе данных Access представлены в таб-
лице 20.2 и таблице 20.3.
Таблица 20.2. Структура таблицы Users
Имя поля
ID
UserName
Password
Ъш данных
Number
Text
Text
Таблица 20.3. Структура таблицы Permissions
Имя поля
Userid
Objectld
Tim данных
Number
Number
Таблица Permissions играет важную роль в проекте docman. Если пользова-
тель имеет полномочие доступа к объекту, то эта информация должна присут-
ствовать в таблице Permissions. Поэтому, если пользователь A, 1D которого
равен 7, имеет доступ к папке, ID объекта которой равен 133, то в таблице
Permissions должна существовать запись с Userid 7 и Object! D 133.
Теперь рассмотрим структуру каталогов фиктивной компании с именем
Door Never Closed Pty, Ltd. В этом примере используется небольшое число
файлов и папок, но иерархия должна отражать структуру реальной системы.
Иерархия всех папок и файлов показана на рис. 20.2.
iUroot
Marketing
Ira Managers
•““D evaluation.doc
renumeration.doc
Ж1 Sales
НГ| europe.doc
i-—2} asia.doc
america.doc
0README.TXT
Рис. 20.2 . Структура каталогов файловой системы
Управление документами на основе Web
529
SELECT O.ParentID
FROM Objects 0, Users U, Permissions P
WHERE O.ID = objectID
AND O.ParentID = P.ObjectID
AND P.UserID = U.ID
AND U.UserName = userName
AND U.Password = password
getChildObjects(String parentld, String userName,
String password)
Этот метод возвращает ArrayList, который содержит все объекты-потомки
текущей папки. Необходимы ID, Туре и Name каждого объекта. Эти три поля
соединяются вместе в строку, и каждое поле отделяется запятой. Здесь вместо
простого оператора SQL:
SELECT ID, Type, Name
FROM Objects
WHERE ParentID = id
используется более «защищенный» оператор SQL:
SELECT ID, Type, Name
FROM Objects
WHERE ParentID =
(SELECT ObjectID FROM Permissions
WHERE ObjectID = id
AND UserID =
(SELECT ID FROM Users
WHERE UserName = userName
AND Passord = password
) )
ORDER BY Type ASC
Предложение ORDER BY обеспечит то, что все папки (Type=0) появятся в
начале.
getFilename(String objectld, String userName, String password)
Этот метод служит для получения имени файла, который запрашивается
пользователем для загрузки. Следующий оператор SQL гарантирует, что толь-
ко пользователи, имеющие полномочия доступа к этому файлу, смогут его заг-
рузить:
SELECT Name
526
Гпава 20
Таблица 20.5. Данные в таблице Users
ID UserName Password
1 boni secret
2 bulbul brownhair
Теперь перейдем к полномочиям. Boni является менеджером отдела марке-
тинга. Она имеет доступ к файлам отдела маркетинга, но не имеет никакого
отношения к отделу продаж. Bulbul является директором национальной тор-
говли компании Door Never Closed Pty, Ltd. Он имеет доступ ко всем файлам
отдела продаж, но отдел маркетинга не его территория. Файл README.TXT
предназначен для всех, поэтому и Boni, и Bulbul имеют к нему доступ. Эта
информация содержится в таблице Permissions (см. таблицу 20.6).
Таблица 20.6. Таблица Permissions
ObjectlD
1
1
2
4
4
5
6
7
8
9
10
11
UserID
1
2
1
1
1
2
2
1
1
1
2
2
2
Код
Фактически для построения системы, подобной этой, требуются только три
файла и один Bean для базы данных и манипуляции объектами. Это файлы:
• Logm.html. Простой файл НТМЬдля пользователей, служащий для вво-
да имени и пароля.
• DisplayObjectsjsp. Файл jsp, который выводит на экран все объекты оп-
ределенной папки. Этот файл содержит также форму для пересылки
файла в папку.
Управление документами на основе Web
531
Полный код класса DBBean представлен в листинге 20.1.
Листинг 20.1 DBBean
package docman;
import java.sql.*;
import java.util.ArrayList;
import java.io.FileOutputStream;
import java.io.File;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;
public class DBBean {
public static final String DATA_PATH =
”C:\\123Data\\JavaProjects\\docman\\Data\\’’;
public String getDataPath() {
return DATA_PATH;
}
// соединение с базой данных
private Connection connection=null;
// JDBC URL, измените значение этой строки
// необходимым образом
String dbUrl = "jdbc:odbc:docman";
// имя пользователя для доступа к базе данных
String dbUserName = "";
// пароль для доступа к базе данных
String dbPassword = "";
I/ имя драйвера JDBC, значение зависит
// от используемого драйвера
String JDBCDriverName =
"sun.jdbc.odbc.JdbcOdbcDriver";
public void setUrl(String dbUrl) {
// этот метод позволяет изменить JDBC URL,
// если требуется JDBC URL, отличный от используемого
// по умолчанию
this.dbUrl = dbUrl;
}
public void setDbUserName(String dbUserName) {
// этот метод позволяет изменить имя
528
Глава 20
любой используемый каталог. Однако в целях безопасности проверьте, что этот
каталог находится вне каталога приложения.
Ниже перечислены другие используемые в Bean методы.
verifyUser(String userName, String password)
Этот метод служит для проверки того, что пользователь авторизован. Ме-
тод проверяет таблицу Users на наличие UserName (userName) и Password
(password). Он возвращает true, если находит имя пользователя и пароль в таб-
лице; иначе возвращается false (неавторизованный пользователь). При этом
используется следующий оператор SQL:
SELECT ID FROM Users
WHERE U,serName=userName AND Password=password
getDataPath()
Этот метод возвращает физический каталог на сервере, где хранятся все
файлы. Метод используется при загрузке или размещении файла. В качестве
пути доступа к данным должен применяться каталог вне виртуального хоста.
getFolderName(String id, String userName, String password)
Этот метод вызывается в DisplayObjects.jsp для получения имени текущей
папки. Эта информация требуется для вывода имени папки, чтобы пользова-
тель знал о своем положении в иерархии дерева. Можно получить имя папки
и родительский ID с помощью оператора SQL:
SELECT Name, ParentID FROM Objects WHERE ID=id
Однако желательно гарантировать, что только пользователи с достаточны-
ми полномочиями получат информацию об определенных папках. Поэтому
используется более сложный оператор SQL:
SELECT O.Name, 0.ParentID
FROM Objects 0, Permissions P, Users U
WHERE O.Id = id
AND O.ID = P.ObjectID
AND P.UserID = U.ID
AND U.UserName = userName
AND U.Password = password
getParentId()
Этот метод возвращает объектный ID родительской папки для текущей
папки. Оператор SQL, который извлекает ParentID только в том случае, если
пользователь имеет полномочие на просмотр родительского объекта, имеет
следующий вид:
Управление документами на основе Web
533
connect();
String sql = "SELECT ID, Type, Name" +
FROM Objects" +
" WHERE ParentID=" +
(SELECT ObjectID FROM Permissions" +
" WHERE ObjectID=" + id +
" AND UserID=" +
" (SELECT ID FROM Users" +
WHERE UserName=’” + userName + .... +
AND Password=”’ + password + "'))" +
" ORDER BY Type ASC";
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
while(r.nextO) {
records.add(r.getString("ID") + "," +
r.getString("Type") + "," +
r.getStringC’Name"));
}
s.close(); // Также закрываем ResultSet
}
catch(Exception e) {
e.printStackTraceO;
}
return records;
}
public String getCookie(
HttpServletRequest request, String cookie) {
// возвращает значение cookie, если cookie
// обнаружен в запросе; иначе возвращается null
Cookie cookies[] = request.getCookiesO;
if (cookies!=null)
for (iot i=0; Kcookies.length; ++i)
if (cookies[i].getName().equals(cookie))
return cookies[i].getValue();
return null;
}
public String getParentId(String objectld,
String userName, String password) {
String parentld = null;
530
Глава 20
FROM Objects
WHERE ID =
(SELECT ObjectID FROM Permissions
WHERE ObjectID = objectld
AND UserID =
(SELECT ID FROM Users
WHERE UserName = userName
AND Password = password
)
)
getCookie(javax.servlet.http.HttpServletRequest request, String cookie)
Этот метод возвращает значение cookie, если в запросе HTTP обнаружен
cookie; иначе возвращается null. Поиск cookie выполняется путем просмотра в
цикле массива Cookie запроса HTTP и сравнения найденного cookie с именем
каждого cookie в массиве:
Cookie cookies[] = request.getCookiesO;
if (cookies!=null)
for (int i=0; Kcookies.length; ++i)
if(cookies[i].getName().equals(cookies))
return cookies[i].getValue();
Код метода DBBean представлен в листинге 20.2. Не
описанные здесь методы будут изучены позже.
hasUploadPermission(String id, String userName, String password)
Этот метод вызывается, когда пользователь пытается разместить файл.
Метод возвращает true, если пользователь может поместить файл в каталог,
идентификатор которого определен как аргумент id.
synchronized public int getLastObjectId()
Этот метод возвращает идентификатор последнего объекта в таблице
Objects. Значение, передаваемое этим методом, плюс единица будет иденти-
фикатором следующего объекта для размещения.
insertObject(String parentld, String objectld, String filename)
Этот метод вставляет строку в объект Tables. Метод вызывается при разме-
щении файла.
insertPermissions(String parentld, String objectID)
Этот метод вставляет строку в таблицу Permissions для заданного объекта и
пользователя, размещающего файл.
Управление документами на основе Web
535
public String getFolderName(String id,
String userName, String password) {
String folderName = null;
String sql = "SELECT O.Name, 0.ParentID" +
FROM Objects 0, Permissions P, Users U" +
" WHERE O.ID=" + id +
AND O.ID = P.ObjectID" +
" AND P.UserID = U.ID" +
" AND U.UserName=’" + userName + ... +
AND U.Password = ’" + password + ...;
try {
if (connection==null)
connectO;
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
while(r.nextO) {
«
folderName = r.getString("Name");
}
s.closeO; // Также закрываем ResultSet
}
catch(Exception e) { }
return folderName;
}
public String getFilename(String objectld,
String userName, String password) {
String filename = null;
String sql = "SELECT Name" +
" FROM Objects" +
" WHERE ID=" +
" (SELECT ObjectID FROM Permissions" +
" WHERE ObjectID=" + objectld +
" AND UserID=" +
" (SELECT ID FROM Users" +
" WHERE UserName=’" + userName + ... +
" AND Password^" + password + "’))";
try {
if (connection==null)
connectO;
java.sql.Statement s =
connection. createStatementO;
java.sql.ResultSet r = s.executeQuery( sql );
532
Глава 20
Листинг 20.1 Продолжение
// пользователя базы данных
this.dbUserName = dbUserName;
}
public void setDbPassword(String dbPassword) {
// этот метод позволяет изменить пароль
// для доступа к базе данных
this.dbPassword = dbPassword;
}
public void setJDBCDriverName
(String JDBCDriverName) {
// этот метод позволяет изменить
И драйвер JDBC для базы данных
this.JDBCDriverName = JDBCDriverName;
}
public void connect() {
// пытаемся соединиться с базой данных
// с помощью заданных
И url, имени пользователя и пароля
connect(dbUrl, dbUserName, dbPassword);
}
public void connect
(String url, String userName, String password) {
// пытаемся соединиться с базой данных
// с помощью url, имени пользователя и пароля,
// переданных как параметры
try {
Class.forName( JDBCDriverName);
connection = DriverManager.getConnection(url,
userName, password);
}
catch (Exception e) {}
}
public ArrayList getChildObjects(String id,
String userName, String password) {
//возвращаемое значение
ArrayList records = new ArrayList();
try {
if (connection==null)
Управление документами на основе Web
537
if (connection==null)
connectO;
String sql = "SELECT MAX(ID) AS LastID FROM Objects";
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
while (r.nextO)
retval = Intege^.parseInt(r.getString("LastID"));
s.closeO; // Также закрываем ResultSet
}
catch(Exception e) {}
return retval;
}
public void insertObject(String parentld,
String objectld, String filename) {
try {
if (connection==null)
connectO;
I/ вставляем запись в таблицу Objects
String sql = "INSERT INTO Objects" +
" (ID, ParentID, Type, Name)" +
" VALUES" +
+ objectld +
”," + parentld +
”. 1,” +
... + filename + "’)";
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
s.closeO;
}
catch(Exception e) {}
}
public void insertPermissions(String parentld,
String objectld) {
try {
if (connection==null)
connectO;
534
Глава 20
Листинг 20.1 Продолжение
String sql = ’’SELECT O.ParentID’’ +
FROM Objects 0, Users U, Permissions P” +
” WHERE O.ID=” + objectld +
" AND O.ParentID = P.ObjectID" +
" AND P.UserID = U.ID" +
" AND U. UserName="’ + userName + ... +
’’ AND U.Password=”’ + password + ...;
try {
if (connection==null)
connectO;
Statement s = connection.createStatementO;
// код SQL:
ResultSet r = s.executeQuery( sql );
while(r.nextO) {
parentld = r.getString("ParentID”);
}
s.closeO; // Также закрываем ResultSet
}
catch(Exception e) { }
return parentld;
}
public boolean verifyUser(String userName,
String password) {
String sql = "SELECT ID FROM Users" +
" WHERE UserName=’" + userName + .... +
" AND Password^" + password + ....;
boolean retval = false;
try {
if (connection==null)
connectO;
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
if (r.nextO) {
retval = true;
}
s.closeO; // Также закрываем ResultSet
}
catch(Exception e) {
return false;
}
return retval;
}
Управление документами на основе Web
539
<TR>
<TD C0LSPAN="2" ALIGN="RIGHT”>
CINPUT TYPE= "SUBMIT" VALUE="LOGIN">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
Door Never Closed Pty Ltd
Pamrord
BOI
Рис. 20.3. Страница Login
DisplayObject.jsp
После того как пользователь вводит имя и пароль и нажимает кнопку Submit
на странице Login.html, запрос HTTP обрабатывается страницей
DisplayObjects.jsp. Страница сначала проверяет допустимость имени пользо-
вателя и пароля. Если имя и пароль недействительны, пользователь возвра-
щается на страницу Login. Если введенные данные действительны, страница
пытается вывести объекты.
Благодаря применению cookie, пользователь регистрируется только один
раз. После выполнения успешной регистрации имя пользователя и пароль
посылаются в браузер как cookie. Эти cookie будут передаваться на сервер при
следующем запросе HTTP. Cookie существуют, пока пользователь не закрыва-
ет браузер.
Код DisplayObjects.jsp показан в листинге 20.3.
536
Глава 20
Л исти н г 20.1 Продолжение
if (r.nextO)
filename = r.getString(’’Name");
s.closeO; // Также закрываем ResultSet
}
catch (Exception e) {}
return filename;
}
public boolean hasUploadPermission(String id,
String userName, String password) {
boolean retval = false;
try {
if (connection==null)
connectO;
String sql = ’’SELECT ObjectID FROM Permissions” +
” WHERE ObjectID=” + id +
" AND UserID=” +
" (SELECT ID FROM Users” +
’’ WHERE UserName=’” + userName + .. +
” AND Password^" + password +
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
while(r.next()) {
retval = true;
}
s.closeO; // Также закрываем ResultSet
}
catch(Exception e) {
e.printStackT race();
}
return retval;
}
synchronized public int getLastObjectldO {
int retval = 0;
try {
Управление документами на основе Web
541
<HEAD>
<TITL Е> Di s р1ayO bjects</TITLE>
</HEAD>
<BODY>
<%
// строка, содержащая сценарий javascript,
// создаваемый на лету
String js="";
И---------------------
String parentld = null;
// id текущей папки
String id = request.getParameter("id");
boolean atRoot = false;
if (id==null)
id = "1"; //1 является root
if (id.equals(’T'))
atRoot = true;
if (!atRoot) {
// выводим ссылку для перехода на один уровень вверх;
// эта ссылка вызывает ту же самую страницу,
// которая передает родительский id
parentld = DBBeanld.getParent!d(id, userName,
password);
out.println("<A HREF=\"DisplayObjects.jsp?id="
+ parentld +
+ "<IMG SRC=\’’images/Up.gif\"
+ "BORDER=\"O\"></A><BR>");
}
String currentFolderName =
DBBeanld.getFolderName(id, userName, password);
if (currentFolderName==null)
out.println("<B>You don’t have permission to"
+ " view this folder.</B>");
else {
out.println("<B>Current Folder:
+ currentFolderName + "</B>");
%>
<TABLE WIDTH=600>
<TR BGC0L0R=#D6D6D6>
<TD WIDTH=100>Name</TD>
538
Глава 20
Л исти н г 20.1 Продолжение
// вставляем записи в таблицу Permissions
String sql = "INSERT INTO Permissions" +
(ObjectID, UserID)" +
SELECT " + objectld + ", UserID" +
FROM Permissions" +
WHERE ObjectID=" + parentld;
Statement s = connection.createStatementO;
ResultSet r = s.executeQuery( sql );
s.close();
}
catch(Exception e) {}
}
}
Страницы
Проект имеет три страницы JSP.
Login.html
Login.html, первая страница приложения, является простым файлом HTML
с формой, атрибут ACTION которой задан как «DisplayObjects.jsp». Код HTML
представлен в листинге 20.2, а страница показана на рис. 20.3.
Л истинг 20.2 Login.html .
<HTML>
<HEAD>
<TITLE>Login Page</TITLE>
</HEAD>
<BODY>
<H2>Door Never Closed Pty Ltd</H2>
<FORM METHOD="POST" ACTION="DisplayObjects.jsp">
CTABLE BORDER="O”>
<TR>
<TD>User Name</TD>
CTDXINPUT TYPE="TEXT" NAME="userName"x/TD>
</TR>
<TR>
<TD>Passwo rd</TD>
CTDXINPUT TYPE="PASSWORD" NAME="password"X/TD>
</TR>
Управление документами на основе Web
543
+ •</AX/TD>\n");
out.println(” <TD>" + objectType +
"</TD>\n");
out.println("</TR>\n");
}
%>
<TR>
<TD C0LSPAN=2XHRx/TD>
</TR>
<TR>
<TD C0LSPAN=2 ALIGN=RIGHT>
<FORM METH0D=P0ST ENCTYPE=MULTIPART/FORM-DATA
ACTION=UploadObject.j sp>
<INPUT TYPE=HIDOEN NAME=parentId VALUE="<%=id%>">
<B>Upload File:</B>
<INPUT TYPE=FILE NAME=filename SIZE=25>
<INPUT TYPE=SUBMIT VALUE=Upload>
</FORM>
</TD>
</TR>
</TABLE>
<SCRIPT LANGUAGE="JavaScript">
parent.deleteSubfolder(parent.folderTree, <%=id%>);
<%=j s%>
parent.openFolder!d=<%=id%>;
parent. redrawTreeO;
</SCRIPT>
<%
}
%>
</BODY>
</HTML>
Страница DisplayObjectsjsp является рекурсивной страницей, которая вы-
зывается по ссылке в своем собственном теле. Сначала она ищет параметры
запроса «userName» и «password»:
String userName = request.getParameter("userName");
String password = request.getParameter("password");
540
Гпава 20
Л исти н г 20.3 DisplayObjects.jsp
<%© page import='’java.util.ArrayList" %>
<%@ page import="java.util.StringTokenizer" %>
<jsp:useBean id="DBBeanId" scope="page"
class="docman.DBBean" />
<%
// проверяем в HTTP-запросе userName и password <
// для запроса co страницы Login
String userName = request.getParameter("userName");
String password = request.getParameter("password");
if (userName==null || password==null) {
// если userName или password не найден,
Ц значит, запрос пришел не со страницы Login,
// проверяем cookie
userName =
DBBeanld.getCookie(request, "userName”);
password =
DBBeanld.getCookie(request, "password");
}
if (userName==null || password==null) {
/I userName и password не найдены,
// это может быть незаконная попытка обойти
// страницу Login
response.sendRedi rect("Login.html");
}
// userName и password найдены,
//проверяем, что это авторизованный пользователь
if (! DBBeanld.verifyUser(userName, password))
response.sendRedi rect("Login.html");
else {
//авторизованный пользователь, создаем cookie
Cookie cookieUserName =
new Cookie("userName", userName);
Cookie cookiePassword =
new Cookie("password", password);
response.addCookie(cookieUse rName);
response.addCookie(cookiePassword);
}
%>
<HTML>
Управление документами на основе Web
545
String id = request.getParameter("id");
boolean atRoot = false;
if(id==null)
id = "1"; //1 означает root
if (id.equals(’T'))
atRoot = true;
Если текущая папка не является root, она должна иметь родительскую папку.
Следующий код создает ссылку, на которой пользователь может щелкнуть
мышью, чтобы вернуться в родительскую папку:
if (!atRoot) {
// выводим ссылку для перехода на один уровень вверх;
И эта ссылка вызывает ту же самую страницу,
// которая передает родительский id
parentld = DBBeanId.getParentId(id, userName, password);
out.println("<A HREF=\”Displayobjects.jsp?id="
+ parentld +
+ "<IMG SRC=\’’iroages/Up.gif\" "
+ "BORDER=\’’O\"></A><BR>");
}
Отметим, что этот код использует метод get Parent ID из DBBean для получения
ID предка текущей папки. Этот ID предка будет затем передан как значение
параметра ID страницы DisplayObjects.jsp. Таким образом, налицо рекурсивная
природа данной страницы.
Чтобы сообщить пользователю о его положении в иерархии, выводится имя
папки. Отметим, что если неавторизованный пользователь попытается обой-
ти страницу Login, то метод getFolderName вернет null:
String currentFolderName =
DBBeanId.getFolderName(id, userName, password);
if (currentFolderName==null)
out.println("<B>You don't have permission to"
+ " view this folder.</B>");
else {
out.println("<B>Current Folder:
+ currentFolderName + "</B>”);
Далее идет основной метод, который извлекает все объекты-потомки теку-
щей папки. Метод getChildObjects из DBBean возвращает ArrayList. Опять же
только авторизованные пользователи смогут это увидеть:
542
Глава 20
Листинг 20.3 Продолжение
<TD >Type</TD>
</TR>
<%
ArrayList records = DBBeanId.getChildObjects(
id, userName, password);
for (int i=0; i<records.size(); i++) {
String row = (String) records.get(i);
StringTokenizer st = new StringTokenizer(row, ’’,’’);
String objectld = (String) st.nextElementO;
String type = (String) st.nextElementO;
String name = (String) st.nextElementO;
String imagePath;
String objectLink;
String objectType;
if (type.equals("O’’)) { //папка
imagePath =
"<IMG SRC=\"images/folder.gif\" BOROER=\"O\’’>”;
objectLink =
"<A HREF=\”DisplayObjects.jsp?id=" +
objectld +
objectType = ’’Folder";
// — это для части 2 -----------
//создаем строку javascript для подпапки
j s+="parent. appendFolder( parent. f olderT ree, ’’
+ id + ", parent.createFolder("
+ objectld + + name + ));\n";
/1-----------------------
}
else { //файл
imagePath = "<IMG SRC=\"images/file.gif\’’ BORDER=\’’O\’’>";
objectLink = "<A HREF=\"DownloadObject.jsp?id=" +
objectld + "\">";
objectType = "File";
}
out. println(’’<TR>\n");
out.println(" <TD>" + objectLink + imagePath
+ "</A> " + objectLink + name
Управление документами на основе Web
547
”<К HREF=\'’DownloadObject. jsp?id=" +
objectld +
objectType = "File";
}
И наконец выводятся объекты-потомки:
out.р rint1n("<TR>\n");
out.println(" <TD>" + objectLink + imagePath
+ "</A> " + objectLink + name
+ ”</A></TD>\n”);
out.println(” <TD>” + objectType +
"</TD>\n”);
out.println(”</TR>\n”);
На рис. 20.4 показана страница в действии.
Страница DisplayObjects.jsp выводит список объектов в каталоге и форму, с
помощью которой пользователь может разместить файл в каталоге.
DownloadObject.jsp
Страница DownloadObject.jsp представлена в листинге 20.4.
Л исти н г 20.4 Код DownloadObject.jsp
<jsp:useBean id="dbBean” scope="page” class=’’docman. DBBean" />
<jsp:useBean id="downloadBean" scope=’’page"
class=”com.brainysoftware.web.FileDownloadBean” />
<%
String objectld = request.getParameter("id");
String userName = dbBean.getCookie(request, "userName”);
String password = dbBean.getCookie(request, "password”);
if (userName==null || password==null)
response.sendRedi rect("Login.html");
// найдены cookie ’userName’ и ’password’;
// теперь посмотрим, имеет ли пользователь доступ к файлу
String filename = dbBean.getFilename(object!d, userName, password);
if (filename==null)
response. sendRedirect(’’Login. html");
else {
downloadBean.forceFilename(filename);
downloadBean.download(response, dbBean.getDataPath() + objectld);
}
%>
544
Глава 20
В этом случае предполагается, что запрос пришел со страницы Login. Од-
нако страница DisplayObjects.jsp может также вызывать сама себя. В этом слу-
чае параметры запроса «userName» и «password» не используются, но должны
присутствовать cookie с теми же именами, т.е. код должен проверять наличие
cookie в запросе HTTP:
if (userName==null || password==null) {
// если userName или password не найден,
// значит, запрос пришел не со страницы Login;
// проверяем cookie
userName =
DBBeanld.getCookie(request, "userName");
password =
DBBeanld.getCookie(request, "password");
}
Если cookie не найдены, значит, запрос HTTP поступил от кого-то, кто
пытается обойти страницу Login, или браузер не поддерживает cookie. В лю-
бом случае пользователь переадресуется на страницу Login.
Если найдены записи для имени пользователя и пароля, либо как парамет-
ры запроса, либо как cookie, нельзя рассчитывать на то, что запрос пришел от
авторизованного пользователя. Все равно необходимо проверять имя и пароль
в базе данных каждый раз, когда запрашивается страница. Если проверка не
проходит, страница посылает пользователя назад на страницу Login. Если про-
верка успешна, код создает cookie «userName» и «password» для обслуживания
последующих запросов от пользователя в сеансе. Это делается в следующем
фрагменте кода:
if (!DBBeanld.verifyUser(userName, password))
response.sendRedi rect("Login.html");
else {
//авторизованный пользователь, создаем cookie
Cookie cookieUserName =
new Cookie("userName", userName);
Cookie cookiePassword =
new Cookie("password”, password);
response.addCookie(cookieUserName);
response.addCookie(cookiePassword);
}
Каждый запрос HTTP этой страницы должен доставлять ID папки, объек-
ты-потомки которой будут выводиться на экран. Исключением является слу-
чай, когда пользователь регистрируется впервые. При этом ID папки равен 1
(root) и задается флаг atRoot:
Управление документами на основе Web
549
downloadBean.forceFilename(filename);
downloadBean.download(response, dbBean.getDataPath() + objectld);
Методу forceFilename() передается имя файла, которое должно появиться в
диалоговом окне Download браузера. Если этот метод не вызывается, диалого-
вое окно Download покажет объектный идентификатор файла.
Размещение файла
Размещение файла является важной функцией системы управления доку-
ментами на основе Web. С ее помощью пользователь может добавить файл в
выбранную папку. Размещение файлов было рассмотрено в главе 13. В этом
проекте применяется File Upload Bean.
Код HTML формы, используемой для размещения файла, включен в файл
DisplayObjects.jsp (см. листинг 20.2). Ниже представлен требуемый фрагмент:
<FORM METHOD=POST ENCTYPE=MULRTIPART/FORM-DATA
ACTION=UploadObj ect. j sp>
CINPUT TYPE=HIDDEN NAME=parentId VALUE="c%=id%>">
<B>Upload File:</B>
CINPUT TYPE=FILE NAME=filename SIZE=25>
CINPUT TYPE=SUBMIT VALUE=Upload>
</FORM>
При передаче этой формы содержимое попадет на страницу
UploadObject.jsp (см. листинг 20.5).
Листинг 20.5 UploadObject.jsp
<%@ page import=”java.io.FileOutputStream" %>
cjsp:useBean id="dbBean’' scope=’'page" class="docman.DBBean" />
cjsp:useBean id="uploadBean" scope="page"
class="com.brainysoftware.web.FileUploadBean" />
<%
String userName =
dbBean.getCookie(request, "userName”);
String password =
dbBean.getCookie(request, "password”);
if (userName==null || password==null)
response.sendRedirect("Login.html");
else {
546
Глава 20
ArrayList records =
DBBeanld.getChildObjects(id, userName, password);
Каждый элемент в записях ArrayList является строкой, содержащей Object ID,
Туре и Name объекта-потомка, разделенные запятыми. Поэтому для получения
каждого поля требуется StringTokenizer. Следующий код проходит в цикле записи
ArrayList и извлекает значения Object ID, Туре и Name каждого объекта:
for (int i=0; i<records.size(); i++) {
String row = (String) records.get(i);
StringTokenizer st =
new StringTokenizer(row, ”,");
String objectld = (String) st. nextElementO;
String type = (String) st.nextElement();
String name = (String) st.nextElement();
String imagePath;
String objectLink;
String objectType;
}
Если необходимо предоставить пользователю описательный визуальный
интерфейс, применяйте различные значки для представления файлов и па-
пок, т. е. используйте для каждой папки изображение Folder.gif, а для каждого
файла — File.gif. Еще одним различием являются целевые ссылки каждого типа.
Когда пользователь щелкает мышью на папке, приложение показывает все
объекты-потомки этой папки, если пользователь имеет полномочия на ее про-
смотр. При этом вызывается DisplayObjects.jsp. Однако когда пользователь
щелкает мышью на файле, приложение посылает файл браузеру, если пользо-
ватель имеет полномочие на его просмотр:
if (type.equals(”O”)) { //папка
imagePath =
"<IMG SRC=\”images/folder.gif\” BORDER=\"O\”>";
objectLink =
”<A HREF=\”DisplayObjects.jsp?id=" +
objectld +
objectType = ’'Folder";
}
else { //файл
imagePath =
"<IMG SRC=\"images/file.gif\" BORDER=\"O\">";
objectLink =
Управление документами на основе Web
551
String objectld = Integer.toString(dbBean.getLastObject!d() + 1);
Также требуется родительский каталогфайла. Идентификатор предка вклю-
чается в форму размещения файла и посылается на сервер, следовательно, он
содержится в одном из полей, сопровождающих размещаемый файл. Можно
получить значение этого поля, вызвав метод getFieldValue() из FileUpload Bean:
String parentld = uploadBean.getFieldValue("parentld");
Для обеспечения безопасности необходимо воспользоваться методом
hasUploadPermission с целью проверки того, что пользователь имеет полномо-
чие на размещения файла в указанном каталоге:
if (dbBean.hasUploadPermission(parentId, userName, password)) {
Если пользователь авторизован, то размещаемый файл получает в каче-
стве имени объектный идентификатор и вызывается метод save() для его со-
хранения:
uploadBean.forceFilename(objectld);
uploadBean.save();
Затем необходимо сохранить метаданные размещенного файла, чтобы его
можно было извлекать:
dbBean. insertObject(parent!d, objectld, uploadedFilename);
dbBean.insertPermissions(parentId, objectld);
Наконец пользователь снова направляется на страницу DisplayObjects.jsp:
response.sendRedirect("DisplayObjects.jsp?id=" + parentld);
Дерево папок
Дерево папок является средством docman, которое облегчает навигацию.
Можно реализовать дерево папок с помощью апплета или JavaScript. При ис-
пользовании апплета можно будет задействовать все средства Java, что позво-
лит выполнить сложные задачи программирования. Однако пользователь зап-
латит за это тем, что ему придется загружать несколько килобайтов апплета.
Более того, возникают проблемы коммуникации между апплетом и кодом
HTML/JavaScript. Эти два фактора делают JavaScript более привлекательным
для использования в нашем проекте.
JavaScript не является таким мощным, как Java, тем не менее реализовать в
JavaScript дерево папок довольно просто. Результат успешно протестирован в
548
Глава 20
Рис. 20.4. Страница DisplayObjects.jsp
Сначала эта страница проверяет, что объект Request содержит cookie для
имени пользователя и пароля.
String objectld = request.getParameter("id");
String userName = dbBean.getCookie(request, "userName");
String password = dbBean.getCookie(request, "password");
Если пользователь является авторизованным, имя и пароль передаются в
метод getFilename вместе с идентификатором загружаемого объекта. Метод
getFilename() возвращает имя файла, если пользователь имеет полномочие на
загрузку файла. Иначе возвращается null:
String filename = dbBean.getFilename(object!d, userName, password);
if (filename==null)
response.sendRedirect("Login.html");
else {
}
Затем мы получаем путь доступа, где хранятся все файлы.
После того как установлено, что пользователь авторизован, можно загру-
зить файл с помощью File Download Bean. Фактически требуются только две
строки кода:
Управление документами на основе Web
553
foldersNodeffoldersNode.length] = childFolder;
else if (foldersNode.length>3)
for (var i=3; i< foldersNode.length; i++)
appendFolder(
foldersNode[i], parentld, childFolder);
}
function redrawTreeO {
var doc = top.treeFrame.window.document;
doc.clear();
doc. write(”<B0DY BGCOLOR='#ffffff ’>’’);
redrawNode(folderTree, doc, 0, 1,
doc.closeO;
}
function redrawNode(
foldersNode, doc, level, lastNode, leftside) {
if (foldersNode==”0’’)
return;
var j=0;
var i=0;
var folderld = foldersNode[0];
var folderName = foldersNode[1];
var hasSubNode = (foldersNode.length>3);
var expanded = foldersNode[2];
doc.write("<TABLE BORDER=O CELLSPACING=O" +
" CELLPADDING=O>”);
doc.writeC<TRXTD VALIGN=middle N0WRAP>");
doc.write(leftSide);
var nodeLink =
"<A HREF=’javascript:top.clickNode(”
+ folderld + ”)’>”;
if (level>0)
if (lastNode) { //последняя папка в массиве
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
"<IMG SRC=’images/LastNodeMinus.gif +
’’ BORDER=O WIDTH=16 HEIGHT=22>" + ”</A>”);
550
Глава 20
Листинг 20.5 Продолжение
uploadBean.setSavePath(dbBean.getDataPath());
uploadBean.doUpload(request);
String uploadedFilename = uploadBean.getFilenameO;
String objectld = Integer.toString(dbBean.getLastObjectldQ + 1);
String parentld = uploadBean.getFieldValue("parentld");
if (dbBean.hasllploadPermission(parentId, userName, password)) {.
uploadBean.forceFilename(objectId);
uploadBean.save();
dbBean.insertObject(parentId, objectld, uploadedFilename);
dbBean.insertPermissions(parentId, objectld);
}
response.sendRedi rect(”DisplayObj ects.j sp?id="
+ parentld);
}
%>
Этот код проверяет, что пользователь авторизован:
String userName = dbBean.getCookie(request, "userName”);
String password = dbBean.getCookie(request, "password");
if (userName = null || password = null)
response.sendRedirect("Login.html");
Процесс размещения начинается с задания каталога, в котором будет со-
хранен размещаемый файл. Это делается путем передачи static final строки
DATA_PATH, получаемой с помощью метода getDataPath():
uploadBean.setSavePath(dbBean.getDataPath());
Затем вызывается метод doUpload() из FileUpload Bean:
uploadBean.doUpload(request);
Для сохранения файла используется не его исходное имя, а объектный иден-
тификатор. Однако исходное имя файла включается в новую строку, вставля-
емую в таблицу Objects. Можно получить исходное имя размещенного файла с
помощью метода getFilenameO из FileUpload Bean:
String uploadedFilename = uploadBean.getFilenameO;
В качестве объектного идентификатора для нового файла используется сле-
дующее число в таблице Objects:
Управление документами на основе Web
555
doc.write(”<FONT SIZE=-1 FACE=*Arial, Helvetica’.>”
+ folderName +”</F0NT>”);
doc.write(”</TABLE>”)
if (hasSubNode && expanded) {
level++;
for (i=3; KfoldersNode. length; i++)
if (i==foldersNode.length-1)
redrawNode(
foldersNode[i], doc, level, 1, leftside);
else
redrawNode(
foldersNode[i], doc, level, 0, leftside);
}
}
function toggleNode(foldersNode, folderld) {
if (foldersNode[0]==folderId)
foldersNode[2] = 1 - foldersNode[2];
else if (foldersNode[2])
for (var i=3; i< foldersNode.length; i++)
toggleNode(foldersNode[i], folderld);
}
function clickNode(folderld) {
toggleNode(folderTree, folderld);
redrawTreeO;
}
function clickFolder(folderld) {
if (openFolderld ! = folderld) {
openFolderld = folderld;
redrawTreeO;
objectFrame.location="DisplayObjects.jsp?id=” +
folderld;
}
}
function deleteSubfolder(foldersNode, parentld) {
//удаляем все подпапки в родительской папке
if (foldersNode[0]==parentId) {
for (var i=foldersNode.length-1; i>=3; i-) {
552
Глава 20
двух основных браузерах — Microsoft Internet Explorer и Nerscape Navigator. Код
работает также в браузерах, поддерживающих JavaScript.
Каждая папка имеет несколько атрибутов, таких как id и name. Папка мо-
жет иметь от нуля до неограниченного числа папок-потомков. Каждая папка-
потомок, в свою очередь, может содержать любое число своих собственных
папок-потомков, и эта иерархия может продолжаться до неограниченного
числа уровней. Первая папка в иерархии, root, находится на уровне 0. Ее пап-
ки-потомки размещаются на уровне 1 и т. д.
Сценарий JavaScript для работы с деревом папок содержится в файле
FolderTree.html, который представлен в листинге 20.6. Этот файл формирует
две рамки. Левая рамка показывает дерево папок, а правая папка предназна-
чена для страниц Login и DisplayObjects.jsp.
Листинг 20.6 FolderTree.html
<HTML>
<HEAD>
<TITLE>Docman</TITLE>
<SCRIPT LANGUAGE^’JavaScript’’>
<!- Скрытый сценарий
// открытая папка, по умолчанию - root
var openFolderld = 1;
var folderTree = 0;
function createFolder(id, name) {
var folder;
folder = new Array;
folder[0] = id;
folder[1] = name;
// folder[2] указывает, что папка
// раскрыта/закрыта.
11 folder[2]=1 означает, что папка раскрыта, 0 - закрыта.
folder[2] = 1;
return folder;
}
function appendFolder(
foldersNode, parentld, childFolder) {
// добавляет папку как подпапку другой
// папки;
// сначала ищем родительскую папку, сравнивая
// parentld с foldersNode[0]
if (foldersNode[0]==parentId)
Управление документами на основе Web
557
</BODY>
</NOFRAMES>
</FRAMESET>
Если Tree.html является пустым и не используется, как получить код HTML
для прорисовки дерева папок? Как будет показано ниже, код создается динами-
чески функциями JavaScript. Этот код обновляется непрерывно при перемеще-
нии пользователя по структуре папок. Когда пользователь щелкает мышью на
одной из папок в дереве, эта папка открывается, и левая рамка получает ин-
струкцию, что она должна запросить файл DisplayObjectsjsp, передав id указан-
ной папки. При выводе объектов-потомков файл DisplayObjectsjsp записывает
код JavaScript, который посылает инструкцию в файл FolderTree.html для об-
новления папки так, чтобы показать все содержащиеся в ней папки-потомки.
Перед изучением файла FolderTree.html отметим ряд вспомогательных фай-
лов .gif, используемых при построении дерева папок (см. рис. 20.5).
fefl ClosedFolder
OpenFolder
г— Node
Q” NodeMinus
[j- NodePlus
LastNode
Pl— LastNodeMinus
[+b- LastNodePlus
VertLine
Рис. 20.5. Вспомогательные файлы .gif для дерева папок
Например, для отображения вертикальной точечной линии с короткой го-
ризонтальной линией в середине используется файл Node.gif. Если папка, со-
единенная с узлом, имеет одну или несколько подпапок-потом ков, то узел
представляется с помощью файлов изображений NodeMinus.gif и NodePlus.gif.
Файл NodeMinus.gif используется для раскрытых узлов, а файл NodePlus.gif—
для свернутых узлов. Для последней папки-потомка применяются следующие
три файла изображений: LastNode.gif, LastNodeMinus.gif и LastNodePlus.gif.
В листинге 20.6 следует обратить внимание на функцию createFolder. Она
служит для создания массива, который представляет папку, и получает два ар-
гумента: id папки и имя папки, id является уникальным идентификатором
объекта, хранящимся в таблице Objects. Например, root является первой пап-
кой с id равным 1. Массив содержит три обязательных элемента: id, имя и
554
Глава 20
Листинг 20.6 Продолжение
else
doc.write(nodeLink +
"<IMG SRC=’images/LastNodePlus.gif+
" BORDER=O WIDTH=16 HEIGHT=22>” + "</A>");
}
else
doc.write(”<IMG SRC='images/LastNode.gif +
" WIDTH=16 HEIGHT=22>”);
leftside += ”<IMG SRC=’images/blank.gif+
” WIDTH=16 HEIGHT=22>”;
}
else { // не последняя папка
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
"<IMG SRC='images/NodeMinus.gif +
” BORDER=O WIDTH=16 HEIGHT=22>” + ”</A>”);
else
doc.write(nodeLink +
"<IMG SRC=’images/NodePlus.gif+
” BORDER=O WIDTH=16 HEIGHT=22>” + ”</A>");
}
else
doc.write("<IMG SRC='images/Node.gif+
" WIDTH=16 HEIGHT=22>”);
leftside += "<IMG SRC=’images/vertline.gif +
" WIDTH=16 HEIGHT=22>";
}
doc.write(”<A HREF=’javascript:top.clickFolder("
+ folderld + ”)’><IMG SRC=images/");
if (folderld == openFolderld)
doc.write("openfolder.gif WIDTH=24 HEIGHT=22”
+ ” BORDER=O></A>");
else
doc.write(”closedfolder.gif WIDTH=24 HEIGHT=22”
+ ” BORDER=O></A>");
doc.write(”<TD VALIGN=middle ALIGN=left NOWRAPX);
Управление документами на основе Web
559
папок. Как и в Windows Explorer, можно щелкать на этих узлах, чтобы изме-
нить состояние с раскрытого на свернутое и наоборот.
Чтобы узел мог изменить свое состояние, изображение Node помещается в
ссылку, которая будет вызывать функцию clickNode при щелчке мыши. Например,
изображение, представляющее раскрытый узел, помещается в такую ссылку:
<А HREF='javascript:top.clickNode(folderld)'>
<IMG SRC='images/NodeMinus.gif’>
</A>
Функция clickNode вызывает функции toggleNode и redrawTree. Функция
toggleNode ищет в дереве папок папку с идентификатором, равным аргументу
folderld. Найдя такую папку, функция изменяет значение третьего элемента
массива папки следующим образом:
if (foldersNode[0]==folderId)
foldersNode[2] = 1 - foldersNode[2];
else if (foldersNode[2])
for (var i=3; i< foldersNode.length; i++)
toggleNode(foldersNode[i], folderld);
Отметим, что для перемещения по дереву папок при поиске папки фун-
кция toggleNode использует тот же алгоритм, что и функция appendFolder.
Функция redrawTree перерисовывает все дерево папок (см. ниже).
Аналогичным образом, если пользователь щелкает мышью на папке, вы-
зывается функция clickFolder, которая в свою очередь посылает сообщение в
другую рамку для запроса DisplayObjects.jsp с правильным значением параметра
id. Чтобы каждая папка реагировала на щелчок мыши, представляющее папку
изображение должно быть встроено в тег <А>:
<А HREF='javascript:top.clickFolder(folderld)’>
<IMG SRC=images/openfolder.gif>
</A>
Функция clickFolder выполняет три действия, если папка не является от-
крытой в данный момент:
• Присваивает переменной open Folder значение аргумента folderld.
X
• Вызывает функцию redrawTree.
• Посылает сообщение в правую рамку для запроса DisplayObjects.jsp с со-
ответствующим значением параметра id, используя следующую строку
кода. В результате объекты-потомки указанной папки будут изображе-
ны правильно.
556
Глава 20
Листинг 20.6 Продолжение
foldersNode[i] = ”0”;
}
}
else if (foldersNode.length>3)
for (var i=foldersNode.length-1; i>=3; i-)
deleteSubfolder(foldersNode[i], parentld);
}
function initializeO {
folderTree = createFolder(1, ’’root”);
}
// конец сценария ->
</SCRIPT>
</HEAD>
<FRAMESET onLoad=”initialize()” FRAMEBORDER="O"
FRAMESPACING="O" BORDER=”O” cols="225,*">
<FRAME SRC=”Tree.html” NAME=”treeFrame”>
<FRAME SRC=”Login.html” NAME=”objectFrame”>
<NOFRAMES>
<BODY>
Please upgrade your browser to
one that understands frames.
</BODY>
</NOFRAMES>
</FRAMESET>
</HTML>
Отметим, что во фрагменте кода, формирующем рамки, для первой рамки
применяется файл с именем Tree.html. Этот файл является пустым и, по сути,
не используется — он присутствует здесь только потому, что браузеры Netscape
выражают недовольство, если атрибут SRC задан не для всех тегов FRAME:
<FRAMESET onLoad=”initialize()’’ FRAMEBORDER=”O”
FRAMESPACING=”O” BORDER=”O” cols=”225,*">
<FRAME SRC=”Tree.html" NAME="treeFrame”>
<FRAME SRC="Login.html" NAME="objectFrame">
<NOFRAMES>
<BODY>
Please upgrade your browser to
one that understands frames.
Управление документами на основе Web
561
Для каждого узла получаем свойства из массива папки с помощью следую-
щего кода:
var folderld = foldersNode[0];
var folderName = foldersNode[1];
var hasSubNode = (foldersNode.length>3);
var expanded = foldersNode[2];
hasSubNode будет true, если foldersNode содержит более трех элементов, т. е.
папка имеет папки-потомки.
Если внимательно посмотреть на рис. 20.1, то можно заметить, что ника-
кие две папки не имеют одну и ту же вертикальную позицию. Можно пред-
ставить дерево папок в виде таблицы с множеством строк, где каждая строка
занята одной единственной папкой. Фактически каждая строка представля-
ется таблицей HTML, что облегчает выравнивание изображений:
doc.write(’’TABLE BORDER=O CELLSPACING=O" +
” CELLPADDING=O>’’);
doc.write(’’<TR><TD VALIGN=middle NOWRAP>’’);
doc.write(leftSide);
var nodeLink =
’’<A HREF= ’ javascript:top. clickNode("
+ folderld +
Остальной код функции redrawNode изображает узлы, папки и вертикаль-
ные линии для каждой папки в дереве. Для папок, уровень которых больше 0,
несколько файлов Blank.gif выводятся слева от узла:
if (level>0)
if (lastNode) { //последняя папка в массиве
if (hasSubNode) {
if (expanded)
doc.write(nodeLink +
"<IMG SRC='images/LastNodeMinus.gif +
” BORDER=O WIDTH=16 HEIGHT=22>” + "</A>");
else
doc.write(nodeLink +
XIMG SRC='images/LastNodePlus.gif’’’ +
’’ BORDER=O WIDTH=16 HEIGHT=22>” + ’’</A>’’);
>
else
doc.write(”<IMG SRC=’images/LastNode.gif”’ +
’’ WIDTH=16 HEIGHT=22>’’);
leftside += ”<IMG SRO’images/blank.gif’" +
558
Глава 20
состояние, которое описывает, раскрыт или свернут узел, если он имеет пап-
ки-потомки:
var folder;
folder = new Array;
folder[0] = id;
folder[1] = name;
folder[2] = 1;
return folder;
При первоначальном создании массив папки содержит только три элемен-
та. В своем исходном состоянии папка не имеет папок-потомков. Чтобы уз-
нать, есть ли папки-потомки или сколько их, можно измерить длину массива.
Число папок-потомков равно значению свойства length минус три.
В дереве папок каждая папка, за исключением root, имеет родительскую папку,
и каждая папка может потенциально иметь одну или несколько папок-потом-
ков. Папка может быть добавлена как папка-потомок другой папки. Именно
поэтому функция appendFolder очень важна здесь. Она получает три аргумента:
foldersNode, parentld и childFolder. Аргументparentld является идентификатором
папки, которая будет предком childFolder. АргументfoldersNode — это перемен-
ная, которая обеспечивает рекурсивный процесс поиска родительской папки.
Когда вызывается appendFolder, root передается какfoldersNode. Однако когда
функция вызывает себя рекурсивно, папка-потомок передается как folders Node.
if (foldersNode[0]==parentId)
foldersNode[foldersNode.length] = childFolder;
Если foldersNode является рассматриваемой папкой, неявно создается эле-
мент массива путем присвоения папки-потомка следующему элементу после
последнего элемента массива. Если это не та папка, которая требуется, функ-
ция вызывает себя рекурсивно, передавая каждую подпапку в качестве перво-
го элемента:
else if (foldersNode.length>3)
for (var i=3; i< foldersNode.length; i++)
appendFolder(
foldersNode[i], parentld, childFolder);
Если папка имеет хотя бы одну папку-потомка, ее узел изображается как
вертикальная линия с небольшим прямоугольником, содержащим знак ми-
нус (-) или плюс (+). Знак минус используется, когда узел раскрыт, т.е. папка-
потомок выводится в дереве папок. Знак плюс указывает, что узел свернут:
папка имеет одного или несколько потомков, но они не показаны в дереве
Управление документами на основе Web
563
else
redrawNode(
foldersNode[i], doc, level, 0, leftside);
}
}
Последней функцией в системе дерева папок является deleteSubfolder. В
связи с трудностями удаления элемента массива папка считается удаленной
при ее установке в «О». Функция deleteSubfolder получает два аргумента:
foldersNode и parentld. Последний аргумент является id папки, предназначен-
ной для удаления, а первый аргумент используется для поиска удаляемой папки.
При вызове этой функции корневая папка пропускается. Если папка имеет id,
равный parentld, она будет помечена как «0».
if (foldersNode[0]==parentId) {
for (var i=foldersNode.length-1; i>=3; i-) {
foldersNode[i] = "0”;
>
}
Если папка не является искомой, функция вызывает себя, передавая каж-
дую папку-потомок в качестве первого аргумента:
else if (foldersNode.length>3)
for (var i=foldersNode.length-1; i>=3; i-)
deleteSubfolder(foldersNode[i], parentld);
Функция deleteSubfolder была бы не нужна, если бы не следующий факт:
пользователь может перемещаться в дереве папок, открывая папку и сво-
рачивая или раскрывая узел, однако дерево папок должно также отражать
перемещение пользователя в правой рамке (на странице DisplayObjects.jsp).
Если пользователь щелкает мышью на папке-потомке в текущей папке,
папка-потомок становится открытой, и это должно отразиться в дереве
папок. Если пользователь переходит на один уровень вверх, щелкнув на
Up, дерево папок должно изменить изображение открытой папки. Когда
пользователь открывает другую папку, дерево должно показать все ее пап-
ки-потомки. Поскольку папка-потомок может быть удалена или новая пап-
ка-потомок может быть добавлена другими пользователями, необходимо
обновить открытую папку. Это делается на странице DispUyObjects.jsp пу-
тем вызова функций appendFolder применительно к предку в рамке для каж-
дой папки-потомка:
for (int i=0; i<records.size(); i++) {
560
Глава 20
objectFrame. location=”DisplayObjects. jsp?id=” + folderld;
Для каждого производимого пользователем действия, такого как сверты-
вание узла или открытие, папки, необходимо очистить левую рамку и пере-
рисовать дерево папок, чтобы отразить изменение. Это делается с помощью
функции redrawTree. Она очищает объект документа левой рамки, вызывает
функцию redrawNode для перерисовки дерева папок и закрывает объект до-
кумента:
var doc = top.treeFrame.window.document;
doc.clear();
doc. write("<BODY BGCOLOR= ’#ffffff
redrawNode(folderTree, doc, 0, 1,
doc. closeO;
Функция redrawNode является рекурсивной. Она рисует все узлы, папки и
вертикальные линии между узлами. Для выравнивания всех объектов в дереве
папок используется таблица без границы с расстояниями между ячейками и
отступами внутри ячеек равными нулю. redrawNode получает следующие пять
аргументов:
• foldersNode. Массив папок.
• doc. Объект документа, в который записывается код HTML.
• level. Положение папки в иерархии относительно корня. Например, ко-
рень имеет уровень 0, папки-потомки корня имеют уровень 1, и т. д.
• lastNode. Флаг для указания, является ли foldersNode последней папкой-
потомком своей родительской папки. Это не имеет особого значения,
за исключением выбора файла .gif для узла.
• leftSide. Строка кода HTML, которая выводит один или несколько фай-
лов Blank.gif и VertLine.gif слева от папки, имеющей уровень больше 0.
Этот код HTML необходим, потому что папки, отличные от корня, вы-
страиваются правее их родительской папки на несколько пикселей.
Важно отметить, что функция redrawNode при ее вызове принимает в каче-
стве первого параметра переменную foldersNode. Эта переменная представляет
первую папку в дереве, т. е. корень. Однако последующие рекурсивные вызо-
вы функции будут передавать каждую папку в иерархии. Кроме того, установка
foldersNode в ”0" означает, что папка удалена и поэтому не изображается.
if (foldersNode==0)
return;
II
Программирование
клиента с помощью
JavaScript
21 Основы JavaScript
22 Основы программирования на стороне клиента
23 Переадресация
24 Проверка ввода на стороне клиента
25 Работа с cookie на клиентской стороне
26 Работа с деревьями объектов
27 Управление апплетами
562
Глава 20
” WIDTH=16 HEIGHT=22>";
}
else { // не последняя папка
if (hasSubNode) {
if (expanded)
doc. write(nodel_ink +
"<IMG SRC='images/NodeMinus.gif +
" BORDER=O WIDTH=16 HEIGHT=22>” + "</A>");
else
doc.write(nodeLink +
”<IMG SRC=’images/NodePlus.gif +
’’ BORDER=O WIDTH=16 HEIGHT=22>" + ”</A>");
}
else
doc.write(”<IMG SRC=’images/Node.gif+
” WIDTH=16 HEIGHT=22>”);
leftside += "<IMG SRC=’images/vertline.gif+
” WIDTH=16 HEIGHT=22>”;
}
doc.write("<A HREF='javascript:top.clickFolder("
+ folderld + ")’><IMG SRC=images/”);
if (folderld == openFolderld)
doc.writeCopenfolder.gif WIDTH=24 HEIGHT=22”
+ " B0RDER=0></A>");
else
doc.write(”closedfolder. gif WIDTH=24 HEIGHT=22”
+ ’’ B0RDER=0x/A>”);
doc.write(”<TD VALIGN=middle ALIGN=left NOWRAPX);
doc.write(”<FONT SIZE=-1 FACE='Arial, Helvetica'>”
+ folderName +”</F0NT>");
doc.write("</TABLE>”)
Затем функция вызывает себя, увеличивая уровень на единицу, чтобы на-
рисовать папки-потомки, если папка раскрыта:
if (hasSubNode && expanded) {
level++;
for (i=3; KfoldersNode. length; i++)
if (i==foldersNode.length-1)
redrawNode(
foldersNode[i], doc, level, 1, leftside);
21
Основы
JavaScript
предыдущих 20-ти главах рассматривалось серверное программирование
с помощью сервлетов и страниц JSP. При таком программировании вся на-
грузка ложится на сервер. Так как популярный web-сайт может привлекать
большое количество посетителей, важна масштабируемость. Можно сделать
сайт более масштабируемым различными способами, одним из которых является
сокращение нагрузки на сервер за счет перемещения некоторой обработки на
клиентскую сторону — а именно, в браузер Web.
Допустим, что имеется регистрационная форма HTML, содержащая не-
сколько полей, которые должны заполняться пользователями. В случае только
серверной обработки необходимо проверить, что пользователь ввел все обя-
зательные значения. Даже если одно обязательное поле не заполнено, код должен
будет послать сообщение об ошибке. Сообщение об ошибке может посылать-
ся с другой страницы HTML или с той же самой регистрационной страницы.
При первом подходе пользователь сможет воспользоваться кнопкой Back в
браузере Web, чтобы вернуться на предыдущую страницу. Если сообщение об
ошибке встроено в ту же регистрационную форму, необходимо снова послать
форму и обеспечить передачу пользователю введенных ранее значений, что-
бы пользователям не приходилось вводить те же самые значения второй раз.
Любой из этих методов создает дополнительные обмены с сервером, т. е.
имеет место ненужная нагрузка на web-сервер. Более того, необходимо прове-
рять, что пользователь правильно ввел все значения — числовые значения для
564
Глава 20
js+="parent.appendFolder(parent.folderTree,”
+ id + ", parent.createFolder("
+ objectld + + name + ));\n";
}
Однако прежде чем добавить новые папки-потомки, необходимо удалить
предыдущие папки-потомки, добавленные при первом открытии папки. Это
делает код:
parent.deleteSubfolder(parent.folderTree, <%=id%>);
Иначе папки-потомки будут показаны дважды.
Возможные усовершенствования
Как говорилось выше, здесь разработаны не все свойства проекта. Напри-
мер, пользователь не может добавить или удалить файл или папку. Кроме того,
невозможно редактировать полномочия каждого пользователя и не введена
концепция групп.
Можно включить дополнительную информацию о каждом объекте, такую
как размер файла, дата создания, многоуровневые полномочия и т. д. Для мно-
гоуровневых полномочий следовало бы иметь полномочия на просмотр, на
редактирование и на просмотр/редактирование. Другим важным свойством,
не представленным здесь, является поддержание версий документов и регис-
трация действий пользователя.
Можнодобавить самые разные свойства с целью улучшения проекта. Сред-
ства, выбираемые для реализации, определяются вашими потребностями.
Заключение
Управление документами на основе Web является обширной темой, а создание
полноценной системы требует реализации множества различных свойств. Вэтой
главе была представлена концепция управления документами на основе Web и
рассмотрены ее основные свойства.
Основы JavaScript
569
потому, что JavaScript не совсем одинаково реализован в двух основных брау-
зерах. Браузеры Netscape строго учитывают регистр символов, в то время как
Internet Explorer относится к этому более свободно. Например, объекты, ме-
тоды и свойства, добавленные в язык в IE, не учитывают регистр символов, а
встроенные объекты, такие как Math и Date, учитывают.
Чтобы сохранить совместимость между двумя браузерами, необходимо строго
поддерживать в коде используемый регистр символов. IE нестрого относится
к регистру символов потому, что он разрешает применять те же клиентские
объекты в VBScript — языке сценариев, который нечувствителен к регистру
символов. Компания Microsoft считает, что ее клиентские объекты не должны
зависеть от регистра символов. Поэтому маловероятно, что в будущей версии
IE клиентские объекты станут зависеть от регистра символов. Это означает,
что нельзя пользоваться возможностью Navigator и применять регистр симво-
лов для создания различных переменных с одним написанием, но с разным
выделением заглавными буквами.
Завершение строки кода
В отличие от Java, точка с запятой, которая заканчивает строку кода, явля-
ется необязательной в JavaScript. Например, можно записать эти две строки
без точки с запятой:
str = "Cockroaches can live up to one year.";
a = 123;
Но если в одной строке размещается более одного оператора, то точка с
запятой является обязательной:
str = "cockroaches can live up to one year."; a = 123;
Для ясности лучше всегда использовать точку с запятой.
Комментарии
Комментарии JavaScript такие же, как и комментарии Java. Используйте //
для комментирования одиночной строки и пару /*...*/ для комментирования
нескольких строк. Например:
// divide by two
b = 489 / 2;
/* Now check which one of the two
is larger
* /
Основы JavaScript
571
Можно инициализировать переменную во время ее объявления. Например:
var s1 = "users";
Можно также инициализировать переменную при ее первом появлении в
операторе:
for (var i = 0; i < 100; i++)
Оператор if
В JavaScript оператор if является таким же, как в Java. Он имеет следующий
синтаксис:
if (expression) {
statement-1
statement-2
statement-m
}
[else {
statement-n
statement-o
statement-z
}]
Цикл while
Как и в Java, цикл while имеет следующий синтаксис:
while (expression) {
statement-1
statement-2
statement-m
}
568
Глава 21
числовых типов данных и т. д. Если одно из полей ввода будет неправильным,
придется попросить пользователя внести изменения. Наконец, необходимо
также убрать ведущие и завершающие пробелы, которые могут присутство-
вать в значении поля.
Хотелось бы, чтобы вводимые значения посылались на сервер только в том
случае, если все они являются допустимыми и их ведущие и завершающие
пробелы удалены. Это можно сделать с помощью клиентской обработки на
основе JavaScript. Почему JavaScript? Причина проста: JavaScript распознается
двумя доминирующими в настоящее время браузерами — Microsoft Internet
Explorer и Netscape Navigator. Для Интернет-разработчика знание JavaScript
является таким же важным, как и знание HTML.
В этой и последующих главах даются лишь базовые сведения о JavaScript.
JavaScript — серьезная тема, требующая отдельной книги. Тем не менее изу-
чение его основ позволит вам справиться с большинством проблем програм-
мирования на клиентской стороне, возникающих при разработке web-при-
ложения.
Эта глава предлагает краткий курс JavaScript и предназначена для тех, кто
не знаком с JavaScript. Читатели, знающие JavaScript, могут пропустить эту главу
и перейти к следующей.
Введение в JavaScript
Первоначально JavaScript назывался LiveScript. Язык JavaScript не имеет ниче-
го общего с Java. Выбор имени для JavaScript был обусловлен в основном мар-
кетинговыми соображениями. JavaScript был создан компанией Netscape, a Java
является продуктом Sun Microsystems. JavaScript — полный и зрелый язык,
однако в этой книге речь пойдет о реализации JavaScript в web-браузерах.
Необходимо помнить о том, что JavaScript по-разному реализован в
IntemetExplorer и Navigator. Компания Microsoft даже разработала свою соб-
ственную версию JavaScript с именем JScript. Эта книга использует ту часть
JavaScript, которая реализована обоими браузерами версий 3.0 и выше. Неко-
торые аспекты аналогичны в JavaScript и в Java. Это отмечается в последую-
щих разделах.
Регистр символов
JavaScript учитывает регистр символов, поэтому myVar не то же самое, что
MyVar или myvar. Имена обработчиков событий, такие как onclick, должны
записываться в JavaScript символами нижнего регистра. В HTML это имя мо-
жет быть записано как onClick или OnClick, поскольку HTML не учитывает
регистр символов. Путаница в отношении регистра символов часто возникает
Основы JavaScript
573
if (a==b)
str = 'The two numbers are equal.
else {
a = b + 12;
str = 'The two numbers are not equal.
}
Логические и побитовые операторы
Как и в Java, в качестве логического AND используется двойной ампер-
санд (&&), а в качестве логического OR — две вертикальные черты (||).
Одиночный амперсанд (&) обозначает побитовое AND, а одиночная вертикальная
черта (|) — побитовое OR.
Функции
Функция использует следующий синтаксис:
function functionName(arg-1, arg-2, ... arg-n) {
statement-1;
statement-2;
statement-m;
}
Следующий пример определяет функцию factorial, которая вычисляет
факториалы:
function factorial(n) {
if (n <= 1)
return 1;
else
return n * factorial(n-l);
}
Необходимо знать, как аргументы передаются в функцию: по ссылке или
по значению. В JavaScript это зависит от типа аргумента. Основное правило
говорит, что примитивные типы передаются по значению, а ссылочные типы
по ссылке. Примерами примитивных типов являются числа и логические зна-
чения. Они состоят из небольшого фиксированного числа байтов, которыми
легко манипулировать на нижних (примитивных) уровнях интерпретатора
JavaScript. С другой стороны, объекты и массивы являются ссылочными типами.
570
Глава 21
Строки
Строки могут заключаться в одиночные или двойные кавычки. Например,
следующие строки являются законными в JavaScript:
s1 = "Silk was discovered in about 3000 BC.";
s2 = 'Silk was first discovered in China.
Более того, двойные кавычки могут содержаться в строках, ограниченных
символами одиночной кавычки, а одиночные кавычки могут содержаться в
строках, ограниченных двойными кавычками. Например:
s1 = "His name is Tim O'Connor.";
s2 = 'He is known as Tim "The Great".
Если желательно придерживаться одного стиля программирования строк, мож-
но использовать символ обратной косой черты (\) перед одиночной или двойной
кавычкой. Приведенные выше строки можно переписать следующим образом:
s1 = 'His name is Tim 0\'Connor.';
s2 = "He is known as Tim \"The Great\".";
Как и в Java, знак плюс (+) используется для конкатенации строк. Например:
s1 = 'His name is Tim 0\’Connor.';
s2 = "He is known as Tim \”The Great\".";
s3 = s1 + + s2;
// s3 содержит: His name is Tim O’Connor. He is known as Tim "The Great".
Типизация
Java является строго типизированным языком, в то время как JavaScript —
слабо типизированный язык. Переменные JavaScript могут содержать значе-
ния любого типа данных; для преобразования одного типа данных в другой
используются функции (см. ниже).
Объявление переменных
Переменные можно использовать без предварительного объявления. Од-
нако хорошая практика программирования предполагает, что переменные все-
гда объявляются. Можно объявить переменную с помощью ключевого слова
var, за которым следует имя переменной, например:
var myVar;
Основы JavaScript
575
Недопустимые идентификаторы
Ниже проводятся идентификаторы, которые используются самим языком
JavaScript и не должны применяться в коде JavaScript:
alert Anchor Area Array assign
blur Boolean Button Checkbox clearTimeout
close closed confirm Date defaultstatus
Document document Element escape eval
FileUpload focus Form Frame frames
Function getClass hidden History history
Image isNaN java Java Array JavaClass
JavaObject JavaPackage length Link Location
location Math MimeType name navigate
Navigator navigator netscape Number Object
onblur onerror onfocus onload onunload
open opener Option Packages parent
parse Float parse I nt password Plugin prompt
prototype Radio ref Reset scroll
Select self setTimeout status String
Submit sun taint Text Textarea
top toString unescape untaint valueOf
Window window
Добавление кода JavaScript в HTML
На сервере код JavaScript представляется обычным текстом, так же как
HTML. Однако для web-браузера этот текст имеет специальное значение и
может интерпретироваться иным образом. Чтобы сообщить браузеру Web, что
часть текста является кодом JavaScript, он помещается между тегами <script> и
</script>:
<script language=”JavaScript” type="text/javascript">
// сценарии находятся здесь
</script>
В действительности, если только вы не хотите, чтобы страница HTML про-
ходила проверку в машине проверки HTML W3, атрибут type в теге script не
572
Глава 21
Цикл for
Как и в Java, цикл for в JavaScript имеет следующий синтаксис:
for (initialize ; test ; increment) {
statement-1
statement-2
statement-m
}
Ключевое слово with
JavaScript использует ключевое слово with, которое недоступно в Java. Син-
таксис показан ниже:
with (object) {
statement-1
statement-2
statement-m
}
Поэтому вместо кода:
х = Math.sin(a);
у = Math.cos(a);
можно записать код:
with (Math) {
х = sin(a);
у = cos(a);
}
Булевы выражения
Булево выражение можно записать с помощью операторов == и !=, так же
как и в Java. Например:
Основы JavaScript
577
</scnpt>
</body>
</html>
Теги <script> и </script> могут появляться более одного раза в различных
местах, если это необходимо.
Подобно другим тегам HTML, код JavaScript выполняется всякий раз, ког-
да web-браузер его встречает. Это означает, что если код JavaScript помещен в
раздел Head, он будет выполнен до того, как будут проанализированы теги
НТМ L в разделе Body. Однако если время обработки будет слишком большим,
пользователь может заметить задержку.
В качестве примера приведем страницу HTML, содержащую JavaScript,
который записывает «Hello»:
<html>
<head>
<title>Some JavaScript</title>
</head>
<body>
<script Language=”JavaScript’’>
document.write("Hello. ”);
</script>
</body>
</html>
Это можно записать следующим образом:
<html>
<head>
<title>Some JavaScript</title>
</head>
<body>
Hello.
</body>
</html>
^Примечанием В JavaScript функции могут появляться в любом месте
страницы HTML. Однако функция должна существовать в тот
момент, когда она вызывается. (Когда говорится, что
«функция существует», то имеется в виду, что web-браузер
прошел точку, где функция определена на странице HTML.)
574
Глава 21
Эти типы данных могут содержать произвольное число свойств или элемен-
тов. Поскольку объекты и массивы могут быть очень большими, не имеет смысла
передавать их по значению, так как в этом случае будет выполняться неэф-
фективное копирование и сравнение большого объема памяти. Что же касается
строковых типов данных, то строки передаются по ссылке, но при сравнении
строк в функции сравниваются значения, а не ссылки.
Объект String и строковый тип данных
Объект String широко используется в JavaScript. Среди других свойств String
имеет свойство length, которое возвращает число символов в строке:
var str, а;
str = ’’Let’s use JavaScript’’;
a = str.length; // а имеет значение 20 после выполнения этой строки
String имеет также следующие методы, знакомые любому программисту Java:
charAt, toUpperCase, substring и т. д. Список членов объекта String можно найти
в последнем разделе этой главы.
Ключевые слова JavaScript
Ниже приводится список ключевых слов JavaScript:
break continue delete else false
for function if in new
null return this true typeof
var void while with
Зарезервированные слова
К зарезервированным словам JavaScript относятся:
abstract boolean byte case catch
char class const default do
double extends final finally float
goto implements import instanceof int
interface long native package private
protected public short static super
switch synchronized throw throws transient
try
Основы JavaScript
579
Объект Window обсуждается более подробно в конце этой главы.
Обработчик событий
JavaScript добавляет свойство интерактивности к статическому HTML, позво-
ляя писать управляемый событиями код. Элемент HTML может быть соединен
с функцией, которая вызывается автоматически, когда что-то происходит
с элементом HTML. Например, можно соединить кнопку с функцией, которая
вызывается, когда пользователь щелкает на кнопке мышью, с помощью кода:
cinput type=button Name=Send onClick="handler">
В таблице 21.1 перечисляются поддерживаемые в JavaScript обработчики
событий.
Таблица 21.1. Обработчики событий JavaScript
Объект
Area
Button
Checkbox
FileUpload
Form
Frame
Image
Link
Radio
Reset
Select
Submit
Text
Textarea
Window
Обработчики событий
onClick, onMouseOut, on MouseOver
on Blur, onClick, on Focus
onBlur, onClick, onFocus
onBlur, onChange, onFocus
on Reset, onSubmit
on Load, on Unload
onAbort, on Error, on Load
onClick, onMouseOut, onMouseOver
onBlur, onClick, onFocus
onBlur, onClick, onFocus
onBlur, onChange, onFocus
onBlur, onClick, onFocus
onBlur, onChange, onFocus
onBlur, onChange, onFocus
onBlur, on Error, onFocus, on Load, on Unload
576
Глава 21
требуется. Атрибут language необходим, потому что существуют различные
языки сценариев, которые могут встраиваться с помощью тегов <script> и </
script>. Определив по тегу <Script>, какой язык используется для записи сце-
нария, браузер может решить, должен ли он пытаться его интерпретировать.
Если сценарий написан на языке, который web-браузер не понимает, сцена-
рий будет пропущен.
Можно разместить код JavaScript влюбом месте кода HTML. Можно напи-
сать сценарий JavaScript между тегами <head> и </head> следующим образом:
<html>
<head>
<script language="JavaScript" type="text/javascript">
// сценарии находятся здесь
</script>
</head>
<body>
Здесь находится тело HTML
</body>
</html>
Либо можно вставить его в раздел body:
<html>
<head>
</head>
<body>
<script language^’JavaScript" type="text/javascript">
// сценарии находятся здесь
</script>
Здесь находится тело HTML
</body>
</html>
или
<html>
<head>
</head>
<body>
Здесь находится тело HTML
<script language="JavaScript" type="text/javascript’>
// сценарии находятся здесь
Основы JavaScript
581
netscape Ссылка на объект JavaPackage, который является вершиной
иерархии имен пакетов Java для пакетов Java netscape компании
Netscape.
opener Свойство для чтения/записи, которое ссылается на объект
Window, вызвавший метод ореп() для создания этого окна.
packages Ссылка на объект JavaPackage, который представляет верши-
ну иерархии имен пакетов Java.
parent Ссылка на родительское окно или рамку текущего окна. По-
лезна, только когда текущее окно является рамкой, а не окном
верхнего уровня.
self Ссылка на само окно. Синоним для window.
status Строка для чтения/записи, которая определяет текущее содер-
жимое статусной строки.
sun Ссылка на объект JavaPackage, который является вершиной
иерархии имен пакетов Java для пакетов Java sun компании Sun
Microsystems.
top Ссылка на окно верхнего уровня, содержащее текущее окно.
Полезна, только когда текущее окно является рамкой, но не
окном верхнего уровня.
window Ссылка на само окно. Синоним для self.
Методы
Ниже приводятся методы объекта Window.
alertO Выводит простое сообщение в диалоговом окне.
ЫигО Получает фокус клавиатуры от окна браузера верхнего уровня;
при этом окно переводится в фоновый режим на большинстве
платформ.
clearTimeoutO Отменяет отложенную на определенный период време-
ни операцию.
closeO Закрывает окно.
confinnO Задает вопрос «yes» или «по» с помощью диалогового окна.
focusO Передает фокус клавиатуры окну браузера верхнего уровня;
при этом окно переводится в режим переднего плана на боль-
шинстве платформ.
ореп() Создает и открывает новое окно.
promptO Запрашивает ввод простой строки с помощью диалогового окна.
scrollQ Прокручивает документ, отображаемый в окне.
578
Глава 21
Объектная модель JavaScript
Программирование JavaScript основывается на объектной модели JavaScript.
На рис. 21.1 показано подмножество этой объектной модели.
Рис. 21.1. Подмножество объектной модели JavaScript
Объект Window является центральным объектом, который представляет
окно web-браузера. Он имеет ряд объектов-потомков: Location, Document,
History и другие (не показаны на рис. 21.1). Каждый объект Window содержит
свойство document, которое ссылается на объект Document, связанный с ок-
ном. В JavaScript действует соглашение о том, что объект Window применяется
по умолчанию. Это означает, что для использования одного из объектов-потом-
ков или свойств достаточно упомянуть в коде объект-потомок, свойство или
метод. Чтобы сослаться на объект в нижней иерархии, нужно отделить его точ-
кой от объекта-предка. Например, для ссылки на первый элемент HTML вто-
рой формы страницы HTML используется код:
document.forms[1].elements[0]
Объект Window имеет ряд методов: alert(), confirm(), prompt(). Можно исполь-
зовать метод объекта Window, не упоминая объект Window в коде, например:
alert('Please enter a value in the "Phone" box.');
Это то же самое, что и запись:
window.alertCPlease enter a value in the "Phone" box.');
Основы программирования
на стороне клиента
JL Программирование на стороне клиента способствует повышению уровня
масштабируемости приложения и может предложить пользователю больше
удобств. Однако при создании кода для клиентской стороны возникают различ-
ные проблемы, отличные от проблем серверного программирования. Код должен
выполняться различными браузерами с разными возможностями и мощностью.
Создание кода, работающего во многих браузерах, требует больших усилий.
В этой главе мы познакомимся с проблемами, которые встречаются при
программировании на клиентской стороне. Существуют программы, помога-
ющие в решении этих проблем.
Глава начинается с рассмотрения простой функции: как проверить, может
ли код JavaScript выполняться в браузере пользователя. Затем мы обсудим, как
различные версии браузеров и различные версии JavaScript влияют на выбор
приемов написания кода.
Проверка поддержки JavaScript
JavaScript позволяет выйти за пределы возможностей HTML. Однако необхо-
димо помнить, что до сих пор все еще используются браузеры, которые не
понимают JavaScript.
Даже если вы уверены, что все пользователи, которые посещают ваш web-
сайт, имеют web-браузер по крайней мере четвертого поколения, все равно
580
Глава 21
К сожалению, существует некоторая несовместимость между браузерами,
работающими в UNIX и в Windows. Например, Netscape Navigator для Windows
не распознает событие Click для элемента Area. В Netscape Navigator для UNIX
некоторые элементы формы — такие, как Area, button, checkbox, radio, reset,
select и submit,—распознают только событие Click, но не события Blur и Focus.
Существует тип событий, которые не порождаются взаимодействием с
пользователем. Так, событие Timer возникает, когда проходит определенный
период времени. Это может быть полезно при создании анимации.
Объекты Window и String
В этом разделе приводится краткий справочник по двум наиболее широко
используемым в JavaScript объектам: Window и String.
Объект Window
Объект Window представляет окно браузера или рамку. Он является корнем
объектной иерархии JavaScript.
Свойства
Ниже приводится список свойств объекта Window.
closed Булево значение только для чтения, которое определяет, было
ли закрыто окно.
defaultstatus Строка для чтения/записи, которая определяет сообще-
ние по умолчанию для вывода в статусной строке.
document Ссылка на объект Document, содержащийся в окне.
frames[] Массив рамок, содержащихся в окне.
history сылка на объект History этого окна.
java Ссылка на объект JavaPackage, который является вершиной
иерархии имен пакетов для базовых пакетов Java, составляющих
язык Java.
length Значение только для чтения, которое определяет число элемен-
тов в массиве frames[].
location Ссылка на объект Location этого окна.
Math Ссылка на объект, содержащий различные математические функ-
ции и константы.
name Строка, которая содержит имя окна. Имя создается при жела-
нии во время создания окна с помощью метода ореп().
navigator Ссылка на объект Navigator, который применяется к этому и
всем другим окнам.
Основы программирования на стороне клиента
585
Пользователи, которые не могут обрабатывать JavaScript, увидят это сооб-
щение.
Эта методика может использоваться в Netscape Navigator версии 2 и выше и
в Internet Explorer версии 3 и выше.
Проверка JavaScript с помощью формы
Предыдущий способ работает хорошо, но иногда лишь одной определен-
ной странице в web-приложении нужно использовать JavaScript в браузере —
например, при отправке пользователем формы. Не хотелось бы заставлять
пользователя в этом месте включать поддержку JavaScript. Предположим, что
web-приложение, поддерживающее членство в каком-то сообществе, исполь-
зует JavaScript только для проверки ввода на страницах членов. В этом случае
пользователи могут посещать страницы без JavaScript, которые пытаются при-
влечь их присоединиться к сообществу. Только становясь членами, они должны
будут включить поддержку JavaScript. Тестирование можно выполнять, когда
пользователь собирается отправить форму — возможно, форму Login, — прежде
чем он попадет в предназначенную только для членов область.
В листинге 22.2 показана форма, которая проверяет наличие поддержки
JavaScript. Страница HTML содержит скрытый элемент с именем
JavaScriptEnabled, начальное значение которого равно 0. Когда форма посы-
лается, можно проверить значение JavaScriptEnabled на серверной стороне.
Если это значение равно 0, то JavaScript не поддерживается или не включена
поддержка в браузере клиента. Можно либо предупредить пользователя, по-
слав страницу «Message», либо вы пол нить дополнительный шаг обработки. По
крайней мере, станет понятно, что JavaScript не поддерживается у этого пользо-
вателя. Если JavaScript применяется для проверки ввода, то пользовательский
ввод будет проверяться на сервере для тех, кю использует браузеры, не под-
держивающие сценарии.
Если же сервер получает элемент JavaScriptEnable со значением 1, то работу
выполнит JavaScript в браузере.
Листинг 22.2 Использование формы для выяснения того, что JavaScript
включен
<НТМ1_>
<HEAD>
<Т1Т1_Е>Проверка JavaScript с помощью формы</Т1Т1_Е>
</HEAD>
<BODY>
<FORM NAME =LoginForm METHOD=POST ACTION=Register.jsp
onSubmit="document.LoginForm.JavaScriptEnabled.value=1”>
582
Глава 21
setTimeoutO Выполняет код после истечения определенного периода
времени.
Объект String
Объект String предоставляет свойство length, которое возвращает число сим-
волов в строке, и следующие методы:
anchorO Возвращает копию строки в контексте <А Name=>.
big() Возвращает копию строки в контексте <ВЮ>.
blinkO Возвращает копию строки в контексте <BLINK>.
boldO Возвращает копию строки в контексте <В>.
charAtO Извлекает символ в данной позиции строки.
fixedO Возвращает копию строки в контексте <ТТ>.
fontcolor() Возвращает копию строки в контексте <FONT.COLOR=>.
fontsizeO Возвращает копию строки в контексте <FONT SIZE=>.
indexOfO Ищет в строке символ или подстроку.
italicsO Возвращает копию строки в контексте <1>.
lastlndexOfO Ищет символ или подстроку в строке в обратном порядке.
linkO Возвращает копию строки в контексте <А HREF=>
smallO Возвращает копию строки в контексте <SMALL>.
spIitO Преобразует строку в массив строк с помощью указанного
символа ограничителя.
strikeO Возвращает копию строки в контексте <STRIKE>.
sub() Возвращает копию строки в контексте <SUB>.
substringO Извлекает подстроку из строки.
supO Возвращает копию строки в контексте <SUP>.
toLowerCaseO Возвращает копию строки, преобразуя все символы в
нижний регистр.
toUpperCaseO Возвращает копию строки, преобразуя все символы в
верхний регистр.
Заключение
Эта глава является кратким курсом JavaScript для тех, кто незнаком с этим язы-
ком. Изучив ее, читатель сможет писать клиентские программы, которые будут
выполняться web-браузером.
Основы программирования на стороне клиента
587
Проверка включения поддержки JavaScrip в
браузерах, поддерживающих JavaScript
В отличие от двух предыдущих методов, которые действуют в условиях пол-
ного отсутствия каких-либо сведений о поддержке JavaScript в браузере, сле-
дующий метод используется для определения, включен ли JavaScript в браузе-
ре, который поддерживает JavaScript. Он работает следующим образом: все
поддерживающие JavaScript браузеры будут игнорировать теги <SCRIPT> ...
</SCRI РТ>, если JavaScript отключен. Однако при отключенном JavaScript эти
браузеры будут интерпретировать теги <NOSCRIPT> ... </NOSCRIPT>. С
помощью этих тегов можно попросить пользователя включить поддержку
JavaScript в браузере.
Страница HTML, которая использует этот прием, представлена в лис-
тинге 22.4.
Л исти н г 22.4 Код для проверки включения JavaScript в браузерах,
поддерживающих JavaScript
<HTML>
<HEAD>
<Т1Т1_Е>Проверка включения JavaScript в браузерах,
поддерживающих JavaScript</TITLE>
</HEAD>
<BODY>
<SCRIPT LANGUAGE="JavaScript">
// здесь код JavaScript
</SCRIPT>
<NOSCRIPT>
Ваш браузер поддерживает JavaScript. Однако эта возможность
сейчас отключена. Пожалуйста, включите ее, чтобы использовать
все возможности этой страницы.
</NOSCRIPT>
</BODY>
</HTML>
Этот метод можно использовать в Netscape Navigator версии 2 и выше и в
Internet Explorer версии 3 и выше.
Работа с браузерами, не
поддерживающими JavaScript
Редко, но все еще используются старые браузеры, которые не поддерживают
JavaScript. Если нет гарантии того, что все пользователи имеют браузеры,
584
Глава 22
нет полной свободы действий в использовании свойств JavaScript. Кроме того,
большое число пользователей Интернета не знают, что такое JavaScript, не
говоря уже о том, чтобы суметь включить эту возможность. Это означает, что
при программировании web-страниц на JavaScript важно убедиться в том, что
JavaScript поддерживается и эта поддержка включена.
Можно использовать ряд методов для проверки того, что браузер пользова-
теля поддерживает JavaScript.
Проверка с помощью переадресации
Использование переадресации является простейшим способом определения
того, что JavaScript поддерживается. Соответствующий сценарий размещается
на первой странице web-сайта, который требует обработки JavaScript. Сцена-
рий предполагает, что JavaScript не поддерживается или не включен, поэтому
пользователи будут получать сообщение, предлагающее им сменить браузер
или включить поддержку JavaScript. Пользователи, у которых включена под-
держка JavaScript, будут переадресовываться на основную страницу, прежде
чем они увидят сообщение. Затем они смогут продолжить свое перемещение
по сайту.
Страница показана в листинге 22.1. Перенаправление пользователя на дру-
гую страницу производится с помощью свойства location объекта window из
объектной модели документа Netscape Navigator.
Листинг 22.1 Переадресация с помощью свойства localtion
<HTML>
<HEAD>
<ТГП_Е>Проверка, что поддержка JavaScript включена </TITLE>
<SCRIPT LANGUAGE^’JavaScript">
location="ActualMainPage.html"
</SCRIPT>
</HEAD>
<BODY>
Этот web-сайт требует применения браузера,
поддерживающего JavaScript. К сожалению, используемый
браузер является либо слишком старым, либо поддержка
JavaScript выключена.
Щелкните, пожалуйста, <А HREF="Message.И1тГ’>здесь</А>,
чтобы получить дополнительную информацию.
</BODY>
</HTML> •
Основы программирования на стороне клиента
589
язык JavaScript 1.2, но вы не можете гарантировать, что пользователи не будут
применять браузеры Navigator 2 и 3 и Internet Explorer 3, то вам придется
включить версии JavaScript, которые будут восприниматься более старыми
браузерами.
Листинг 22.6 предлагает схему для обработки нескольких версий JavaScript.
Необходимо размещать теги <SCRIPT> в убывающем порядке версий. Будет
выполняться последняя функция, которую браузер сможет интерпретировать.
Листинг 22.6 Страница, которая обрабатывает различные версии
JavaScript
<HTML>
<HEAD>
<Т1Т1Е>0бработка различных версий JavaScript</TITLE>
<SCRIPT LANGUAGE=”JavaScript”>
// операторы для браузеров JavaScript 1.0
</SCRIPT>
<SCRIPT LANGUAGE^’JavaScript!. T>
<! -
// операторы для браузеров JavaScript 1.1
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript1.2">
// операторы для браузеров JavaScript 1.2
</SCRIPT>
</HEAD>
</HTML>
Эту методику можно использовать в Netscape Navigator версии 2 и выше и в
Internet Explorer версии 3 и выше.
Включение файла JavaScript
При создании кода JavaScript, который будет выполняться на стороне клиен-
та, часто формируется пакет функций JavaScript, используемых на нескольких
страницах. Вставка всех функций во все эти страницы создает большие про-
блемы для сопровождения. Решение состоит в сохранении функций в отдельном
файле (предпочтительно с расширением .js) и во включении его в атрибут SRC
586
Глава 22
Листинг 22.2 Продолжение
CINPUT TYPE=HIDDEN NAME=JavaScriptEnabled VALUE=O>
CINPUT TYPE=TEXT NAME=UserName>CBR>
CINPUT TYPE=PASSWORD NAME=Password><BR>
CINPUT TYPE=RESET> CINPUT TYPE=SUBMIT VALUE=Login>
C/FORM>
C/BODY>
C/HTML>
Предупреждение! Нельзя полностью полагаться на работу JavaScript и
рассчитывать на то, что браузер пошлет допустимые
входные данные, если поддержка JavaScript включена.
Знающий пользователь может отключить JavaScript и
создать для сервера впечатление, что он включен.
Например, форма в листинге 22.3 обманывает сервер,
делая вид, что JavaScript включен, хотя на самом деле
это не так.
Листинг 22.3 Попытка обмануть сервер, который проверяет включение
поддержки JavaScript
CHTML>
CHEAD>
сТ1Т1_Е>Пусть сервер считает, что поддержка JavaScript включенас/Т1Т1_Е>
C/HEAD>
CBODY>
CFORM NAME =LoginForm METHOD=POST ACTION=Register.jsp>
CINPUT TYPE=HIDDEN NAME=JavaScriptEnabled VALUE=1>
CINPUT TYPE=TEXT NAME=UserName>cBR>
CINPUT TYPE=PASSWORD NAME=Password>cBR>
CINPUT TYPE=RESET> CINPUT TYPE=SUBMIT VALUE=Login>
C/FORM>
C/BODY>
C/HTML>
Если контроль ввода является критически важным для приложения, необ-
ходимо всегда выполнять клиентскую и серверную проверку. Клиентская про-
верка с помощью JavaScript полезна также в плане сокращения нагрузки на
сервер.
Эта методика работает в Netscape Navigator версии 2.0 и выше и в Internet
Explorer версии 3.0 и выше.
KttK устроены сервлеты
591
var os;
var v = navigator.appVersion.toUpperCaseO;
if (1+v.index0f('WIN95') || 1+v.indexOf('WINDOWS 95')
|| 1+v.index0f('WIN32'))
os = 'Win95';
else if (1+v.indexOf('WIN98')
|| 1+v.indexOf('WINDOWS 98'))
os = 'Win98';
else if (1+v.indexOf('WINNT')
11 1+v.indexOfCWINDOWS NT'))
os = 'WinNT'; //WinNT или Win2K
else if (1+v.indexOf('WINDOWS 3.1'))
os = 'Win3.1';
else if (1+v.indexOf('WINDOWS 3'))
os = 'Win3';
else if (1+v.index0f('WIN16'))
os = 'Win16'; // может быть 3.x или NT
else if((1+v.indexOf('MAC)) &&
(1+v. indexOfC PPC) || 1+v.indexOfCPOWERPC')))
os = 'Macintosh ppc';
else if (1+v.indexOfCMAC))
os = 'Mac68K';
else if (1+v.indexOf('SUNOS'))
os = 'Sun';
else if (1+v.indexOfCLINUX'))
os = 'Linux';
else if (1+v.indexOfCIRIX'))
os = 'Irix'
else if (1+v.index0f('HP-UX'))
os = 'HP-UX';
else if (1+v.index0f('0SF'))
os = 'OSF';
else if (1+v.indexOfCAIX'))
os = 'AIX';
else if (1+v.indexOf('OS/2'))
os = 'OS/2';
else if (1+v.indexOf('WEBTW))
os = 'WebTv';
document.forms[0].OS.value=os;
}
//->
</SCRIPT>
</HEAD>
<BODY>
588
Глава 22
которые понимают JavaScript, необходимо учитывать, что будет происходить
во время визуализации сценария в этих браузерах. Как минимум сценарий будет
изображен как есть, что может сбить пользователя с толку, так как он увидит
текст, не имеющий смысла. В худшем случае JavaScript может привести содер-
жимое страницы в полный беспорядок.
Можно справиться с этой ситуацией, поместив строки сценария между
символами комментария HTML, т. е. между тегами <!— и —>. Большинство
браузеров, поддерживающих сценарии, игнорируют содержимое этих тегов.
В листинге 22.5 представлен пример страницы, которая скрывает код
JavaScript от браузеров, не поддерживающих JavaScript.
Л истинг 22.5 Сокрытие JavaScript от не поддерживающего JavaScript
браузера
<HTML>
<HEAD>
<Т1Т1_Е>Страница, которая скрывает JavaScript от примитивных браузеров
</TITLE>
<SCRIPT LANGUAGES JavaScript ">
<! _
Код JavaScript находится здесь
// ->
</SCRIPT>
</HEAD>
</HTML>
Два слэша в конце конструкции предназначены для браузеров, которые
поддерживают JavaScript. Они предписывают интерпретатору JavaScript про-
пустить строку полностью; иначе JavaScript будет пытаться интерпретировать
тег (—>), завершающий HTML-комментарий.
Можно использовать этот метод для Netscape Navigator версии 2 и выше и
для Internet Explorer версии 3 и выше.
Работа с различными версиями JavaScript
JavaScript версии 1.0 был встроен в Netscape Navigator 2.0. Когда был выпущен
Navigator 3, он содержал более зрелую версию JavaScript: версию 1.1. В Navigator 4
был включен JavaScript версии 1.2. Для браузера компании Microsoft история вер-
сий JavaScript совершенно другая. Internet Explorer 3 воспринимает уровень
JavaScript в Navigator 2; однако Internet Explorer 4 понимает JavaScript 1.2.
Естественно, что каждая новая версия JavaScript содержит свойства, которые
были недоступны в предыдущих версиях. Если вам необходимо использовать
Основыпрограммирования на стороне клиента
593
Листинг 22.9 Получение информации о поколении браузера -
<SCRIPT LANGUAGE="JavaScript">
<function getBrowserGeneration() {
return navigator.appVersion.charAt(O);
}
</SCRIPT>
Эта методика может использоваться для Netscape Navigator версии 2 и выше
и для Internet Explorer версии 3 и выше.
Проверка типа браузера
При программировании JavaScript, особенно при использовании динамичес-
кого HTML (DHTML), необходимо проверять, применяется IE или Netscape,
так как эти браузеры имеют различные стандарты для DHTML. Две функции
JavaScript (см. л истин г 22.10) выполняют эту задачу. Функция isNetscapeBrowser
возвращает true, если пользователь имеет браузер Netscape. Функция
isMicrosoftBrowser возвращает true, если применяется браузер Microsoft.
Л исти н г 22.10 Проверка типа браузера
<SCRIPT LANGUAGE="JavaScript">
function isNetscapeBrowser() {
return (navigator.appName=="Netscape");
}
function isMicrosoftBrowser() {
return (navigator. userAgent.indexOf("MSIE")!=-1);
}
</SCRIPT>
Эта методика работает c Netscape Navigator версий 2 и выше и с Internet
Explorer версии 3 и выше.
Проверка языка браузера
Web-мастер международной корпорации должен, вероятно, обеспечить под-
держку нескольких версий web-сайта компании на различных языках. В
Netscape Navigator 4 и выше можно запросить свойство language объекта
navigator, чтобы определить версию языка браузера. Однако такое свойство
отсутствует в Internet Explorer.
590
Глава 2
тега <SCRIPT>. Листинг 22.7 показывает страницу, которая использует атри-
бут SRC тега <SCRIPT> для добавления исходного файла JavaScript.
Л исти н г 22.7 Включение исходного файла JavaScript
<НТМ1_>
<HEAD>
<Т1ТЕЕ>Включение исходного файла JavaScript </TITLE>
<SCRIPT LANGUAGE^ JavaScript' SRC="MyFunctions.js">
</HEAD>
<BOOY>
<SCRIPT LANGUAGES’JavaScript’>
// Здесь можно вызвать функцию JavaScript из файла MyFunctions.js
</SCRIPT>
</BODY>
</HTML>
Отметим, что включаемый файл не начинается с тега <SCRIPT> и не за-
канчивается тегом </SCRlPT>. Файл также не обязан иметь расширение .js.
Можно использовать эту методику в Netscape Navigator версии 2 и выше и в
Internet Explorer версии 3 и выше.
Проверка операционной системы
Можно задаться вопросом, существуют ли такие обстоятельства, которые требу-
ют проверки, какую операционную систему использует посетитель web-сайта. В
конце концов, выбор операционной системы — дело пользователя. И если его
браузер поддерживает некоторую версию JavaScript, можно спокойно писать про-
грамму. Но можно рассмотреть, к примеру, ситуацию, когда web-сайт предостав-
ляет драй веры устройств для различных операционных систем: от Windows до Мас
и от UNIX до Linux. Желательно, чтобы посетители сайта (которые, очевидно,
посещают его с целью загрузки драйвера устройства) получали ссылку на версию
операционной системы, которая используется на их компьютере. Методика про-
верки операционной системы представлена в листинге 22.8.
Листинг 22.8 Проверка операционной системы пользователя
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function getOperatingSystem(){
Основы программирования на стороне клиента
595
}
</SCRIPT>
Без привлечения функции eval код необходимо переписать, как показано в
листинге 22.12.
Листинг 22.12 Модификация кода листинга 22.11
<SCRIPT LANGUAGE="JavaScript">
, var nO = "train";
var n1 = "abc4";
var n2 = 2;
alert(nO);
alert(nl);
alert(n2);
</SCRIPT>
Этот метод работаете Netscape Navigator версии 2 и выше и c Internet Explorer
версии 3 и выше.
Заключение
В этой главе было продемонстрировано несколько методов, которые могут быть
полезны при кодировании на клиентской стороне. Вы узнали, как проверить,
что браузер поддерживает JavaScript и поддержка JavaScript включена, и как вклю-
чить пакет функций JavaScript в файл. Кроме того, были представлены некото-
рые возможности четвертого поколения браузеров, и было показано, как ис-
пользовать функцию eval для обработки переменных, имена которых могут
изменяться на странице.
Эти методы могут показаться простыми, но их должен знать каждый
серьезный разработчик Web.
592
Глава 22
Листинг 22.8 Продолжение
Welcome to Brainy Software. We are happy to provide you
with the version of device driver that works with
your operating system.
Please click the button below to start downloading.
<BR>
<FORM METHOD=POST ACTION="Download.jsp"
onSubmit=’getOperatingSystem()’>
CINPUT TYPE=HIDDEN NAME=OS>
CINPUT TYPE=SUBMIT VALUE="Download">
c/FORM>
C/B0DY>
C/HTML>
При отправке скрытый элемент ввода OS будет иметь значение, соответ-
ствующее операционной системе пользователя. Другим способом определения
применяемой операционной системы является проверка на сервере значения
строки USER AGENT заголовка HTTP, однако обработка на клиентской стороне
сокращает нагрузку на сервер.
Можно использовать эту методику для Netscape Navigator версии 2 и выше
и для Internet Explorer версии 3 и выше.
Проверка поколения браузера
Если знать, какое поколение браузеров применяет пользователь, можно об-
служивать различные версии JavaScript, посылая подходящую функцию, ко-
торая работает в данном браузере.
Для этой цели можно использовать свойство аррVersion объекта navigator.
Возвращаемое этим свойством значение имеет вид:
version number [language] (operating system information)
Например, в браузере Netscape Navigator 4.02 для Windows 95 возвращаемым зна-
чением этого свойства является 4.02 — (Win95;I;Nav). В Internet Explorer 3.01 для
Windows 95 это будет 2.0 (compatible; MS IE 3.01;Windows 95). Последний браузер в
действительности эмулирует Navigator 2. Поэтому версия 3 IE имеет версию прило-
жения 2. Отметим также, что элемент language является необязательным.
JavaScript-функция getBrowserGeneration (см. листинг 22.9) может исполь-
зоваться для получения информации о поколении браузера.
Переадресация
597
переадресация создает в браузере cookie, то отказ может происходить по той
причине, что браузер не принимает cookie.
При любой переадресации предоставляйте ссылку, на которой пользова-
тель сможет щелкнуть мышью в случае нарушения переадресации. В листинге
23.1 показана страница, которая обеспечивает такую ссылку.
Л исти н г 23.1 Страница, которая предупреждает нарушение переадресации
<НТМ1>
<HEAD>
<Т1Т1Е>Предупреждение нарушения переадресации</Т1Т1Е>
<SCRIPT LANGUAGE^ JavaScript">
// код для переадресации
</SCRIPT>
</НЕА0>
<BODY>
Щелкните <А HREF="RedirectionURL">3flecb,</A>
если переадресация не была выполнена автоматически.
</B0DY>
</HTML>
Использование meta-тега refresh
Простейшим способом переадресации страницы, выводимой на определенный
период времени, является использование тега meta. Тег meta для переадресации
имеет следующую форму:
<МЕТА HTTP-EQUIV=”Refresh" C0NTENT="x;URL=http://flpyron_URL">
Здесь х является числом секунд, ожидаемых браузером перед выполне-
нием переадресации.
Например, сценарий в листинге 23.2 переадресует браузер на http://
www.newriders.com по истечении 5 секунд.
Листинг 23.2 Переадресация с помощью тега meta
<HTML>
<HEAD>
<Т1Т1Е>Применение тега meta для переадресации пользователя</Т1Т1_Е>
<МЕТА HTTP-EOUIV="Refresh" C0NTENT="5;URL=http://www.newriders.com">
</HEAD>
<BODY>
594
Глава 22
Например, если браузер использует немецкий язык, значением
navigator.language будет de. В таблице 22.1 перечислены коды языков.
Таблица 22.1. Коды некоторых языков для navigator.language
Код языка
da
de
en
es
fr
it
ja
ko
nl
Pt
SV
Язык
Датский
Немецкий
Английский
Испанский
Французский
Итальянский
Японский
Корейский
Голландский
Бразильский португальский
Шведский
Если невозможно гарантировать, что все пользователи будут применять браузер
Netscape версии 4 и выше, необходимо делать эту проверку на серверной стороне.
Эта методика работает только с Netscape Navigator 4 и более старших версий.
Использование динамических имен
переменных
При программировании на JavaScript можно столкнуться с ситуацией, когда
необходимо использовать переменную, имя которой не является фиксирован-
ным. Может ли JavaScript обрабатывать переменные имена переменных? Да.
Если имя переменной может быть динамическим, полезно применять функ-
цию eval.
Листинг 22.11 представляет пример использования переменной, имя кото-
рой должно изменяться.
Листинг22.11 Использование переменного имени переменной
<SCRIPT LANGUAGE="JavaScript">
var nO = "train";
var n1 = "abc4";
var n2 = 2;
for (var i=0; i<3; i++) {
alert(eval("n" + i));
Переадресация
599
Листинг 23.4 Переадресация с помощью объекта location
<HTML>
<HEAD>
<Т1Т1_Е>Переадресация с помощью объекта location </TITLE>
<SCRIPT LANGUAGE="JavaScript">
locations'http://www.newriders.com’;
</SCRIPT>
</HEAD>
<BODY>
Вы будете перенаправлены на другую страницу.
Щелкните <А HREF="http://www.newriders.com">3flecb,</A> если переадресация
не будет выполнена автоматически.
</BODY>
</HTML>
Чтобы переадресовать пользователя после нескольких секунд (например,
.для создания экрана-вспышки), примените метод setTimeout:
setTimeout("locations’anotherURL’", у);
Здесь у — число миллисекунд, ожидаемых перед тем, как пользователь бу-
дет перенаправлен на anotherURL. Листинг 23.5 переадресует пользователя
на www.newriders.com по истечении двух секунд.
Листинг 23.5 Создание экрана-вспышки с помощью объекта location
<HTML>
<HEAD>
<ТГП_Е>Экран-вспышка с применением объекта location</TITLE>
<SCRIPT LANGUAGE="JavaScript">
setTimeout("locations'http://www.newriders.com’", 2000);
</SCRIPT>
</HEAD> '
<BODY>
Greeting
<IMG SRC="logo.gif">
</BODY>
</HTML>
Эта методика работает c Netscape Navigator 2 и выше и c Internet Explorer 3 и
выше.
23
Переадресация
Переадресация пользователя на другую страницу часто применяется в
web-программировании. Например, можно создать эффект вспышки экра-
на — вывести логотип компании на несколько секунд, а затем переадресовать
пользователя на основную страницу. Или вы можете послать браузеру cookie,
записав его в ответе HTTP, плюс указание, которое просит браузер извлечь
другую страницу, — таким образом эффективно создав cookie за один проход.
Два упомянутых примера переадресуют пользователя без взаимодействия с
ним. В других ситуациях может понадобиться, чтобы переадресация происхо-
дила после того, как пользователь выполнит какое-то действие.
В этой главе представлено несколько примеров переадресации страницы,
в том числе обсуждается, как предупредить нарушение переадресации (кото-
рое может происходить по разным причинам).
Предупреждение нарушения
переадресации
Если должна осуществляться автоматическая переадресация без вмешатель-
ства пользователя, не забудьте предоставить пользователям ссылку для пере-
хода на случай нарушения переадресации.
Переадресация может быть не выполнена, если, например, браузер не поддер-
живает JavaScript или он поддерживает JavaScript, но это свойство отключено. Если
Переадресация
601
Листинг 23.7 Продолжение
Щелкните <А HREF="javascript:history.go(-3)">3flecb,</A>
чтобы вернуться к началу формы ввода.
</BODV>
</HTML>
Эта методика работает с Netscape Navigator! и выше и с Internet Explorer 3 и
выше.
Перемещение вперед
Метод go объекта history можно использовать и для перемещения вперед, если
передать в качестве аргумента положительное число. Код листинга 23.8 пре-
доставляет страницу со ссылкой для перемещения вперед.
Листинг 23.8 Перемещение вперед
<HTML>
<HEAD>
<ТГП_Е>Перемещение вперед</Т1Т1Е>
</HEAD>
<BODY>
Щелкните <А HREF=”javascript:history.до(1)">здесь</А>
для перемещения вперед.
</BODY>
</HTML>
Отметим, что не требуется использовать знак плюс (+) перед числом в ме-
тоде go.
Эта методика работает с Netscape Navigator 2 и выше и с Internet Explorer 3 и
выше.
Перемещение с помощью элемента SELECT
Возможна ситуация, когда нужно разрешить пользователю выбрать на теку-
щей странице новое место назначения, скажем, продукт, который он хочет
посмотреть, или филиал компании, который его интересует. Для этой цели
можно использовать элемент SELECT. Например, код листинга 23.9 приме-
няет элемент SELECT для выбора одного из региональных офисов. Пользова-
тель может щелкнуть мышью на выбранном элементе для переадресации по
соответствующему U RL.
598
Глава 23
Листинг 23.2 Продолжение
Вы будете перенаправлены на другую страницу.
Щелкните <А HREF="http://www.newriders.com">3flecb,</A> если переадресация
не будет выполнена автоматически.
</BODY>
</НТМ1>
Если задать х равным 0, переадресация будет выполняться без задержки.
С помощью ненулевого значения х можно создать экран-вспышку, выво-
димый на х секунд, прежде чем браузер будет переадресован на другую стра-
ницу. Листинг 23.3 предоставляет код, который выводит экран на 3 секунды,
после чего пользователь переадресуется куда-то еще.
Листинг 23.3 Экран-вспышка
<HTML>
<НЕАО>
<Т1Т1Е>Применение тега meta для переадресации пользователя</Т1Т1_Е>
<МЕТА HTTP-EQUIV="Refresh" C0NTENT=”3;URL=http://anotherPage">
</HEAD>
<BODY>
Welcome to Brainy Software
<IMG SRC="logo.jpg">
</BODY>
</HTML>
Этот метод работает в Netscape Navigator 3 и выше и в Internet Explorer 4 и
выше.
Использование объекта location
Альтернативным способом переадресации пользователя на другую страницу
является применение объекта location в JavaScript. Он имеет следующий син-
таксис:
<SCRIPT LANGUAGE55’’JavaScript’’>
location=newURL
</SCRIPT>
Здесь newURL — это новый URL, куда будет переадресован браузер. Лис-
тинг 23.4 дает пример переадресации с помощью объекта location.
Переадресация
603
Эта методика работает с Netscape Navigator! и выше и с Internet Explorer3 и
выше.
Заключение
В этой главе были рассмотрены приемы автоматической и ручной переадре-
сации. Автоматическая переадресация направляет пользователя надругой URL
или другую страницу без вмешательства пользователя, в то время как ручная
переадресация выполняется, когда пользователь что-то делает: щелкает мы-
шью на кнопке или гиперссылке.
С целью предупреждения нарушения автоматической переадресации не-
обходимо предоставлять ссылку, на которой пользователь сможет щелкнуть
мышью, чтобы перейти на другую страницу. Автоматическая переадресация
может применяться для вывода окна-вспышки или для отправки cookie бра-
узеру.
Для ручной переадресации обычно используются методы go и back объекта
history.
600
Глава 23
Возврат на предыдущую страницу
Иногда нужно предоставить пользователю ссылку для возврата на предыду-
щую страницу. Например, имеются форма для ввода и страница, которая вы-
дает сообщение об ошибке, если одно из значений, введенных пользователем,
оказывается неправильным. На странице Error можно предоставить ссылку,
щелчок мыши на которой вернет к форме ввода.
Объект history имеет два метода, которые могут использоваться для возвра-
та к предыдущей странице: метод back и метод go. Синтаксис применения
объекта history для возврата к предыдущей странице таков:
history.back()
или
history.до(-1)
Например, код в листинге 23.6 предоставляет пользователю ссылку для воз-
врата к предыдущей странице.
Листинг 23.6 Возврат к предыдущей странице
<HTML>
<HEAD>
<Т1Т1_Е>Возврат к предыдущей странице</Т1Т1_Е>
</HEAD>
<BODY>
Введенный пароль недостаточно длинный.
Пожалуйста, <А HREF=”javascript:history.Ьаск()”>вернитесь,</А>
чтобы исправить его.
</BODY>
</НТМ1>
С помощью метода go объекта history можно передать любое отрицатель-
ное число для возврата на п страниц относительно текущей страницы. Код
листинга 23.7 является примером страницы, которая предоставляет ссылку для
возврата пользователя на 3 страницы назад.
Листинг 23.7 Возврат на п страниц назад
<НТМ1_>
<HEAD>
<Т1Т1_Е>Возврат на л страниц назад</Т1Т1_Е>
C/HEAD.
<BODY>
Проверка ввода на стороне клиента
605
На уровне формы проверка выполняется сразу после того, как пользова-
тель заканчивает вводить значение элемента этой формы. Если элемент являет-
ся полем TEXT, можно написать обработчик для события onBlur или onChange
этого элемента. Событие onBlur возникает, если фокус перемещается с элемен-
та TEXT надругой объект, как в случае, когда пользователь щелкает мышью
на другом элементе. Например, следующая строка кода проверяет значение
элемента TEXT ProductQuantity каждый раз, когда его значение изменяется:
<INPUT TYPE=TEXT Name=ProductQuantity
onChange='isNumeric(document.forms[0].ProductQuantity.value)'>
Здесь isNumeric является пользовательской функцией, которая получает в
качестве параметра строку и проверяет, что строковый параметр является допу-
стимым числовым значением.
В этой главе рассматриваются полезные функции JavaScript, которые мож-
но скопировать и вставить в любую из форм. Можно даже поместить все в
Файл .js, если эти функции будут использоваться достаточно часто.
Все способы, обсуждаемые в этой главе, можно использовать
с Netscape Navigator версии 2.0 и выше и с Internet Explorer
версии 3.0 и старше.
Функция isEmpty
Функция isEmpty проверяет, является ли переданный ей строковый параметр
null или строкой нулевой длины. Она возвращает true, если строковый пара-
метр содержит null или пустую строку; иначе она возвращает false. Функция
isEmpty показана в листинге 24.1.
Листинг 24.1 Функция isEmpty
<SCRIPT LANGUAGE="JavaScript">
function isEmpty(str) {
if (str==null || str==”")
return true;
return false;
}
</SCRIPT>
Наиболее распространенным приложением этой функции является про-
верка того, что пользователь не оставил элемент TEXT пустым. Листинг 24.2
демонстрирует применение isEmpty. Код предназначен для формы с двумя
602
Глава 23
Л истин г 23.9 Выбор нового места назначения
<НТМ1_>
<HEAD>
<Т1Т1Е>Перемещение в новый мир</Т1Т1_Е>
</HEAD>
<BODY>
Пожалуйста, выберите один из региональных офисов
<BR>
<FORM>
<SELECT onChange="location=this.options[this.selectedlndex].value">
<0PTI0N>Select a destination</OPTION>
COPTION VALUE="asia.html">Asia</OPTION>
COPTION VALUE-'europe.html">Europec/OPTION>
COPTION VALUE="northAmerica.html">North Americac/OPTION>
cOPTION VALUE="australasia.html">Australasia</OPTION>
c/SELECT>
c/FORM>
c/B00Y>
C/HTML>
Код листинга 23.9 использует событие onChange для изменения значения
объекта location. Синтаксис следующий:
location = "newURL"
Здесь newURL является значением следующей формы:
this.options[this.selectedlndex].value
Ключевое слово this указывает на текущий объект, который является эле-
ментом SELECT. A options — это массив вариантов выбора. В листинге 23.9
options имеет четыре элемента: asia.html, europe.html, northAmerica.html и
australasia.html. Чтобы выбрать значение, необходимо передать индекс в скоб-
ках [...]• Таким образом, следующий код вернет первый элемент массива options:
this.options[0].value
В листинге 23.9 будет возвращен asia.html.
Наконец, this.selectedlndex возвращает значение индекса выбранного
пользователем варианта. Поэтому, если пользователь щелкает на Australasia в
элементе SELECT, то this.options[this.selectedlndex].value будет транслировано
в australasia.html.
Проверка ввода на стороне клиента
607
которые должны быть удалены на сервере. Код листинга 24.3 предоставляет
функцию, которая обрезает ведущие и завершающие пробелы.
Листинг 24.3 Функция trim
<SCRIPT LANGUAGE="JavaScript">
function trim(str) {
while (str.charAt(str.length - 1)==" ")
str = str.substring(O, str.length - 1);
while (str.charAt(O)==" ")
str = str.substring(1, str.length);
return str;
>
</SCRIPT>
Функция trim получает один параметр — str, являющийся строкой, которая
должна быть усечена. Функция состоит из двух циклов while. Первый цикл
-vhile последовательно проверяет последние символы строки и удаляет после-
дний символ, если он является пробелом:
while (str.charAt(str.length - 1)==" ")
str = str.substrings, str.length - 1);
Второй цикл while проверяет первый символ строки, удаляет его, если он
является пробелом, и сдвигает остаток строки на один символ влево:
while (str.charAt(O)==” ")
str = str.substring(1, str.length);
Функция возвращает усеченную строку:
return str;
Может возникнуть вопрос: нужно ли проверять, что функции не был пере-
дан null. Нет, не нужно, если для проверки значения элемента формы исполь-
зуется функция trim. Значение элемента формы никогда не будет null, даже
если пользователь вообще ничего не вводит. Если пользователь не трогает эле-
мент TEXT, то он имеет значение пустой строки.
Если вы собираетесь передавать в функцию trim переменную, то необходи-
мо модифицировать ее, чтобы выполнить проверку на null. Иначе функция
будет генерировать ошибку и останавливать выполнение JavaScript. Код лис-
тинга 24.4 является модифицированной функцией trim, которая может безо-
пг сно обрабатывать строковые переменные. Код листинга 24.3 безопасен толь-
ко для обработки элементов формы.
24
Проверка ввода
на стороне клиента
Li ели применяется проверка на клиентской стороне, то значения эле-
ментов формы являются действительными перед отправкой формы. Для сер-
вера это означает сокращение нагрузки, так как он не должен возвращать
форму пользователю для исправления данных. Пользователи получают ответ
быстрее, поскольку мгновенно выдается предупреждение, если запись фор-
мы неправильна.
При проверке на клиентской стороне помните о том, что некоторые пользо-
ватели отключают поддержку JavaScript, а некоторые способны обойти про-
верку на клиентской стороне. Из-за этих проблем приходится выполнять сер-
верную проверку ввода. Однако использование проверки на клиентской
стороне делает всю систему более масштабируемой. В большинстве случаев на
сервере выполняется последняя проверка допустимости ввода.
На клиентской стороне можно организовать два типа проверки ввода: на
уровне формы и на уровне элемента поля или формы. При проверке на уровне
формы проверка каждого элемента формы выполняется перед ее отправкой.
Это делается с помощью обработчика событий для события onSubmit формы:
<FORM METHOD=POST ACTION=buy.jsp onSubmit='validate()’>
Здесь validate() является функцией JavaScript, которая выполняет проверку
ввода.
Проверка ввода на стороне клиента
609
Первый цикл while проверяет, что строка str не является пустой. Затем со-
здается строка «\n\r\t» и используется функция indexOf для проверки, не
соответствует ли последний символ str какому-либо из символов строки
«\n\r\t». Если да, то indexOf возвращает позицию соответствующего символа в
«\n\r\t»; иначе возвращается -1. Отметим, что в JavaScript \п представляет сим-
вол новой строки, \г — символ возврата каретки, a \t — символ табуляции.
Второй цикл while делает то же самое применительно к первому символу
в str.
Функция isPositivelnteger
Функция isPositivelnteger возвращает true, только если все символы, которые
составляют передаваемый ей строковый аргумент, являются числовыми, т. е.
от 0 до 9 включительно. Функция просматривает в цикле строковый аргумент
от первого символа до последнего. Она сравнивает каждый символ с шабло-
ном и останавливается и возвращает false, если находит символ, который не
соответствует никакому символу шаблона.
Функция isPositivelnteger представлена в листинге 24.6. Если функция ис-
пользуется в форме, необходимо обрезать (trim) проверяемую строку, прежде
чем передать ее в функцию isPositivelnteger.
Л исти нг 24.6 Функция isPositivelnteger
<8CRIPT LANGUAGE=”JavaScript”>
function isPositivelnteger(str) {
var pattern = ”0123456789”
var i = 0;
do {
var pos = 0;
for (var j=0; j<pattern.length; j++)
if (str.charAt(i)==pattern.charAt(j)) {
pos = 1;
break;
}
} while (pos==1 && i<str.length)
if (pos==0)
return false;
return true;
}
</SCRIPT>
606
Глава 24
элементами ввода: UserName и Password. Когда пользователь отправляет фор-
му, функция validate использует isEmpty для проверки двух элементов. Отправка
формы будет отменена, если любое из полей окажется пустым.
Листинг 24.2 Использование функции isEmpty
<HTML>
<HEAD>
<TITLE>Using the isEmpty Function</TITLE>
cSCRIPT LANGUAGE="JavaScript">
function isEmpty(str) {
if (str==null || str=="")'
. return true;
return false;
}
function validate(userName, password) {
if (isEmpty(userName)) {
alert('User name must have a value');
return false;
}
if (isEmpty(password)) {
alert('Password must have a value');
return false;
}
return true;
}
</SCRIPT>
</HEAD>
<B0DY>
CFORM ACTION=Login.jsp METHOD=POST
OnSubmit="return validate(this.UserName.value,
this.Password.value);">
User Name; CINPUT TYPE=TEXT NAME=UserName>
<BR>
Password: <INPUT TYPE=PASSWORD NAME=Password>
<BR>
CINPUT TYPE=SUBMIT VALUE="Login">
</FORM>
</BODY>
</HTML>
Функция trim
При проверке пользовательского ввода желательно убедиться в том, что пользо-
ватель случайно (или намеренно) не ввел ведущие или завершающие пробелы,
Проверка ввода на стороне клиента
611
Отметим, что можно выполнять проверку строки с помощью регулярных
выражений. Например, следующая функция проверяет, что строковый ввод
имеет формат (хх)уу, где хх и уу определяют любое количество числовых сим-
волов, а символы «(» и «)» являются необязательными. При использовании
этой функции (999)5565656 является допустимым, a (ab)8989898 нет:
function isValidPhoneNumber(str) {
var re = new RegExp(/"\(?\d*\)?\d*$/);
if (re.test(str))
return true;
else
return false;
}
Проблема с регулярными выражениями состоит втом, что браузеры Internet
Explorer и Netscape Navigator младше версии 4.0 их не распознают. Кроме того,
совместимость между двумя типами браузеров не гарантируется. Например,
эта функция возвращает true в IE 5.5 для str, равной (121)888, а в Netscape 4.7
она возвращает false.
Функция isMoney
Функция isMoney, являющаяся производной от функции isPositivelnteger, про-
веряет передаваемый ей строковый параметр. Функция возвращает true, если
строковый параметр является допустимой денежной единицей; иначе она воз-
вращает false.
Пример функции isMoney представлен в листинге 24.8.
Листинг 24.8 Функция isMoney
<SCRIPT LANGUAGE=”JavaScript">
function isMoney(str) {
var pattern = "0123456789,.”
var i = 0;
do {
var pos = 0;
for (var j=0; j<pattern.length; j++)
if (str.charAt(i)==pattern.charAt(j)) {
pos = 1;
break;
}
} while (pos==1 && i<str.length)
608
Глава 24
Листинг 24.4 Версия функции trim, безопасно обрабатывающая
строковые переменные
<SCRIPT LANGUAGE="JavaScript">
function trim(str) {
if (str!=null) {
while (str.charAt(str.length - 1)==" ")
str = str.substring^, str.length - 1);
while (str.charAt(O)==" ")
str = str.substring(1, str.length);
}
return str;
}
</SCRIPT>
Отметим, что функция trim в листинге 24.4 сначала проверяет параметр на
null. Функция trim в листинге 24.3 этого не делает, так как элемент формы
всегда не null.
Функция trimAII
Функция trim работает с элементом TEXT формы, но она не работает с эле-
ментами, которые могут получать символы возврата каретки, например с
TEXTAREA. Функция trimAII (см. листинг 24.5) расширяет функцию trim и
удаляет любые ведущие и завершающие пробелы, символ возврата каретки,
символ новой строки и символ табуляции.
Листинг 24.5 Функция trimAII
function trimAll(str) {
if (str!=null) {
while (str.length > 0 &&
"\n\r\t ".indexOf(str.charAt(str.length - 1)) != -1)
str = str.substring^, str.length - 1);
while (str.length > 0 &&
"\n\r\t ".indexOf(str.charAt(O)) != -1)
str = str.substring(1, str.length);
}
return str;
}
Проверка ввода на стороне клиента
613
Функции isUSDate и isOZDate
Другим важным типом данных, заслуживающим внимания, является дата.
Различные форматы данных всегда усложняют код и на клиентской, и на
серверной стороне. Если пользователь должен вводить дату на клиентской
стороне, то хорошим решением будет наличие трех полей SELECT с до-
пустимыми вариантами: одно для дня, одно для месяца и одно для года.
Таким образом, введенная дата всегда будет допустимой, так как пользова-
тель не сможет ввести никакого значения, отличного от заданных в полях
SELECT.
Если проект не допускает применения этой методики и предлагается толь-
ко одно текстовое поле для дня, месяца и года, то помогут функции isUSDate
и isOZDate. OZ означает Австралию, одну из стран, где дата записывается как
dd/mm/yyyy. Обе функции предполагают, что допустимая дата имеет длину 10
символов и содержит два символа крсой черты в качестве разделителей. В ре-
зультате, если день или месяц даты будет иметь только один символ, то он
должен дополняться нулем. Например,/» мдя 2002 г. должно записываться как
05/05/2002.
Различие между двумя функциями заключается в порядке записи дня и
месяца. Функция isUSDate считает допустимой дату в формате mm/dd/yyyy, а
функция isOZDate — в формате dd/mm/yyyy.
Функция для проверки допустимости даты усложняется тем, что обычный
год имеет 365 дней, а високосный — 366 дней. Напомним, что високосный год
удовлетворяет следующим математическим критериям:
• Он без остатка делится на 400.
• Если он не делится без остатка на 400, то он делится без остатка на 4,
но не делится без остатка на 100.
Так, 2000 год является високосным, потому что делится без остатка на 400,
но 1900 не является високосным годом, так он делится на 100. Високосными
являются 1980,1984 и 1988 годы, потому что они делятся на 4, но не делятся на
100. Листинг 24.10 представляет функцию isUSDate, а листинг 24.11 — функцию
isOZDate.
Листинг 24.10 Функция isUSDate
<SCRIPT LANGUAGE="JavaScript">
function isUSDate(str) {
if (str.length! =10 || str.charAt(2)!="/" II str.charAt(5)!="/" II
! isPositive!nteger(str. substrings, 2) +
610
Глава 24
Функцию isPositivelnteger можно использовать для проверки ZIP-кода.
Нетрудно изменить isPositivelnteger для создания аналогичных функций,
для этого достаточно изменить шаблон. Примерами могут быть функции
isValidPhoneNumber и isMoney (см. ниже).
Функция isValidPhoneNumber
Функция isValidPhoneNumberявляется производной отфункции isPositivelnteger
и работает аналогичным образом. Единственным различием является значе-
ние строки шаблона (см. листинг 24.7).
Листинг 24.7 Функция isValidPhoneNumber
<SCRIPT LANGUAGES JavaScript'^
function isValidPhoneNumber(str) {
var pattern = "0123456789( )-”
var i = 0;
do {
var pos = 0;
for (var j=0; j<pattern.length; j++)
if (str.charAt(i)==pattern.charAt(j)) {
pos = 1;
break;
}
} while (pos==1 && i<str.length)
if (pos==0)
return false;
return true;
}
</SCRIPT>
("Примечание^ Отметим, что функция isValidPhoneNumber проверяет только,
что строка состоит из допустимых символов. Она не
проверяет допустимость самого телефонного номера.
Функция isValidPhoneNumber анализирует строковый ввод только на осно-
ве шаблона. Она не проверяет, например, что символ «(» появляется перед «)».
Допустимость телефонных номеров зависит прежде всего от страны. Кроме
того, проверка допустимости телефонного номера требует применения неко-
торых правил. Чем более сложными являются правила, тем труднее создать
функцию проверки. Приведенная здесь функция isValidPhoneNumber выпол-
няет лишь базовую задачу проверки. Эту функцию следует расширить, исходя
из существующих потребностей.
Проверка ввода на стороне клиента
615
Функция isUSDate сначала проверяет, что переменная str имеет длину 10
символов и что третий и пятый символы являются символами косой черты (/).
Функция также проверяет, что остальные символы являются целыми от Одо 9.
if (st г.length!=10 || str.charAt(2)!=’7" || st г.charAt(5)! = '/” ||
!isPositivelnteger(st г.subst ring(0,2) +
str.substrings,5) + str.substrings. Ю)))
Отметим, что функция isUSDate применяет функцию isPositivelnteger (см.
листинг 24.6) для проверки, что все символы, кроме третьего и пятого, являются
числами. При использовании функции isUSDate необходимо скопировать
функцию isPositivelnteger.
Затем функция извлекает из str компоненты день, месяц и год и сохраняет
их в переменных d, m и у соответственно:
var d = str.substrings» 5) - 0;
var m = str.substrings. 2) - 0;
var у = str.substring^, 10) - 0;
Из результата операции substring вычитается 0, что заставляет преобразо-
вать строку в число. Можно также использовать функцию parselnt для преоб-
разования строки в целое число.
Затем проверяется, что значение месяца не превышает 12:
if (m>12) return false;
Число дней в январе, марте, мае, июле, августе, октябре и декабре равно 31,
поэтому максимальным значением d является 31:
if (т==1 ।। m==3 || m==5 || m==7 || m==8 || m=10 || m=12)
var dmax = 31;
Однако в апреле, июне, сентябре и ноябре 30 дней:
if (m==4 || m==6 || m==9 || m==11) dmax = 30;
Месяц февраль является самым трудным. В високосный год число дней
равно 29, а в не високосный — 28:
if ((у%400==0) || (у%4==0 && у%100!=0)) dmax = 29;
else dmax = 28;
Функция isOZDate работает аналогично, за исключением того, что день и
месяц переставлены.
612
Глава 24
Листинг 24.8 Продолжение
if (pos==0)
return false;
// Теперь проверим, что десятичная точка, если присутствует,
// появляется только однажды и в позиции (длина строки-3),
// так как допустимым форматом является только ххх.уу.
// Следующий оператор возвращает также
// false, если имеется больше одной десятичной точки.
pos = str.indexOf(".”);
if (pos!=-1 && pos!=str.length-3)
return false
// Теперь проверим, что если есть запятая,
// то формат должен быть ххх,ххх,ххх,..., ххх
if (pos==-1)
pos = str.length;
while (str.lastlndexOfC, ”, pos-1) != -1) {
if (str.lastlndexOfC”,”, pos-1) != pos-4)
return false;
else
pos -= 4;
}
return true;
}
</SCRIPT>
При проверке строки денежной единицы необходимо помнить, что даже
если в строке может присутствовать запятая, ее необходимо удалить при пере-
даче значения, иначе ее придется удалять на сервере. Функция removeComma
(см. листинг 24.9) выполняет эту работу.
Л истинг 24.9 Функция removeComma
<SCRIPT LANGUAGE="JavaScript">
function removeComma(str) {
var result = ””;
for (var i=0; i<str.length; i++)
if (str.charAt(i)!=”,")
result += str.charAt(i);
return result;
}
Функция removeComma проверяет всю строку на наличие запятой и фор-
мирует результат. Переменная result не содержит запятых.
Проверка ввода на стороне клиента
617
Листинг 24.14 Логическая ошибка, возникающая в том случае, если не
учитываются типы данных
<НТМ1_>
<HEAD>
<TITLE>A logic error</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function getTotalCostO {
var freight = 2;
var totalCost = freight + document.forms[0].price.value;
alert(totalCost); //результат: 2300
}
</SCRIPT>
</HEAD>
<BODY>
<FORM>
<INPUT TYPE=HIDDEN NAME=price VALUE="300">
<INPUT TYPE=BUTTON VALUE=”Get Total Cost"
onClick="javascript:getTotalCost()">
</FORM>
</B0DY>
</HTML>
Вы можете предположить, что результатом функции getTotalCost будет 302
(300 + 2); однако получается 2300. Что происходит? Все, что вводится в эле-
мент TEXT, считается строкой, даже если все введенные символы являются
цифрами от Одо 9. Когда выполняется операция сложения переменной friegth
со значением 2, выполняется конкатенация строк, а не математическая опе-
рация сложения.
Чтобы исправить это, необходимо явно или неявно преобразовать строку в
числовой тип.
Большинство учебников по JavaScript учат разработчиков использовать
функции parselnt и parseRoat. Различие между целыми числами и числами с
плавающей точкой в JavaScript в том, что целые числа никогда не содержат
десятичной точки или цифр справа от десятичной точки. Числа же с плаваю-
щей точкой могут иметь дробное значение справа от десятичной точки. Одна-
ко математические операции JavaScript не различают целые и числа с плаваю-
щей точкой.
Рассмотрим результат функции parselnt:
parselnt("98"); // результат = 98
parselnt("98.87"); // результат = 98
614
Глава 24
Л истин г 24.10 Продолжение
str.substrings,5) + str.substrings, 10)))
return false;
var d = str. substrings, 5) - 0;
var m = str.substrings,2) - 0;
var у = str.substrings, Ю) - 0;
if (d==0 || m==0 || y==0)
return false;
if (m>12) return false;
if (m==1 || m==3 || m==5 || m==7 || m==8 || m==10 || m==12)
var dmax = 31;
else
if (m==4 || m==6 || m==9 || m==11) dmax = 30;
else
if ((y%400==0) || (y%4==0 && y%100!=0)) dmax = 29;
else dmax = 28;
if (d>dmax) return false;
return true;
}
</SCRIPT>
Листинг 24.11 Функция isOZDate
<SCRIPT LANGUAGE^’JavaScript’’>
function isOZDate(str) {
if (str.length! =10 || str.charAt(2)l =”/” I I str.charAt(5)! =”/” II
! isPositiveInteger(str. substrings, 2) +
str.substrings,5) + str.substrings, Ю)))
return false;
var d = str.substrings, 2) - 0;
var m = str. substrings, 5) - 0;
var у = str.substrings, Ю) - 0;
if (d==0 || m==0 || y==0)
return false;
if (m>12) return false;
if (m==i ।। m==3 ।। m==5 || m==7 || m==8 || m==10 || m==12)
var dmax = 31;
else
if (m==4 || m==6 || m==9 || m==11) dmax = 30;
else
if ((y%400==0) || (y%4==0 && y%100!=0)) dmax = 29;
else dmax = 28;
if (d>dmax) return false;
return true;
}
</SCRIPT>
Проверка ввода на стороне клиента
619
function toString(n) {
return + n;
}
/* примечание:
+ 98; //результат = "98"
("" + 98).length; // результат = 2
*/
</SCRIPT>
Использование функций проверки
Рассмотрим пример использования функций проверки в форме. Листинг 24.17
является страницей HTML с формой, содержащей три элемента ввода:
Company, ProductID и ProductQuantity. Эта форма служит для заказа продук-
тов. Поля Company, ProductID и ProductQuantity не должны оставаться пусты-
ми. Кроме того, ProductQuantity может содержать только число. Отметим, что
страница HTML включает в себя раздел <SCRIPT> с тремя функциями: trim,
isPositiveInteger и validateForm. Функция validateForm является обработчиком
события onSubmit формы. Она вызывается перед отправкой формы и возвраща-
ет true, только если все три поля содержат действительные значения. Если
функция validateForm возвращает false, форма не отправляется. Если один из
трех элементов формы имеет неправильное значение, создается предупреж-
дающее окно и фокус перемещается в поле, значение которого неверно.
Л исти н г 24.17 Пример проверки ввода на клиентской стороне
<НТМ1_>
<HEAD>
<TITLE>Form Validation</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function trim(str) {
while (str.charAt(str.length - 1)==” ’’)
str = str.substring(O, str.length - 1);
while (str.charAt(O)==” ")
str = str.substring^, str.length);
return str;
}
function isPositivelnteger(str) {
var pattern = "0123456789"
616
Глава 24
Преобразование форматов даты
Если клиентская и серверная сторона используют различные форматы дан-
ных, то необходимо преобразовывать даты при отправке формы, чтобы избе-
жать обработки на сервере. Например, если нужно опубликовать web-сайт в
Великобритании, но данные будут поступать на сервер в США, то следует про-
верять данные с помощью функции isOZDate и преобразовывать их в формат
США при отправке формы. Однако если неизвестно, где находится клиент,
используйте поля выбора, чтобы дата имела определенный формат, т. е. либо
dd/rnm/yyyy, либо mm/dd/yyyy.
Рассмотрим две функции. Функция convertToUSDate преобразует допус-
тимую дату в формате dd/mm/yyyy в формат mm/dd/yyyy. Функция
convertToOZDate делает обратное.
Функция convertToUSdate представлена в листинге 24.12, а функция
convertToOZdate — в листинге 24.13.
Л исти н г 24.12 Функция convertToUSDate
<SCRIPT LANGUAGE=”JavaScript”>
function convertTollSDate(str) {
// Может быть, следует сначала проверить, что это isOZDate?
return (str. substrings, 5) + ’7” + str. substrings, 2) + ”/” +
str.substrings, 10));
}
</SCRIPT>
Л исти н г 24.13 Функция convertToOZDate
<SCRIPT LANGUAGE=”JavaScript”>
function convertToOZDate(str) {
// Может быть, следует сначала проверить, что это isUSDate?
return (st г. subst ring (3, 5) + ”/” + str. substrings, 2) + "/" +
str.substrings, 10));
}
</SCRIPT>
Преобразование типов данных:
строка в число
Чтобы понять важность преобразования типов данных в JavaScript, рассмот-
рим код листинга 24.14.
Проверка ввода на стороне клиента
621
<TABLE>
<TR>
<TD>Company:c/TD>
CTDXINPUT TYPE=TEXT NAME=CompanyX/TD>
<AR>
<TR>
<TD>Product ID:</TD>
CTDXINPUT TYPE=TEXT NAME=ProductID>c/TD>
<AR>
<TR>
<TD>Product Quantity:</TD>
CTDXINPUT TYPE=TEXT NAME=ProductQuantityX/TD>
C/TR>
CTR>
CTD C0LSPAN=2 ALIGN=RIGHTXINPUT TYPE=SUBMITX/TD>
C/TR>
C/TABLE>
C/F0RM>
C/BODY>
C/HTML>
На рис. 24.1 показано предупреждающее окно, которое появится в случае
ввода неверного значения.
Рис. 24.1. Проверка допустимости пользовательского ввода
на клиентской стороне
Заключение
Проверка ввода на клиентской стороне сокращает серверную нагрузку и ус-
коряет выдачу ответа клиентам. В этой главе приведено несколько полезных
приемов проверки допустимости клиентского ввода, включая проверку строк,
чисел и дат. Рассмотрены также методы преобразования типов данных.
618
Глава 24
Функция parseFloat возвращает целое, если это возможно; иначе она воз-
вращает число с плавающей точкой:
parseFloat(”98”); // результат = 98
parseFloat("98.87”); // результат = 98.87
Для получения ожидаемого результата поместите строку в скобки и добавьте
-О (минус 0), чтобы заставить строку превратиться в число.
Перепишем функциюgetTotalCost, применив преобразование строки в чис-
ло (см. листинг 24.15).
Л и сти н г 24.15 Исправленная функция getTotalCost
<SCRIPT LANGUAGE=”JavaScript">
function getTotalCost() {
var freight = 2;
var totalCost = freight + (document.forms[0].price.value - 0);
alert(totalCost); //результат: 302
}
</SCRIPT>
Отметим, что использование знака минус (-) для преобразования строки в
число может служить альтернативой функции parselnt или parseFloat. Однако
этот подход делает код менее читаемым.
Преобразование типов данных:
число в строку
Иногда необходимо преобразовать число в строку. Например, после выполне-
ния математического вычисления желательно передать результат в функцию
format, которая выполняет различные операции для аргументов number и string.
Если нужно выполнить строковую операцию, то требуется преобразовать число
в строку перед передачей ее в функцию format. Это делается конкатенацией
числа с пустой строкой. Код листинга 24.16 предоставляет пример преобразо-
вания числа в строку.
Листинг 24.16 Преобразование числа в строку
<SCRIPT LANGUAGE=”JavaScnpt”>
Работа с cookie на клиентской стороне
623
Создание cookie с помощью тега <МЕТА>
Можно создавать и читать cookie как на клиентской, так и на серверной стороне.
На клиентской стороне используется JavaScript. При создании cookie на сервер-
ной стороне потребуется Java в сервлетах или страницах JAS для записи cookie в
ответ HTTP. Однако cookie можно также создавать с помощью тега <МЕТАХ
Создание cookie с помощью тега <МЕТА> выполняется очень просто. Одним
из преимуществ тега <МЕТА> является возможность использования страницы
HTML для создания cookie. Это отличается от метода, описанного в гла-
ве 5, где для формирования cookie требовалось создать экземпляр класса
javax.servlet.http.Cookie.
Другое преимущество тега <МЕТА> в том, что он работает даже в том слу-
чае, если JavaScript отключен в браузере клиента. Недостатком является то,
что старые браузеры не понимают теги <МЕТА>. Однако некоторые web-cep-
веры транслируют тег <МЕТА> в заголовок HTTP, чтобы браузеру не нужно
было делать это самому. Можно сказать, что тег <МЕТА> является лучшим
способом создания cookie.
В листингах 25.1 и 25.2 приводятся примеры использования тега <МЕТА>
для создания cookie в web-браузере клиента. Код листинга 25.1 применяет дату
окончания действия, чтобы cookie оставался после закрытия браузера пользо-
вателем, при этом cookie сохраняется надиске (додаты окончания действия).
Код листинга 25.2 создает cookie, который действителен только в текущем
сеансе и удаляется после закрытия браузера пользователем.
Л истинг 25.1 Создание cookie, который действителен до определенной даты
<HTML>
<HEAD>
<TITLE>Creating a cookie that is valid until a certain date</TITLE>
<META HTTP-EQUIV="Set-Cookie" C0NTENT="userId=678;expires=Wednesday,
ч> 26-Dec-01 16:00:00 GMT; path=/">
</HEAD>
<BODY>
Unless you set your browser to not accept cookies, a cookie called
userid with a value of 678 has been created for you.
</B0DY>
</HTML>
Л исти н г 25.2 Создание cookie, который действителен до закрытия
браузера
<HTML>
<HEAD>
620
Глава 24
Листинг 24.17 Продолжение
var i = 0;
do {
var pos = 0;
for (var j=0; j<pattern.length; j++)
if (str.charAt(i)==pattern.charAt(j)) {
pos = 1;
break;
}
1++ I
} while (pos==1 && i<str.length)
if (pos==0)
return false;
return true;
}
function validateForm(theForm) {
if (trim(theForm.Company.value)==’') {
alert(’Please enter a value in the "Company’’ box’);
theForm.Company.focus();
return false;
}
if (trim(theForm.ProductID.value)==’') {
alert('Please enter a value in the ’’ProductID" box');
theForm.ProductID.focus();
return false;
}
if (trim(theForm.ProductQuantity.value)==’’) {
alert('Please enter a value in the "Product Quantity" box’);
theForm.ProductQuantity.focus();
return false;
}
if (’isPositiveInteger(trim(theForm.ProductQuantity.value))) {
alert(’Please enter a number only in the "Product Quantity” box’);
theForm.ProductQuantity.focus();
return false;
}
return true;
}
</SCRIPT>
</HEAD>
<B0DY>
<FORM METH0D=P0ST ACTION=buy.jsp onSubmit=’return validateForm(this)’>
Работа с cookie на клиентской стороне
625
Создание cookie с помощью функции
setCookie
Предыдущий способ создает cookie, когда пользователь загружает страницу.
Однако иногда желательно интерактивно создавать cookie на клиентской сто-
роне. Например, необходимо создать cookie, если пользователь решил что-то
купить в сетевом магазине, обслуживаемом web-приложением. Функция
setCookie (см. листинг 25.4) подходит для создания cookie в браузере. Она име-
ет шесть аргументов, но должны присутствовать только аргументы name и
value. В случае отсутствия какого-либо необязательного аргумента ему при-
сваивается значение по умолчанию.
("Примечание^ Функции setCookie (листинг 25.4) и getCookie (листинг 25.8)
~ можно найти в Интернете. Автор этих функций неизвестен.
Листинг 25.4 Функция setCookie
<SCRIPT LANGUAGE=”JavaScript”>
function setCookie(name, value, expires, path, domain, secure) {
document.cookie = name + ”=" + escape(value) +
((expires) ? expires=" + expires.toGMTStringO : ’”’) +
((path) ? path=” + path : ’”’) +
((domain) ? domain=’’ + domain : ””) +
((secure) ? secure” : ’”’);
}
</SCRIPT>
Код листинга 25.5 является файлом HTML, который применяет функцию
setCookie для создания cookie по желанию пользователя. Страница HTML имеет
форму с кнопкой и элементом TEXT с именем UserID. Когда пользователь щел-
кает мышью на кнопке, создается cookie с именем UserID. Значением cookie
является значение, введенное в ТЕХТ-элементе UserID.
Листинг 25.5 Использование функции setCookie
<HTML>
<HEAD>
<TITLE>Using the setCookie function</TITLE>
<SCRIPT LANGUAGE=”JavaScript">
function setCookie(name, value, expires, path, domain, secure) {
document.cookie = name + ”=” + escape(value) +
((expires) ? expires=” + expires.toGMTStringO : ”’’) +
((path) ? path=” + path : ”") +
((domain) ? domain=” + domain : ””) +
25
Работа с cookie
на клиентской
стороне
главе 5 обсуждалось использование cookie в управлении сеансом. В этой
главе cookie рассматриваются более подробно, в частности обсуждается обра-
ботка cookie на клиентской стороне (например, в браузере).
Повторим, что cookie — это небольшой фрагмент информации, который
сохраняется web-браузером на клиентской машине или в оперативной памяти
компьютера. Эта информация посылается на сервер каждый раз, когда клиент
запрашивает страницу. Cookie обычно используются для хранения состояния или
информации протокола HTTP, не поддерживающего состояния.
Однако некоторые пользователи предпочитают не принимать cookie, так
как не хотят, чтобы сервер что-то записывал на их компьютер. Пользователи
могут сконфигурировать свои web-браузеры, чтобы не получать cookie. Основные
браузеры работают с cookie по-разному. В Netscape Navigator пользователь
может разрешить прием только тех cookie, которые посылаются на исходный
сервер. Internet Explorer различает cookie, которые сохраняются только в
памяти (cookie сеанса), и cookie, которые остаются после закрытия браузера
(хранятся в долговременной памяти компьютера). Что это означает для web-
разработчика? То, что критически важно проверить отношение пользователь-
ского браузера к приему cookie, прежде чем разрешить пользователю войти в
область web-сайта, где активно применяются cookie. В этой глаяе даются
рекомендации по работе с cookie, включая вопросы создания, удаления и ре-
дактирования cookie, — как на серверной, так и на клиентской стороне.
Работа с cookie на клиентской стороне
627
Листинг 25.7 Пример, создающий cookie с датой окончания действия
<HTML>
<HEAD>
<TITLE>Using the setCookie function</TITLE>
<SCRIPT LANGUAGE=”JavaScript">
function setCookie(name, value, expires, path, domain, secure) {
document.cookie = name + + escape(value) +
((expires) ? expires=" + expires. toGMTStringO : "") +
((path) ? path=” + path : ’”’) +
((domain) ? domain=” + domain : ’”’) +
((secure) ? ”; secure” : ””);
}
function fixDate(date) {
var base = new Date(O);
var skew = base.getTimeO;
if (skew > 0) date.setTime(date.getTime() - skew);
}
var expiryDate = new Date();
fixDate(expiryDate);
expiryDate.setTime(expiryDate.getTime() + 365 * 24 * 60 * 60 * 1000);
setCookie(”authorizationLevel”, 2, expiryDate);
</SCRIPT>
</HEAD>
<B0DY>
A cookie which is valid for a year has been created for this page.
</BODY>
</HTML>
Чтение cookie в браузере
Функция getCookie (см. листинг 25.8) имеет один аргумент: name. Этот аргу-
мент является именем cookie, значение которого нужно получить.
«
Листинг 25.8 Функция getCookie
<SCRIPT LANGUAGE=”JavaScript">
function getCookie(name) {
var cName = name +
var de = document.cookie;
if (de.length>0) {
begin = de.indexOf(cName);
if (begin ’. = -1) {
begin += cName.length;
end = de.indexOf(”;”, begin);
if (end == -1) end = de.length;
return unescape(dc.substring(begin,end));
624
Гпава 25
Листинг 25.2 Продолжение
<TITLE>Creating a cookie that is valid for this session only</TITLE>
<META HTTP-EQUIV="Set-Cookie" C0NTENT="ProductID=2x3;">
</HEA0>
<BODY>
Unless you set your browser not to accept cookies, this page creates a
cookie called ProductID with a value of "2x3". The cookie won’t be
written to your hard drive. It lives until the browser is closed.
</BODY>
</HTML>
Создание cookie с помощью document.cookie
На клиентской стороне для создания cookie применяется JavaScript. Суще-
ствует несколько способов. Простейшим из них является использование
document.cookie. Синтаксис создания cookie таков:
document.cookie = "cookieName=cookieValue
[; expires=timeInGMTString]
[; path=pathName]
[; domain=domainName]
[; secure]”
Код листинга 25.3 создает cookie с именем Quantity и значением 7, который
существует, пока открыт браузер.
Л исти н г 25.3 Создание cookie с помощью document.cookie
<НТМ1_>
<HEAD>
<TITLE>Creating a cookie with document.cookie</TITLE>
<SCRIPT LANGUAGE^’JavaScript’’>
document.cookie=”Quantity=7”;
</SCRIPT>
</HEAD>
<BODY>
This page creates a cookie on the client side.
Make sure that your browser is set to accept cookies.
</BODY>
</HTML>
Работа с cookie на клиентской стороне
629
CINPUT TYPE=BUTTON VALUE="Create Cookie"
onClick='setCookie("UserID”, document.forms[0].UserID.value)’>
<BR>
Click the Read Cookie button to display the cookie.
CINPUT TYPE=BUTTON VALUE="Read Cookie"
onClick='alert(getCookie("UserID"))'>
</F0RM>
</B0DY>
</HTML>
Удаление cookie в браузере
Cookie на самом деле не удаляется, просто в качестве даты окончания действия
задается первая секунда 1970 года (см. функцию deleteCookie в листинге 25.10).
Отметим, что функция использует функцию getCookie (листинг 25.8), поэтому
на страницу необходимо также добавить функцию getCookie.
Листинг 25.10 Удаление cookie
<SCRIPT LANGUAGE^’JavaScript">
function deleteCookie (name, path, domain) {
if (getCookie(name)) {
document.cookie = name + "=” +
((path==null) ? "" : path=" + path) +
((domaih==null) ? ”” : "; domain=" + domain) +
expires=Thu, 01-Jan-70 00:00:01 GMT";
}
}
function getCookie(name) {
var cName = name + ”=";
var de = document.cookie;
if (dc.length>0) {
begin = de.indexOf(cName);
if (begin ’= -1) {
begin += cName.length;
end = de.indexOf(";", begin);
if (end == -1) end = de.length;
return unescape(dc.substring(begin,end));
} }
return null;
}
</SCRIPT>
626
Гпава 25
Листинг 25.5 Продолжение
((secure) ? secure" :
}
</SCRIPT>
</HEAD>
<BODY>
Type your user id, and then click the button below.
A cookie will be created for you.
<BR>
<FORM>
User ID: CINPUT TYPE=TEXT NAME=UserID>
<BR>
<INPUT TYPE=BUTTON VALUE="Create Cookie"
onClick='setCookie("UserID", document.forms[0].UserID.value);’>
</FORM>
</BODY>
</HTML>
В листинге 25.5 cookie существует, пока пользователь не закроет браузер.
Если желательно определить дату окончания действия, чтобы cookie хранился
дольше, чем текущий сеанс браузера, то нужно передать дату в качестве аргу-
мента expires. Однако в связи с ошибкой в некоторых браузерах, необходимо
использовать fixDate для «подготовки» даты, прежде чем передавать ее в фун-
кцию setCookie. Функция fixDate представлена в листинге 25.6.
Листинг 25.6 Функция fixDate
<SCRIPT LANGUAGE="JavaScript">
// date - экземпляр объекта Date
// обрабатываем все экземпляры объекта Date с помощью этой функции для
// их подготовки
function fixDate(date) {
var base = new Date(O);
var skew = base.getTimeO;
if (skew > 0) date.setTime(date.getTime() - skew);
}
</SCRIPT>
Пример в листинге 25.7 задает cookie с именем authorization Level, со значе-
нием 2 и датой окончания действия через год после определения. Обратите
внимание, как дата исправляется перед передачей функции.
Работа с cookie на клиентской стороне
631
</HEAD>
<BODY>
Содержимое страницы
</BODY>
</HTML>
Проверка возможности получения cookie
браузером без помощи JavaScript
Недостатком приведенного метода является необходимость проверки того, что
браузер понимает JavaScript. Этот метод не будет работать, если JavaScript
отключен.
Другим способом проверки того, что браузер способен принимать cookie,
является создание cookie на одной странице и затем немедленная переадресация
пользователя на вторую страницу. На второй странице можно попробовать
прочитать cookie. Код листинга 25.12 использует тег <МЕТА> для создания
cookie с именем «test» и затем переадресует браузер на вторую страницу с име-
нем checkCookie.jsp (см. листинг 25.13).
Л исти н г 25.12 Проверка, принимает ли браузер cookie, с помощью
переадресации
<HTML>
<HEAD>
<МЕТА HTTP-EQUIV=’’Set-Cookie" CONTENT="test=ok; ">
<МЕТА HTTP-EQUIV=’’Refresh” CONTENT=”O;URL=checkCookie.jsp">
</HEAD>
</HTML>
На второй странице, реализованной в этом примере с помощью ASP, про-
изводится попытка прочитать тот же cookie (см. листинг 25.13).
Листинг 25.13 Чтение cookie с целью проверки, принимает ли браузер cookie
<%
If Request.Cookies("test") о Then
Response.Write ’’Cookies accepted.”
Else
Response.Write "Cookies not accepted."
End If
%>
628
Гпава 25
Листинг 25.8 Продолжение
}
}
return null;
}
</SCRIPT>
В листинге 25.9 приведен пример. Страница HTML содержит кнопки, ко-
торые пользователь может нажимать для записи и чтения cookie.
Листинг 25.9 Запись и чтение cookie
<HTML>
<HEAD>
<TITLE>Writing and Reading Cookies</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function setCookie(name, value, expires, path, domain, secure) {
document.cookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires.toGMTStringO : "") +
((path) ? path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? secure" : "");
}
function getCookie(name) {
var cName = name + "=";
var de = document.cookie;
if (dc.length>0) {
begin = de. indexOf(cName);
if (begin != -1) {
begin += cName.length;
end = dc.indexOf(";", begin);
if (end == -1) end = de.length;
return unescape(dc.substring(begin,end));
)
}
return null;
)
</SCRIPT>
</HEAD>
<BODY>
Type in your user id, and then click the Create Cookie button.
A cookie will be created for you.
<BR>
<FORM>
User ID: <INPUT TYPE=TEXT NAME=UserID>
<BR>
Работа
с деревьями
объектов
этой главе изучается работа с объектами в иерархии с помощью JavaScript.
В качестве примеров можно привести различные приложения. Одно из них —
дерево папок, подобное тому, что используется в Windows Explorer. В таком при-
ложении можно перемещаться в системе каталогов файлов и открывать каталог,
щелкая мышью на значке папки, как мы видели в приложении по управлению
документами в главе 20. Другими примерами являются сетевая справочная сис-
тема на основе XML и оглавление сетевой книги (см. главу 19).
При работе с объектами в JavaScript массив объектов является, по сути, един-
ственной возможностью. В этой главе рассматривается массив объектов в
JavaScript. Описываются все операции, которые требуются для работы с деревом
объектов: создание объекта, добавление объекта-потомка к корню, поиск объек-
та в дереве объектов, добавление объекта в другой объект и удаление объекта.
Глава завершается примером дерева папок. В последующих главах будут
представлены другие приложения этого дерева.
Объект Array
Объект Array является единственной структурой данных в JavaScript, доступ-
ной для хранения и манипуляции упорядоченной совокупностью данных. Эта
структура работает во всех браузерах, использующих языки сценариев. В от-
личие от массивов Java, Массив JavaScript может содержать в качестве своих
630
Глава 25
Функция deleteCookie (листинг 25.10) использует функцию getCookie для
проверки того, что удаляемый cookie был создан. Если функция getCookie воз-
вращает false, то cookie, который требуется удалить, не существует. Если cookie
обнаружен, то дата окончания его действия получает значение первой секунды
января 1970 г., что приводит к немедленному прекращению его существования.
Проверка с помощью JavaScript
возможности получения cookie браузером
В этом разделе предлагается простейший способ проверки того, что браузер
при ни мает cookie. С помощью JavaScript страница создает cookie с именем «test»
и значением «ОК». Затем она сразу же пытается прочитать созданный cookie.
Если ей не удается найти cookie, значит, браузер не принимает cookie.
Листинг 25.11 представляет пример страницы HTML, которая использует
эту методику.
Л исти н г 25.11 Проверка с помощью JavaScript того, что браузер может
получать cookie
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
document.cookie=”test=OK”;
function getCookie(name) {
var cName = name +
var de = document.cookie;
if (de.length>0) {
begin = de.indexOf(cName);
if (begin != -1) {
begin += cName.length;
end = de.indexOf(";", begin);
if (end == -1) end = de.length;
return unescape(dc.substring(begin,end));
}
}
return null;
}
if (getCookie('test’)==null)
alert("Please change your browser to accept cookies.");
else
alert("Browser accepts cookies");
</SCRIPT>
Работа с деревьями объектов
635
вый и второй элементы. В данном случае первый элемент является числом, а
второй — строкой. В отличие от некоторых других языков, где массив должен
содержать элементы одного типа данных, в JavaScript разрешается заполнять
массив элементами различных типов.
Листинг 26.2 Заполнение массива
<SCRIPT LANGUAGE="JavaScript">
// определяем переменную массива и создаем объект Array
var myArray = new ArrayO;
// заполняем первый и второй элементы
myArray[0] = 1;
туАггау[1] = "the пате";
</SCRIPT>
Можно даже использовать массив в качестве элемента другого массива, как
показано в листинге 26.3.
Листинг 26.3 Присвоение массива элементу другого массива
<SCRIPT LANGUAGE="JavaScript">
// определение переменной массива и создание объекта Array
var anArray = new ArrayO;
var anotherArray = new ArrayO;
anArray[0] = 1;
// присвоение другого массива элементу anArray
anArray[1] = anotherArray;
</SCRIPT>
Знание того, как создать и заполнить массив, является основным при рабо-
те с иерархией объектов в JavaScript.
Оператор удаления
До Navigator 4.0 JavaScript не имел оператора delete, предназначенного для
удаления элемента массива. Как будет показано ниже, использование опера-
тора delete не идеальный способ удаления элементов, потому что он не умень-
шает свойство length массива.
Листинг 26.4 представляет код, который использует оператор delete для уда-
ления третьего элемента массива. Отметим, что значение третьего элемента
массива изменяется.
632
Глава 25
Приведенный код посылает пользователю сообщение о том, что его брау-
зер принимает или не принимает cookies. При^необходимости можно расши-
рить возможности примера. Скажем, можно перенаправить пользователя на
страницу предупреждения, если cookie не принимаются.
Заключение
Cookie являются весьма полезными в различных приложениях. В этой главе
рассматривались методы обработки cookie как на серверной, так и на клиент-
ской стороне. Однако помните, что пользователь может отключить cookie в
своем браузере. Выполняйте в коде тест на прием cookie, если они использу-
ются в приложениях.
Работа с деревьями объектов
637
myArray[O] = "first element.";
myArray[1] = "second element.";
myArray[2] = "third element.";
document.write(myArray.length + "<BR>");
document.write(myArray[1] + "<BR>");
delete myArray[1];
document.write(myArray.length + "<BR>");
document.write(myArray[1] + "<BR>");
myArray[1] = "new element.";
document.write(myArray.length + "<BR>");
document.write(myArray[1] + "<BR>");
document.write(myArray.length + "<BR>");
document.write(myArray[2] + "<BR>");
</SCRIPT>
</HTML>
Если выполнить этот код в Internet Explorer 4.0 и 5.0 и в Netscape Navigator
1 0, то будет получен следующий результат.
3
second element.
3
undefined
3
new element.
3
third element.
Очевидно, что оператор delete в действительности не удаляет элемент из
памяти. Так что в ряде случаев приходится придумывать другой способ удале-
ния элемента массива.
Истинное удаление элемента массива
Чтобы действительно удалить последний элемент, можно уменьшить значе-
ние свойства length массива. Рассмотрим код листинга 26.6.
Листинг 26.6 Истинное удаление элемента массива
<HTML>
<SCRIPT LANGUAGE="JavaScript">
var myArray = new ArrayO;
634
Глава 26
элементов различные типы данных. Массив может создаваться с определением
или без определения числа элементов в нем. Если количество элементов не
задано, массив ведет себя как объект Vector в Java, где элементы могут добав-
ляться в произвольном порядке.
Объект Array имеет один недостаток: удаление элемента из массива выпол-
няется с большим трудом.
Можно также использовать конструктор new Object() для создания объек-
та, но это работает только в Navigator версии 3 и выше и в Internet Explorer
версии 4 и выше.
Создание массива
Массив создается с помощью ключевого слова new:
var myArray = new ArrayO;
Можно не беспокоиться об определении числа элементов, так как массив
JavaScript является динамическим. Разрешается добавлять элементы в любое
время после создания массива. Если по какой-то причине требуется задать
размер массива, можно использовать следующий оператор:
var myArray = new Array(20);
Отсчет элементов массива начинается с 0. Если определен массив с 20 эле-
ментами, то индекс массива будет принимать значения от 0 до 19.
Массив имеет свойство length. Оно изменяет свое значение, когда добавля-
ется элемент с индексом, большим размера массива. Например, в коде лис-
тинга 26.1 сначала определяется массив с 20 элементами, а затем в массив до-
бавляется двадцать первый элемент. Свойство length изменяется
соответствующим образом.
Л истинг 26.1 Создание массива
<SCRIPT l_ANGUAGE="JavaScript">
var myArray = new Array(20);
myArray[20] = "new element.";
// значение myArray.length равно 21
</SCRIPT>
Заполнение массива
Массив заполняется путем присвоения значений его элементам. Напри-
мер, код листинга 26.2 создает массив с именем myArray и заполняет его пер-
Работа с деревьями объектов
639
Листинг 26.8 Использование функции deleteElement
<HTML>
<SCRIPT LANGUAGE="JavaScript">
function deleteElement(arrayf n) {
// удаление n-го элемента массива
var length = array.length;
if (n >= length || n<0)
return;
for (var i=n; i<length-1;.i++)
array[i] = array[i+1];
array.length-;
}
var myArray = new ArrayO;
myArray[0] = "first element.";
myArray[1] = "second element.";
myArray[2] = "third element.";
myArray[3] = "4th";
document.write("length : " + myArray.length + "<BR>");
for (var i=0; KmyArray.length; i++)
document.write("element " + i + " : " + myArray[i] + "<BR>");
deleteElement(myArray, 1);
document.write("length : " + myArray.length + "<BR>");
for (var i=0; KmyArray.length; i++)
document.write("element " + i + " : " + myArray[i] + ”<BR>");
</SCRIPT>
</HTML>
Код создает и заполняет массив с именем myArray и выводит значение свой-
ства length. Затем в цикле просматривается объект Array для вывода значений
его элементов:
for (var i=0; j<myArray.length; i++)
document.write("element " + i + " : " + myArray[i] + "<BR>";
После этого код удаляет второй элемент myArray и снова выводит свойство
length и содержимое. Результат будет следующим:
length : 4
element 0 : first element.
636
Глава 26
Л истин г 26.4 Удаление элемента массива
<НТМ1_>
<SCRIPT LANGUAGE="JavaScript">
var myArray = new ArrayO;
myArray[0] = "first element.";
myArray[1] = "second element.";
myArray[2] = "third element.";
document.write(myArray.length + "<BR>");
document.write(myArray[2] + "<BR>");
delete myArray[2];
document.write(myArray.length + "<BR>");
document.write(myArray[2] + "<BR>");
</SCRIPT>
</HTML>
Этот код записывает свойство length и третий элемент массива myArray до
и после удаления третьего элемента с помощью оператора delete. Если выпол-
нить код в Internet Explorer 4.0 и 5.0 и в Netscape Navigator 4.0, то будет получен
следующий результат:
3
third element.
3
undefined
Свойство length не изменяется после удаления третьего
элемента.
В Netscape Navigator 3.0 будет получен следующий результат:
3
third element.
3
null
Код листинга 26.5 удаляет элемент массива и присваивает новое значение
удаленному элементу.
Листинг 26.5 Удаление элемента массива и присвоение нового значения
<HTML>
<SCRIPT LANGUAGE="JavaScript">
var myArray = new ArrayO;
Работа с деревьями объектов
641
С содержит папку с именем Program Files. Папка Program Files является объек-
том-потомком диска С. Диск С является предком папки Program Files. Папка
Program Files в свою очередь может иметь свои собственные объекты-потомки.
В иерархии, подобной системе каталогов Windows, всегда существует объект,
у которого нет предка. В системе каталогов это диск С. Во многих контекстах
он называется корнем (root). Корень играет важную роль, так как он является
точкой входа в иерархию. Каждая одиночная операция включает в себя ко-
рень. Например, если в дереве, описывающем родословную собаки, нужно
найти определенную собаку, то поиск начинается с корня.
Добавление объекта-потомка
к другому объекту
Для получения дерева объектов необходимо создать хотя бы два объекта и
отношение предок-потомок между ними. Предположим, что объект Dog имеет
name, color и нуль или больше объектов-потомков Dog. Можно использовать
массив для представления объекта Dog. Первый и второй элементы резервиру-
ются для name и color, третий и все последующие элементы используются для
объектов-потомков Dog. Поэтому объект Dog имеет, по крайней мере, два эле-
мента: имя и цвет. Объект Dog без элементов-потомков будет иметь только два
элемента. Объект Doge одним объектом-потомком содержиттри элемента: name,
color и ссылку на другой объект Dog. Если у объекта Dog два объекта-потомка,
то он имеет четвертый элемент для еще одного объекта Dog.
Важным моментом здесь является создание объекта Dog и установление
отношений между предком и объектом-потомком Dog. Нам нужна функция
для добавления объекта как объекта-потомка к другому объекту. Назовем эту
функцию append (см. листинг 26.11).
Листинг 26.11 Функция append
<8CRIPT LANGUAGE=”JavaScript">
function append(parent, child) {
parent[parent.length] = child;
}
</SCRIPT>
Функция append имеет два аргумента: объект, который будет предком, и
объект, который будет потомком в отношении. Так как массив является пол-
ностью динамическим, можно создать новый элемент для объекта-потомка.
Свойство length возвращает число элементов, но индекс элементов начинает-
ся с 0, поэтому свойство length возвращает индекс, который является следую-
щим элементом в массиве.
638
Глава 26
Листинг 26.6 Продолжение
myArray[O] = ’’first element.’’;
myArray[1] = ’’second element.’’;
myArray[2] = ’’third element.";
myArray.length-;
document.write("Array length : " + myArray.length + "<BR>");
for (var i=0; KmyArray.length; i++)
document.write("element " + i + ’’ : ” + myArray[i] + "<BR>");
document.write("element " + 3 + " : " + myArray[2] + "<BR>");
</SCRIPT>
</HTML>
Этот код создает следующий результат:
Array length : 2
element 0 : first element
element 1 : second element
element 3 : undefined
Таким образом, после уменьшения свойства length последний элемент дей-
ствительно исчезает. Листинг 26.7 представляет функцию для удаления п-го
элемента массива.
Листинг 26.7 Функция deleteElement
. - — —г
function deleteElement(array, n) {
//удаление n-го элемента массива
var length = array.length;
if (n >= length || n<0)
return;
for (var i=n; i<length-1; i++)
array[i] = array[i+1];
array.length-;
}
Предполагается, что n — числовое значение. Рассмотрим пример исполь-
зования функции deleteElement (см. листинг 26.8).
Работа с деревьями объектов
643
Для построения этого дерева необходимо сначала создать отдельных со-
бак. Затем необходимо сформировать структуру дерева, добавив собаку-по-
томка к своему объекту-предку. Листинг 26.13 представляет код, который стро-
ит родословное дерево Во.
Л истин г 26.13 Создание дерева собачьего семейства
<НТМ1>
<SCRIPT LANGUAGE="JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDogC’bo", ''brown");
var boli = createDogC’boli", "black and white");
var boy = createDogC'boy", "brown");
var bulbul = createDog("bulbul", "brown");
var boni = createDogC’boni", "black and white");
var spotty = createDog("spotty", "black and white");
var шагу = createDogC’boni", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
append(boni, mary);
</SCRIPT>
</HTML>
Теперь можно перемещаться по дереву, вводя имя каждой собаки. Для пе-
ремещения необходимо знать поколение собаки. Поколение начинается с 1.
Во является корнем и, следовательно, первым поколением в дереве. Переме-
щение по дереву означает, что вы начинаете с корня, который является един-
ственным объектом первого поколения. Если корень имеет наследников (оче-
видно, что это так — отсюда и дерево), то наследники представляют второе
поколение. В цикле просматриваются все члены второго поколения и ищутся
наследники. Наследники второго поколения являются третьим поколением.
Листинг 26.14 показывает, как перемещаться в дереве объектов с помощью
рекурсивной функции.
640
Глава 26
element 1 : second element.
element 2 : third element.
element 3 : 4th
length : 3
element 0 : first element.
element 1 : third element.
element 2 : 4th
Создание объекта
Полезным является прием, который заставляет массив вести себя, как объект.
Функция createObject (см. листинг 26.9) возвращает объект Array.
Листинг 26.9 Функция createObject для создания объекта
<SCRIPT LANGUAGE3"JavaScript">
function createObjectO {
var anArray = new ArrayO;
return anArray;
>
</SCRIPT>
Можно написать функцию, которая создает объект с предопределенными
свойствами. Например, функция createDog (см. листинг 26.10) создает объект
dog с указанными именем (name) и цветом шерсти (color).
Листинг 26.10 Функция, которая создает объект с предопределенными
свойствами
<SCRIPT LANGUAGE="JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
</SCRIPT>
Иерархия объектов
Имея функцию для создания объектов, можно получить иерархию объектов. Это
означает создание отношений предок-потомок между объектами. Рассмотрим,
например, систему каталогов операционной системы Windows надиске С. Диск
Работа с деревьями объектов
645
</HEAD>
<BODY>
<SCRIPT LANGUAGE=”JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDog(”bo”, "brown”);
var boli = createDog("boli", "black and white");
var boy = createDog(”boy", "brown");
var bulbul = createDog("bulbul", "brown");
var boni = createDogC'boni", "black and white");
var spotty = createDog("spotty", "black and white");
var mary = createDog("mary", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
append(boni, mary);
var found = false;
function search(dog, generation, name) {
if (name == dog[0]) {
found = true;
alert("name:" + dog[0] + "\ncolor:” + dog[1]);
}
else if (!found) {
generation++;
for (var j=2; j<dog.length; j++ ) // имеет потомков
if (!found)
search(dog[j], generation, name);
}
}
search(bo, 1, "bulbul");
</SCRIPT>
</B0DY>
</HTML>
642
Гпава 26
Например, код листинга 26.12 создает два объекта Dog с именами doggy и
puppy и затем формирует между ними отношение предок-потомок.
Л исти н г 26.12 Создание объекта-предка и объекта-потомка
<SCRIPT LANGUAGE="JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var doggy = createDogC’boli", "black");
var puppy = createDogC’boni", "white");
append(doggy, puppy);
</SCRIPT>
Если вас интересует отношение предок-потомок между двумя объектами,
вставьте следующий фрагмент в конце кода:
for (var i = 0; i<doggy.length; i++) {
alert(doggy[i]);
}
При выполнении кода в web-браузере появляются три окна. В первом окне
выводится «boli», во втором — «black», а в третьем — «boni, white».
Перемещение по дереву
Предположим, что создается родословная для Во, самого известного пса
семейства. Для упрощения будем считать, что собака имеет только одного пред-
ка в дереве. У Во два щенка: Boli и Boy. Воу остается холостяком до конца
своей жизни, а у Boli появляются два щенка: Boni и Bulvul. Потомками Boni
являются Spotty и Магу.
Родословная собачьего семейства имеет вид:
Во - Boli - Bulbul
- Boni - Spotty
r- Mary
- Boy
Работа с деревьями объектов
647
append(boni, тагу);
function navigate(dog, generation) {
var name = dog[OJ;
for (var i=1; i<generation; i++)
document.write("<IMG BORDER=1 SRC=images/blank.gif>");
document.write(name + "<BR>");
generation++;
for (var j=2; j<dog.length; j++ ) // имеет потомков
navigate(dog[j], generation);
}
navigate(bo, 1);
</SCRIPT>
</B0DY>
</HTML>
Результат выводится в web-браузере, как показано на рис. 26.1.
Рис. 26.1. Вывод дерева объектов
Удаление объектов-потомков
Листинг 26.17 представляет код для удаления объекта из дерева объектов с
помощью функции deleteElement. Функцию deleteElement нужно использо-
вать с осторожностью. Например, в дереве родословной собак индексы мас-
сива 0 и 1 зарезервированы для клички и цвета шерсти. Если удалить элементы
644
Глава 26
Л исти н г 26.14 Перемещение в дереве объектов
<НТМ1_>
<SCRIPT LANGUAGE="JavaScript”>
function navigate(dog, generation) {
var name = dog[OJ;
document.write(name + ”<BR>”);
generation++;
for (var j=2; j<dog.length; j++ ) // имеет наследников
navigate(dog[j], generation);
}
navigate(bo, 1);
</SCRIPT>
</HTML>
При выполнении этого кода в web-браузере будет получен следующий ре-
зультат:
Ьо
boli
bulbul
boni
spotty
mary
boy
Поиск объекта в дереве объектов
Как говорилось выше, перемещение по дереву объектов выполняется от
корня в направлении объектов следующих поколений. Тот же самый принцип
используется при поиске в дереве определенного объекта. Поиск начинается
с корня и продолжается, пока не будет найдено соответствие. В ряде случаев
можно вернуть объект, не осуществляя перемещение до последнего объекта.
Например, код листинга 26.16 создает дерево объектов для родословной и
затем ищет собаку с кличкой «Bulbul». Как только собака найдена, на экран
выводятся ее кличка и масть, и функция устанавливает флаг found (найдено).
Если флаг равен true, поиск прекращается.
Листинг 26.15 Поиск объекта в дереве объектов
<НТМ1_>
<HEAD>
<TITLE>Searching for Bulbul</TITLE>
Работа с деревьями объектов
649
return;
for (var i=n; Klength-1; i++)
array[i] = array[i+1];
array, length—;
}
function navigate(dog, generation) {
var name = dog[0];
for (var i=1; i<generation; i++)
document.write("<IMG BORDER=1 SRC=images/blank.gif>’’);
document.write(name + ”<BR>”);
generation++;
for (var j=2; j<dog.length; j++ ) // имеет потомков
navigate(dog[j], generation);
}
navigate(bo, 1);
deleteElement(boni, 2);
navigate(bo, 1);
</SCRIPT>
</BODY>
</HTML>
Результат работы кода показан на рис. 26.2.
Рис. 26.2. Объектное дерево до и после удаления
646
Глава 26
Отметим, что функция search при вызове получает корень (Ьо), поколение
(1) и имя для поиска («bulbul»).
Отображение дерева объектов
Вывод дерева объектов на экран является важной задачей. Можно изме-
нить код листинга 26.15, чтобы создать новую функцию, которая отображает
дерево объектов (см. листинг 26.16).
Отметим, что код листинга 26.16 использует изображение blank.gif, распо-
ложенное в подкаталоге images каталога, хранящего файл HTML. Этот файл
можно найти на сайте www.lory-press.ru.
Каталог 26.16 Вывод дерева объектов на экран
<HTML>
<HEAD>
<TITLE>Displaying an object tree</TITLE>
</HEAD>
<BODY>
<SCRIPT LANGUAGE=’’JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDogC’bo”, "brown”);
var boli = createDogC’boli", "black and white");
var boy = createDogC’boy", "brown");
var bulbul - createDog("bulbul", "brown");
var boni = createDogC’boni". "black and white");
var spotty = createDog("spotty", "black and white");
var mary = createDog("mary", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
Работа с деревьями объектов
651
function handler(name) {
alert(name);
}
function navigate(dog, generation) {
var name = dog[0];
for (var i=1; i<generation; i++)
document.write(’’<IMG BORDER=1 SRC=images/blank.gif>’’);
document.write("<A HREF=\”javascript:handler(+ name + •••)\->" +
name + ”</A><BR>”);
generation++;
for (var j=2; j<dog.length; j++ ) // имеет потомков
navigate(dog[j], generation);
}
navigate(bo, 1);
</SCRIPT>
</B0DY>
</HTML>
Теперь каждый объект представляется гиперссылкой (см. рис. 26.3), кото-
рая может реагировать на щелчок пользователя.
Рис. 26.3. Демонстрация обработчика событий в дереве объектов
Иногда следует изменять внешний вид дерева объектов при ответе на со-
бытие пользователя. Например, в дереве папок желательно выводить значок
открытой папки для папки, на которой пользователь щелкнул мышью. Это
648
Глава 26
с индексами 0 и 1, то дерево потеряет свою структуру. Однако, разумно ис-
пользуя функцию delete Element, можно производить безопасное удаление
объектов. Код листинга 26.17 удаляет из дерева третий элемент (Spotty), свя-
занный с Boni.
Листинг 26.17 Удаление объекта-потомка
<НТМ1_>
<HEAD>
<TITLE>Deleting a child object</TITLE>
</HEAD>
<BODY>
<SCRIPT LANGUAGE="JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDogC’bo", "brown");
var boli = createDogC’boli", "black and white");
var boy = createDogC’boy", "brown");
var bulbul = createDogC’bulbul", "brown");
var boni = createDogC’boni", "black and white");
var spotty = createDog("spotty", "black and white");
var mary = createDog("mary", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
append(boni, mary);
function deleteElement(array, n) {
//удаление n-го элемента массива
var length = array.length;
if (n >= length || n<0)
Работа с деревьями объектов
653
var boni = createDog("boni", "black and white");
var spotty = createDog("spotty", "black and white");
var mary = createDog("mary", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
append(boni, mary);
var dogClicked="";
function handler(name) {
dogClicked = name;
var doc = framesfO].document;
doc.clear();
redraw(bo, 1, doc);
doc.close();
}
function redraw(dog, generation, doc) {
var name = dog[0];
for (var i=1; Kgeneration; i++)
doc.write("<IMG BORDER=1 SRC=images/blank.gif>");
doc.write("<A HREF=\"javascript:parent.handler(’" +
name + "’)\">");
if (name==dogClicked)
doc.write("<I><B>” + name + "</B></!>");
else
doc.write(name);
doc.wfite(”</A><BR>");
generation++;
for (var j=2; j<dog.length; j++ ) // имеет потомков
redraw(dog[j], generation, doc);
}
</SCRIPT>
<FRAMESET onLoad="redraw(bo, 1, frames[0].document);
frames[0].document.closeO" R0WS="100%, *">
<FRAME NAME=frame1 SRC=framel.html>
<FRAME NAME=frame2 SRC=frame2.html>
</FRAMESET>
</HTML>
650
Глава 26
Во втором дереве Spotty отсутствует.
Обработка событий в дереве объектов
Можно добавить в дерево обработчик событий, чтобы приложение могло
реагировать на действия пользователя: на щелчок мыши или перемещение
мыши по объекту. Например, код листинга 26.18 добавляет функцию с име-
нем handler, которая реагирует на то, что пользователь щелкает мышью на
объекте. В ответ выводится окно с кличкой собаки.
Листинг26.18 Добавление обработчика событий в дерево объектов
<НТМ1_>
<HEAD>
<TITLE>Event handling in an object tree</TITLE>
</HEAD>
<BODY>
<SCRIPT LANGUAGE="JavaScript">
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDog("bo", "brown");
var boli = createDog("boli", "black and white");
var boy = createDog("boy", "brown");
var bulbul = createDog("bulbul”, "brown");
var boni = createDogC'boni", "black and white");
var spotty = createDog("spotty", "black and white");
var mary = createDogC'mary", "black and white");
append(bo, boli);
append(bo, boy);
append(boli, bulbul);
append(boli, boni);
append(boni, spotty);
append(boni, mary);
Управление
апплетами
пплеты Java являются мощным инструментом. С их помощью можно
делать множество вещей, в частности создавать анимацию, общаться с серве-
ром, писать текстовый редактор, выводить заголовки новостей. С помощью
апплетов можно делать практически все то же самое, что и с помощью Java.
Апплеты широко распространены. Они работают в браузерах Netscape и
Microsoft. Аналогичные вещи можно делать и с помощью компонентов ActiveX,
но компоненты ActiveX хорошо работают только в браузерах Microsoft. Пона-
добится специальный подключаемый модуль (plug-in), если придется исполь-
зовать ActiveX в Netscape Navigator.
Очень странно, что компания Microsoft решила исключить поддержку ап-
плетов Java в своей новой операционной системе Windows ХР. Однако пользо-
ватели могут отдельно загрузить подключаемый модуль.
В этой главе не рассматривается вопрос создания апплетов. Здесь обсужда-
ются различные аспекты работы с апплетами: как можно управлять апплета-
ми на странице HTML с помощью JavaScript. Может понадобиться выполнить
методы апплета, прочитать его свойства или передать ему значение для после-
дующей обработки. Представьте себе, что имеется защищенный паролем апп-
лет chat. Пользователь должен зарегистрироваться на странице HTML, преж-
де чем он сможет загрузить сам апплет на следующей web-странице. Это
делается для того, чтобы неавторизованные лица не могли загрузить апплет.
Проблема кажется простой, но она требует выполнения следующих действий:
652
Глава 26
создает проблему, так как дерево должно перерисовываться. Для ее решения
используется прием, сохраняющий код JavaScript на другой странице.
Пример листинга 26.19 использует рамку для сохранения кода JavaScript и
рисует дерево объектов в другом документе.
>uR>ul
Ьо
___boli
_Jbov
Рис. 26.4. Дерево объектов, которое может менять свой вид
Листинг 26.19 Использование рамки
<НТМ1_>
<SCRIPT LANGUAGE="JavaScript”>
function createDog(name, color) {
var dog = new ArrayO;
dog[0] = name;
dog[1] = color;
return dog;
}
function append(parent, child) {
parent[parent.length] = child;
}
var bo = createDog("bo”, ’’brown’’);
var boli = createDogC’boli", ’’black and white”);
var boy = createDog("boy”, "brown”);
var bulbul = createDog("bulbul", "brown");
Управление апплетами
657
который не готов к работе, произойдет ошибка. Функция isAppletReady (см.
листинг 27.2) решает эту проблему.
Листинг 27.2 Функция isAppletReady
<SCRIPT LANGUAGE="JavaScript">
function isAppletReady(applet) {
return applet.isActive();
}
</SCRIPT>
Функция isAppletReady получает в качестве параметра апплет, чью готов-
ность надо проверить. Объект апплета имеет свойство isActive, значение ко-
торого равно true, если апплет готов к работе.
На странице HTML апплет ничем не отличается от любого другого объек-
та. Предположим, что существует совокупность апплетов, представленная ап-
плетами в объектной модели документа. Для ссылки на первый апплет в доку-
менте можно использовать document.applets[0], а n-ый апплет в документе —
это document.applets[n-l].
Код листинга 27.3 показывает страницу HTML с апплетом. Имеется форма
с кнопкой, которую пользователь может нажать для проверки того, что пер-
вый апплет в документе готов к работе.
Л истин г 27.3 Код для проверки готовности апплета к работе
<НТМ1_>
<HEAD>
<TITLE>Checking if an applet is ready</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function isAppletReady(applet) {
return applet.isActive();
}
</SCRIPT>
</HEAD>
<BODY>
<APPLET
CODEBASE = ”. ”
CODE = "MyApplet.class"
NAME = "TestApplet"
WIDTH = 200
HEIGHT = 50
HSPACE = 0
VSPACE = 0
654
Глава 26
Браузеры Netscape будут выказывать недовольство, если framel.html или
frame2.html будет пустым. Во избежание этого запишите пустую строку.
Отметим, что необходимо закрыть документ в случае события onLoad набо-
ра рамок (frameset); иначе Internet Explorer будет вести себя непредсказуемо —
например, он может отказаться очистить документ.
Объектное дерево, которое может изменять свой вид, показано на рис.
26.4. В этом примере «boni» выводится курсивом, так как это выбранная
собака.
Заключение
Объект Array является одной из немногих структур данных, доступных при
работе с объектами в JavaScript. Благодаря гибкости объекта Array, можно при-
сваивать массив в качестве элемента другого массива, что позволяет создавать
связанные списки и деревья объектов.
Как было показано в этой главе, деревья объектов имеют целый ряд прило-
жений. Снабженные функциями для создания объектов, добавления объекта
к другому объекту и удаления объекта, деревья объектов становятся простыми
и удобными в работе. Были также представлены функции перемещения, ото-
бражения и поиска — все, что требуется для использования дерева объектов.
Управление апплетами
659
return (5 + а);
}
}
Если известно, как обратиться к объекту апплета в документе HTML, то
можно вызвать открытый метод апплета, как если бы он был методом любого
другого объекта JavaScript. Код листинга 27.6 показывает пример использова-
ния метода myMethod апплета.
Листинг 27.6 Вызов открытого метода апплета
<HTML>
<HEAD>
<TITLE>Call an applet's method</TITLE>
<SCRIPT>
function add() {
var result = document.applets[0].myMethod(”3'');
alertC'The sum is " + result);
}
</SCRIPT>
</HEAD>
<BODY>
Click the following button to run the applet's method.
<BR>
<FORM>
<INPUT TYPE=BUTTON onClick='add();'
VALUE="Run Applet's Method ">
' </FORM>
<APPLET
CODEBASE = "
CODE = "MyApplet.class”
NAME = "TestApplet"
WIDTH = 200
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
</APPLET>
</BODY>
</HTML>
656
Глава 27
1. Вывод страницы Login, где пользователь может ввести имя и пароль.
2. Перехват имени пользователя и пароля и их передача на вторую страни-
цу в виде cookie.
3. Вывод второй страницы.
4. Проверка того, что браузер закончил загрузку апплета.
5. Если загрузка апплета завершена, передать cookie (имя пользователя и
пароль) в апплет. Если браузер не закончил загрузку апплета, вернуться
к шагу 4.
Кроме того, прежде чем позволить серверу послать апплет Java браузеру,
необходимо убедиться втом, что браузер понимает Java и что это свойство вклю-
чено.
В этой главе выполняются эта и ряд других задач. Обсуждается также
вопрос создания апплетов, которые могут взаимодействовать с объектной
моделью документов страницы HTML.
Поддержка Java
Современные браузеры поддерживают Java, но пользователи могут отключить
это свойство по ряду причин. В случае применения апплетов на какой-либо из
своих страниц вы должны проверять, включено ли это свойство. Это можно де-
лать с помощью метода javaEnabled объекта navigator. Функция isJavaEnabled,
использующая этот метод, проверяет, можно л и выполнить апплет на странице.
С помощью этой функции можно предупредить пользователя о том, что под-
держка Java в браузере отключена. Листинг 27.1 представляет функцию
isJavaEnabled.
Листинг 27.1 Функция isJavaEnabled
<SCRIPT LANGUAGE=”JavaScript”>
function isJavaEnabled() {
return ( navigator.javaEnabledO );
}
</SCRIPT>
Готовность апплета
При взаимодействии с апплетом посредством JavaAScript необходимо убедить-
ся в том, что браузер закончил загрузку апплета и апплет инициализировал
себя. Если функция или оператор JavaScript попытается обратиться к апплету,
Управление апплетами
661
Листинг 27.8 Открытый метод, который возвращает значение свойства
import java.applet.*;
public class MyApplet extends Applet {
private String secretValue =
public String getAppletSecretValueO {
return secretValue;
}
}
Задание свойства апплета
Как и в предыдущем коде, запись свойства апплета должна выполняться
опосредованно с помощью открытого метода в апплете. В апплете Java со-
здайте открытый метод, который получает аргумент. Этот аргумент явля-
ется значением, которое необходимо присвоить свойству.
Листинг 27.9 Запись свойства апплета
<HTML>
<HEAD>
<TITLE>Write to an applet’s property</TITLE>
<SCRIPT>
function setSecretValue(str) {
document.applets[0].setSecretValue(st r);
}
</SCRIPT>
</HEAD>
<BODY>
<FORM>
Type your message in the following box as the applet’s property value.
<INPUT TYPE=TEXT NAME=Secret>
<INPUT TYPE=BUTTON
onClick='setSecretValue(this.form.Secret.value);’
VALUE=”Set Property”>
</FORM>
<BR>
<APPLET
CODEBASE =
658
Глава 27
Листинг 27.3 Продолжение
ALIGN = middle
</APPLET>
Click the button below to check if the applet is ready.
<BR>
<FORM>
<INPUT TYPE=button VALUE="Check if the applet is ready”
onClick="if (!isAppletReady(document.applets[0])) alert('not ready');”
</FORM>
</BODY>
</HTML>
Изменение размера апплета
Браузер обращается с апплетом так же, как с другими объектами документа. В
результате можно ссылаться на апплет по его имени или по порядку появле-
ния в документе. Например, код JavaScript в листинге 27.4 устанавливает вы-
соту первого апплета на странице в значение 90 пикселей, а его ширину — в
60 пикселей.
Л исти н г 27.4 Изменение размера апплета
<SCRIPT LANGUAGE="JavaScript">
document.applets[O].height = 90;
document.applets[O], width = 60;
</SCRIPT>
Вызов метода апплета
Если метод апплета является открытым, его можно вызвать из кода JavaScript.
Предположим, что имеется апплет с одним методом с именем myMethod, ко-
торый возвращает int и получает аргумент int (см. листинг 27.5).
Листинг 27.5 Апплет, открытый метод которого будет вызываться в
сценарии JavaScript
import java.applet.*;
public class MyApplet extends Applet {
public int myMethod(int a) {
Управление апплетами
663
j ava. awt.Toolkit.getDefaultToolkit().getScreenSize().height);
</SCRIPT>
</HEAD>
</HTML>
Взаимодействие апплета и JavaScript
Можно реализовать коммуникацию между апплетом и JavaScript для досту-
па к объектной модели документа или для вызова функции JavaScript на стра-
нице HTML. Internet Explorer версии 4.0 и выше и Netscape Navigator версии
3.0 и выше позволяют сделать это с помощью класса-оболочки
netscape.javascript.JSObject. В Navigator 3.0 класс JSObject поставляется в фай-
ле с именем java_30 или java_301; в Navigator 4.0 этот файл называется java40.jar.
В Internet Explorer 4.0 файл находится в файле H3rfb7jn.zip.
По соображениям безопасности поддержка JSObject отключена по умолча-
нию. Чтобы включить поддержку JSObject, в тег APPLET нужно добавить но-
вый атрибут с именем MAYSCRIPT:
<APPLET CODE="MyApplet.class" CODEBASE="." WIDTH="200"
HEIGHT="100" MAYSCRIPT>
JSObject будет отключен, если MAYSCRIPT отсутствует.
Каждый объект JSObject инкапсулирует сущность в объектной модели до-
кумента JavaScript. Методы JSObject перечислены в таблице 27.1.
Таблица 27.1. Методы JSObject
Метод
Описание
public Object call(String
methodName, Object args[])
public Object eval(String s)
public Object getMember(String
name)
Вызывает функцию JavaScript. Передает
null, если функция не имеет аргументов;
иначе передает массив Object. Эквива-
лентен this.methodName(args[0],
args[1],...) в JavaScript.
Анализирует выражение JavaScript.
Выражение является строкой исходного
Кода JavaScript, которая будет рассматри-
ваться в контексте, заданном ключевым
словом this.
Извлекает именованный член объекта
JavaScript. Эквивалентен this.name в
JavaScript.
660
Глава 27
Получение свойства апплета
Свойство апплета невозможно читать напрямую, даже если оно является от-
крытым. Чтобы прочитать свойство апплета, необходимо создать открытый
метод в апплете, который возвращает значение свойства. Затем этот метод
вызывается в коде JavaScript для опосредованного чтения свойства.
Файл HTMLвлистинге27.7 показываетJavaScript-cjjyHKuniogetSecret Value,
которая вызывает метод getAppletSecretValue в первом апплете документа. В
апплете должен быть создан метод getAppletSecretValue, возвращающий зна-
чение свойства апплета, которое необходимо читать в коде JavaScript.
Листинг 27.7 Чтение свойства апплета
<HTML>
<HEAD>
<TITLE>Read an applet's property</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function getSecretValueO {
var secret = document.applets[O].getAppletSecretValue();
alert(”The secret is " + secret);
}
</SCRIPT>
</HEAD>
<BODY>
Click <A HREF="javascript:getSecretValue() ">
here</A> to display the secret value.
<BR>
<APPLET
CODEBASE = ". ”
CODE = "MyApplet.class"
NAME = "TestApplet"
WIDTH = 200
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
</APPLET>
</BODY>
</HTML>
Код апплета MyApplet приведен влистинге 27.8.
Управление апплетами
665
Этот метод возвращает JSObject, который представляет объект Window в
сценарии JavaScript для окна, содержащего данный апплет. Так как этот метод
получает в качестве параметра только java.awt.Applet, JSObject может быть до-
ступен в апплете, но не в Bean, если только Bean не является также апплетом.
Java Plug-in предоставляет полную поддержку JSObject в Internet Explorer
3 1/4.0 с доступом к объектной модели документа через СОМ.
Java Plug-in предоставляет ограниченную поддержку
JSObject в Navigator 3.0/4.0 с доступом к объектной модели
документа через Plug-in API Netscape.
В настоящее время в Navigator 3.0 можно получить доступ с помощью
JSObject к следующим объектам JavaScript:
• Anchor
• Document
• Element
• Form
• Frame
• History
• Image
• Link
• Location
• Navigator
• Option
•URL
• Window
В Netscape Navigator 4.0 поддерживаются все перечисленные объекты
JavaScript. Кроме того, Navigator поддерживает объекты:
• Layer
•UIBar
Все другие объекты JavaScript не поддерживаются, и обращение к ним че-
рез JSObject приведет к порождению исключений Java.
Отметим, что различные браузеры могут поддерживать один и тот же объект
JavaScript, но предоставляемые этим объектом методы и свойства могут быть
р °личны.
662
Глава 27
Листинг 27.3 Продолжение
CODE = "MyApplet.class”
NAME = ’’TestApplet”
WIDTH = 200
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
MAYSCRIPT
</APPLET>
</BODY>
</HTML>
В листинге 27.10 показан апплет с открытым методом для записи свойства.
Л исти н г 27.10 Открытый метод, который записывает свойство апплета
import java.applet.*;
public class MyApplet extends Applet {
private String secretValue =
public void setSecretValue(String str) {
secretValue = str;
}
}
Прямое использование классов Java
Java на странице HTML без апплета? Вполне возможно. Однако эта методика
работает только в браузерах Netscape. Например, код листинга 27.11 использует
методы класса java.awt.Toolkit для вывода ширины и высоты монитора.
Л исти н г 27.11 Прямое использование классов Java
<HTML>
<HEAD>
<TITLE>Accessing Java directly from JavaScript</TITLE>
<SCRIPT LANGUAGE="JavaScript">
alert("Screen Dimension\n" +
width:" +
j ava.awt.Toolkit.getDefaultToolkit().getScreenSize().width +
" height:" +
Управление апплетами
667
</FORM>
<BR> Апплет копирует значение firstName в loginName.
<BR>
<APPLET
CODEBASE = ". ”
CODE = "MyApplet.class”
NAME = "TestApplet"
WIDTH = 40
HEIGHT = 30
HSPACE = 0
VSPACE = 0
ALIGN = middle
MAYSCRIPT
</APPLET>
</BODY>
</HTML>
Вызов функций JavaScript из апплета
Получив доступ к объектной модели документа, апплет может делать все, что
можно делать в JavaScript, в том числе вызывать функции JavaScript. Листинг
27.14 показывает апплет, который вызывает функции JavaScript noArg и twoArgs,
а также метод alert.
Л и сти н г 27.14 Апплет, который вызывает функции JavaScript
import java.applet.*;
import netscape.javascript.*;
public class MyApplet extends Applet {
public void init() {
JSObject window = JSObject.getWindow(this);
// вызов функции noArg,
// которая не имеет аргументов
window. call(’’noArg", null);
// вызов метода alert с передачей одного
// аргумента obj1
Object[] obj1 = new Object[1];
obj1[0] = "Hello from MyApplet!!!";
window.call("alert", obj1);
664
Глава 27
Табл и ца 27.1. Продолжение
Метод
public void setMember(String name,
Object value)
public void setSlot(int index, Object
value)
public String toStringO
public Object getSlot(int index)
public static JSObject
getWindow(Applet applet)
public void removeMember(String
name)
Описание
Задает именованный член объекта
JavaScript. Эквивалентен this.name =
value в JavaScript.
Задает индексированный член объекта
JavaScript. Эквивалентен this[index] =
value в JavaScript.
Преобразует JSObject в String.
Извлекает индексированный член объек-
та JavaScript. Эквивалентен this[index] в
JavaScript.
Возвращает JSObject для окна, содержа-
щего заданный апплет. Этот метод досту-
пен только на стороне клиента.
Удаляет именованный член объекта
JavaScript.
Для компиляции кода Java с целью использования JSObject необходимо
указать пакет netscape.javascript в CLASSPATH. В настоящее время Java Plug-in
1.2.2, который можно загрузить с сайта компании Sun, содержит
netscape.javascript в JAR-файле с именем JAWS.JAR. Чтобы откомпилировать
апплет, который использует JSObject, добавьте JAWS.JAR в CLASSPATH пе-
ред компиляцией. Этот пакет включен в современные инструментальные ути-
литы Java, такие как J Builder 4.0 и 5.0.
Отметим, что JSObject не поддерживается в Appletviewer на платформе Java
2.0, Standard Edition версии 1.2.2. В результате апплеты, использующие JSObject,
могут не выполняться в Appletviewer или приводить к генерации исключений.
Чтобы проверить апплет, который обращается к объектной модели документа
страницы HTML, откройте в браузере страницу HTML, содержащую тег
<APPLET>.
Для совместимости с браузером используйте только методы getWindow(),
call(), eval(), setMember() и getMember(). Реализации методовgetSlot(), setSlot(),
removeMemberO и toStringO зависят от браузера, поэтому результат выполне-
ния может отличаться в разных версиях и платформах браузеров.
Любая разработка с помощью JSObject начинается со статического метода:
public static JSObject getWindow(Applet a)
Управление апплетами
669
</APPLET>
</BODY>
</HTML>
Анализ оператора JavaScript в апплете
Пример, демонстрирующий, как проанализировать оператор JavaScript в апп-
лете, представлен в листингах 27.16 и 27.17. Листинг 27.16 показывает апплет,
который анализирует функцию JavaScript. Код листинга 27.17 является стра-
ницей HTML, которая применяет этот апплет. Сам апплет использует метод
eval для записи в скрытые (HIDDEN) элементы loginName и password и для
вызова метода alert.
Л исти н г 27.16 Апплет, который использует JavaScript-метод eval
import java.applet.*;
import netscape.javascript.*;
public class MyApplet extends Applet {
public void init() {
JSObject window = JSObject.getWindow(this);
String loginName = "boni";
String password = "secret”;
window. eval( "document. forms[0]. loginName. value=’” + loginName +
window.eval(”document.forms[0].password.value="’ + password + .);
window.eval("alert(’Secret login name and password copied.’)");
}
}
Л истинг 27.17 Страница HTML, содержащая апплет MyApplet
<HTML>
<HEAD>
<TITLE>Invoke a JavaScript statement</TITLE>
</HEAD>
<BODY>
CFORM ACTION=Login.jsp METHOD=POST>
<INPUT TYPE=HIDDEN NAME=loginName>
CINPUT TYPE=HIDDEN NAME=password>
CINPUT TYPE=SUBMIT VALUE="Click to Login’’>
C/FORM>
The following applet will copy the secret login name and
password to the HIDDEN elements.
666
Глава 27
Доступ к объектной модели документа
в апплете
Как упоминалось выше, можно использовать объект JSObject для доступа к
объектной модели документа. Например, код листинга 27.12 является аппле-
том с именем MyApplet, который считывает значение элемента TEXT firstName
в объект String с именем str. Затем апплет записывает str в ТЕХТ-элемент
loginName той же формы.
Л исти н г 27.12 Апплет, который обращается к объектной модели
документа
import java.applet.*;
import netscape.javascript.*;
public class MyApplet extends Applet {
public void init() {
JSObject window = JSObject.getWindow(this);
JSObject doc = (JSObject) window.getMember("document")’
JSObject form = (JSObject) doc.getMember("form1");
JSObject firstName = (JSObject) form.getMember("firstName'.');
String str = (String) firstName.getMember("value");
JSObject loginName = (JSObject) form.getMember("loginName");
loginName.setMember("value", str);
}
}
Страница HTML, приведенная влистинге 27.13, содержит апплет (листинг
27.12) и демонстрирует, как апплет может обращаться к документу HTML.
Л исти н г 27.13 Страница HTML, содержащая апплет, который
обращается к объектной модели документа
<HTML>
<HEAD>
< TITLE>Accessing the document object model</TITLE>
</HEAD>
<BODY>
< FORM NAME=form1 ACTION=BLAH.ASP METHOD=POST>
< BR>firstName: <INPUT TYPE=TEXT NAME=firstName VALUE=Laylian>
< BR>loginName: <INPUT TYPE=TEXT NAME=loginName>
<BR><INPUT TYPE=SUBMIT>
Управление апплетами
671
</APPLET>
</BODY>
</HTML>
В браузерах компании Microsoft используйте метод document.write() для
изменения APPLET PARAM во время компоновки.
Коммуникации между апплетами с
помощью JavaScript
Зная, как вызвать функцию JavaScript из апплета и метод апплета из JavaScript,
можно организовать коммуникации между апплетами. Если, например, тре-
буется выполнить метод второго апплета в методе первого, то можно создать
промежуточную функцию JavaScript, которая вызывает метод второго аппле-
та, а затем функцию JavaScript из первого апплета.
На рис. 27.1 показано, как апплет может послать сообщение другому апплету.
Первый апплет имеет текстовое поле и кнопку, а второй апплет имеет только одно
текстовое поле. Пользователь может ввести строку в текстовое поле первого апп-
лета. Когда пользователь щелкает мышью на кнопке первого апплета, введенная
строка выводится в текстовом поле второго апплета.
Рис. 27.1. Коммуникации между апплетами
Код апплетов приводится в листингах 27.19 и 27.20. Листинг 27.21 показы-
вает страницу HTML, которая содержит эти апплеты.
668
Глава 27
Листинг 27.14 Продолжение
// вызов функции twoArgs
// с двумя аргументами
Object[] obj2 = new 0bject[2];
obj2[0] = "one ’’;
obj2[1] = "2";
window.call( "twoArgs’’, obj2);
}
}
Этот апплет можно использовать на странице HTML (см. листинг 27.15).
Листинг 27.15 Страница HTML, содержащая апплет, который вызывает
функции JavaScript
<HTML>
<HEAD>
<TITLE>Invoke a JavaScript function from an applet</TITLE>
<SCRIPT LANGUAGE=”JavaScript’’>
function noArg() {
alert (’’You have successfully invoked a no argument ” +
’’JavaScript function from an Applet.”);
}
function twoArgs(arg1, arg2) {
alert(arg1 + arg2);
}
</SCRIPT>
</HEAD>
<BODY>
Следующий апплет вызывает метод alert и две пользовательские функции
noArg и twoArgs:
<APPLET
CODEBASE =
CODE = "MyApplet.class"
NAME = "TestApplet"
WIDTH = 400
HEIGHT = 300
HSPACE = 0
VSPACE = 0
ALIGN = middle
MAYSCRIPT
Управление апплетами
673
textFieldl.setText(message);
}
}
Листинг 27.21 Страница HTML, где осуществляется коммуникация
между апплетами
<HTML>
<HEAD>
<TITLE>Applet-to-applet communication through JavaScript</TITLE>
<SCRIPT LANGUAGE^'JavaScri pt ">
function sendMessage(message) {
document. applets[1 ]. setMessag'e(message);
}
</SCRIPT>
</HEAD>
<BODY>
Вы можете послать сообщение второму апплету из первого апплета.
<ВВ>Введите свое сообщение в текстовом поле первого апплета
<BR>n нажмите кнопку.
<BR>
<APPLET
CODEBASE = "."
CODE = "MyApplet.class"
NAME = "TestApplet"
WIDTH = 300
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
MAYSCRIPT
</APPLET>
<BR><BR>
<APPLET
CODEBASE = "."
CODE = "SecondApplet.class"
NAME = "SecondApplet"
WIDTH = 300
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
670
Глава 27
Листинг 27.17 Продолжение
<APPLET
CODEBASE = ”. ”
CODE = ’’MyApplet. class”
NAME = "TestApplet”
WIDTH =400 k
HEIGHT = 300
HSPACE = 0
VSPACE = 0
ALIGN = middle
MAYSCRIPT
</APPLET>
</BODY>
</HTML>
Задание параметра апплета
PARAM VALUETera <APPLET> не может изменяться во время выполнения —
оно может изменяться только во время компоновки. Работающий только в
браузерах Netscape, код листинга 27.18 использует «сущности» JavaScript для
задания вычисляемых VALUES, которые могут поступать из переменной
JavaScript, функции JavaScript или выражения JavaScript.
Листинг 27.18 Задание параметров апплета в браузерах Netscape
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var greeting = "Hello World";
function sayHello () {
return "Hello again";
}
</SCRIPT>
</HEAO>
<BODY>
<APPLET CODE="MyApplet.class" HEIGHT=100 WIDTH=400>
<PARAM NAME="jsVariable" VALUE="&{greeting};">
<PARAM NAME="jsFunction" VALUE="&{sayHello()}; ">
<PARAM NAME="jsExpression"
VALUE=”&{ 'This is an expression’ .toUpperCaseQ}; ">
Управление апплетами
675
window, eval ("document. applets[ 1 ]. setMessage('" + textFieldl. getText ()
}
}
Листинг 27.23 Второй апплет при прямой коммуникации между апплетами
import java.applet.*;
import java.awt.TextField;
public class SecondApplet extends Applet {
TextField textFieldl = new TextFieldO;
public void init() {
this.setLayout(null);
add(textField1, null);
textFieldl.setSize(100, 20);
}
public void setMessage(String message) {
textField1.setText(message);
}
)
Листинг 27.24 Файл HTML для прямой коммуникации между апплетами
<HTML>
<HEAD>
<TITLE>Direct applet-to-applet communication</TITLE>
</HEAD>
<B0DY>
Вы можете послать сообщение второму апплету из первого апплета.
<ВВ>Введите свое сообщение в текстовом поле в первом апплете
<ВВ>и нажмите кнопку.
<BR>
<APPLET
CODEBASE = "."
CODE = "MyApplet.class"
NAME = "TestApplet"
WIDTH = 300
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
672
Глава 27
Листинг 27.19 Первый апплет
import java.applet.*;
import java.awt.*;
import java.awt.event;
import netscape.javascript.*;
public class MyApplet extends Applet implements ActionListener {
TextField textFieldl = new TextFieldO;
Button buttonl = new Button(”Send Message”);
TextField textField2 = new TextFieldO;
JSObject window;
public void init() {
this.setLayout(null);
add(textField1, null);
textFieldl.setSize(100, 20);
add(button1, null);
buttonl.setBounds(120, 0, 100, 20);
buttonl.setForeground(new Color(255, 255, 255));
buttonl.setBackground(new Color(0, 0, 0));
buttonl.addActionListener(this);
window = JSObject.getWindow(this);
}
public void actionPerformed(ActionEvent ae) {
window, eval (”sendMessage( + textFieldl. getTextO + ”’)’’);
}
}
Л истинг 27.20 Второй апплет
import java.applet.*;
import java.awt.TextField;
public class SecondApplet extends Applet {
TextField textFieldl = new TextFieldO;
public void initO {
this.setLayout(null);
add(textField1, null);
textFieldl.setSize(100, 20);
}
public void setMessage(String message) {
пт
Разработка
масштабируемых
приложений
с помощью EJB
28 Enterprise JavaBeans
29 EJ В сеанса
30 EJB сущности
31 Язык запросов EJB
32 Служба сообщений Java
33 EJB, управляемые сообщениями
674
Глава 27
Листинг 27.21 Продолжение
</АРР1_ЕТ>
</BODY>
</HTML>
Прямые коммуникации между апплетами
Можно использовать прямую коммуникацию между апплетами, которая об-
ходится без JavaScript.
Рассмотрим пример прямой коммуникации между двумя апплетами. Пер-
вый апплет посылает сообщение второму апплету на одной странице HTML.
Листинги 27.22 и 27.23 показывают код первого и второго апплетов соответ-
ственно. Листинг 27.24 представляет страницу HTML, которая содержит два
апплета.
Листинг 27.22 Первый апплет при прямой коммуникации между
апплетами
import java.applet.*;
import java.awt.*;
import java.awt.event.
import netscape.javascript.*;
public class MyApplet extends Applet implements ActionListener { 1
TextField textFieldl = new TextFieldO;
Button buttonl = new Button(”8end Message”);
TextField textField2 = new TextFieldO;
JSObject window;
public void init() {
this.setLayout(null);
add(textField1, null);
textFieldl.setSize(100, 20);
add(button1, null);
buttonl.setBounds(120, 0, 100, 20);
buttonl.setForeground(new Color(255, 255, 255));
buttonl.setBackground(new Color(0, 0, 0));
buttonl.addActionListener(this);
window = JSObject.getWindow(this);
}
public void actionPerformed(ActionEvent ae) {
28
Enterprise
JavaBeans
предыдущих главах рассказывалось о разработке и развертывании серв-
летных приложений и приложений JSP. Сервлеты и JSP являются технологи-
ями, которые становятся все более и более популярными.
В этой части мы будем изучать, как разрабатывать и развертывать прило-
жения Enterprise JavaBeans (EJB). Вообще говоря, в случае простых web-при-
ложений следует использовать сервлеты/JSP и избегать EJB. Однако если на-
дежность и масштабируемость являются основными требованиями,
необходимо разрабатывать приложения EJB.
Технология EJB опирается на другие технологии Java. Прежде всего она
использует вызов удаленного метода Java (RMI, remote method invocation) в
качестве протокола коммуникаций между двумя EJB и между EJB и его кли-
ентом. Опытный программист Java должен знать, как применить RMI в рас-
пределенных вычислениях Java для вызова удаленных методов на удаленных
машинах.
Другой технологией, применяемой в EJB, является вызов удаленного ме-
тода по протоколу Итернета Inter-ORB (RMI-ПОР), где ORB означает «бро-
кер объектных запросов». RMI-IIOP — более переносимая версия RMI, кото-
рая может использовать ПОР консорциума OMG (Object Management Group).
RMI-IIOP особенно полезен при коммуникациях между EJB и клиентом.
И, наконец, EJB используют JNDI (Java Naming and Directory Interface) в
качестве службы именования, которая связывает имя с EJB.
676
Глава 27
Листинг 27.24 Продолжение
MAYSCRIPT
</APPLET>
<BRXBR>
<APPLET
CODEBASE = ”. ”
CODE = ’’SecondApplet. class”
NAME = ’’SecondApplet”
WIDTH = 300
HEIGHT = 50
HSPACE = 0
VSPACE = 0
ALIGN = middle
</APPLET>
</BODY>
</HTML>
Заключение
Апплеты являются полезными объектами, которые могут делать множество
вещей, недоступных HTML и JavaScript. По этой причине следует знать, как
работать с апплетами в контексте объектной модели документа.
В этой главе представлены методы, которые помогут при работе с апплетами.
Вы узнали, как проверять, включена ли поддержка Java в браузере, задавать и
получать свойство из апплета, посылать сообщения в апплет из JavaScript.
Говорилось также об организации коммуникаций между апплетами.
Enterprise JavaBeans
681
ков, можно избежать необходимости создавать свои собственные EJB,
что приводит к более быстрой разработке приложений. Спецификация
EJB гарантирует, что EJB, разработанные другими, можно использовать
в собственном приложении.
• Существует четкое разделение работ по созданию, развертыванию и ад-
министрированию приложения EJB. Это ускоряет процесс разработки
и развертывания.
• Контейнер EJB управляет транзакциями, данными управления состоя-
нием, многопоточностью, созданием пулов соединений и другими низ-
коуровневыми API без участия разработчика.
• Контейнер EJB обеспечивает безопасность для приложения.
• Архитектура EJB совместима с другими API Java.
Архитектура приложения EJB
Архитектура приложения EJB (см. рис. 28.1) расширяет архитектуру web-при-
ложения, добавляя дополнительный уровень.
Рис. 28.1. Архитектура приложения EJB
Клиентами EJB могут быть традиционное приложение Java, апплет, стра-
ница JSP или сервлет, другой EJB и т. п.
Отметим, что клиент никогда не вызывает методы EJB непосредственно.
Коммуникации между клиентами и EJ В осуществляются через контейнер EJB.
Сравните это с web-приложением, где клиентский web-браузер должен обра-
щаться к web-контейнеру для использования сервлета или страницы JSP.
На рис. 28.2 представлена структура приложения EJB в случае, если клиент
является сервлетом или страницей JSP.
Рис. 28.2. Приложение EJB с сервлетом в качестве клиента
Enterprise JavaBeans
683
Системный администратор
После развертывания необходимо обеспечить сопровождение приложения
EJB на повседневной основе — возможно, более точно будет сказать, на по-
минутной основе. Задача системного администратора состоит в том, чтобы
гарантировать круглосуточную работу приложения. Если сервер отказывает,
администратор должен обеспечить его немедленный перезапуск.
Системный администратор отвечает также за поддержание безопасности.
Например, на ответственности системного администратора находится созда-
ние новых учетных записей для новых пользователей или групп пользовате-
лей. В больших организациях эта роль требует привлечения отдельного со-
трудника.
Поставщик контейнера EJB
Поставщик контейнера EJB — это продавец, который имеет ресурсы для
создания контейнера EJB и следит за тем, чтобы программное обеспечение
соответствовало спецификации EJB. Разработчик контейнера EJB должен
предоставить различные утилиты для системного администратора. Например,
должна быть утилита, которую системный администратор может использо-
вать для добавления пользователя.
Поставщик сервера EJB
Поставщик сервера EJB предоставляет сервер EJB, который в свою очередь
содержит контейнер EJB. В настоящее время архитектура EJB не определяет
четко отличие этой роли от роли поставщика контейнера EJB. Большинство
контейнеров EJB поставляются вместе с сервером EJB.
Типы EJB
Существуют три типа EJB: сеанса, сущности и управляемые сообщением. EJB
сеанса — это компонент, который выполняет определенную задачу для кли-
ента. EJB сущности представляет сущность в базе данных или в другом посто-
янном хранилище. EJB, управляемые сообщением, появились в EJB 2.0. Они
используются в качестве приемника API службы сообщений Java, которая де-
лает возможной асинхронную обработку сообщений.
Создание EJB
С целью обоснования концепции приступим к созданию приложения. Все
примеры в этой книге выполняются на сервере приложений JBoss; поэтому
680
Глава 28
(^Примечанием Чтобы освоить EJB, необходимо знать эти поддерживающие
технологии.
Эта глава служит в качестве введения в EJB. Обсуждение начинается с опре-
деления EJB и знакомства с некоторыми преимуществами EJB, большинство
из которых недоступно в сервлетах/JSP. Затем рассматриваются архитектура и
определенные роли в приложении EJB и в жизненном цикле развертывания.
Дается пример приложения и изучаются некоторые технические приемы. Опи-
сывается пакетjavax.ejb. И, наконец, предлагаются два клиентских приложения
для тестирования примера приложения.
В этой главе слово Bean эквивалентно EJB. Не путайте их с Bean, которые
используются в приложениях JSP и являются совершенно другой вещью.
Введение в EJB
EJB является серверным компонентом. Как и любой другой компонент, EJB
инкапсулирует бизнес-логику. Однако EJB должны соответствовать специфи-
кациям, и они разворачиваются и могут выполняться только в контейнере EJB,
подобно тому, как сервлеты выполняются внутри контейнера сервлетов.
Контейнер сервлетов предоставляет сервлетам службы, такие как управле-
ние сеансом и безопасность. Аналогично, контейнер EJB предоставляет службы
системного уровня приложениям EJB. Фактически именно контейнер EJB
обеспечивает основные достоинства EJB. Несмотря на схожесть названий, EJB
имеют мало общего с JavaBeans.
Преимущества EJB
Приложения EJB гораздо сложнее создавать и администрировать по срав-
нению с приложениями сервлетов/JSP. Изучение EJB — непростая задача.
Почему же EJB так популярны, и почему так много организаций готовы
инвестировать их? Дело в том, что контейнер EJB предоставляет ряд инте-
ресных возможностей.
К преимуществам EJB относится следующее:
• Приложения EJB легко разрабатывать, так как разработчик приложения
может сконцентрироваться на бизнес-логике. В то же время разработ-
чик использует службы, предоставляемые контейнером EJB, такие как
транзакции и создание пулов соединений. Самым трудным является
процесс изучения.
• EJB являются компонентами. Велика вероятность того, что имеются
поставщики EJB, которые продают компоненты, инкапсулирующие
необходимую функциональность. Покупая EJB независимых поставщи-
Enterprise JavaBeans
685
import javax.ej b.CreateException;
import javax.ejb.EJBHome;
public interface AdderHome extends EJBHome {
Adder create() throws RemoteException, CreateException;
}
Л истин г 28.2 Интерфейс Adder
package com.brainysoftware.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Adder extends EJBObject {
public int add(int a, int b) throws RemoteException;
Листинг 28.3 Класс AdderBean
package com.brainysoftware.ejb;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.Sessioncontext;
public class AdderBean implements SessionBean {
public int add(int a, int b) {
System.out.println(”from AdderBean");
return (a + b);
}
public void ejbCreateO {
}
public void ejbRemoveO {
}
public void ejbActivateO {
}
public void ejbPassivateO {
}
public void setSessionContext(SessionContext sc) {
}
682
Глава 28
Шесть ролей EJB
Шестью различными ролями жизненного цикла разработки и развертывания
приложения EJB являются следующие:
• Разработчик EJB
• Составитель приложения
• Настройщик
•Системный администратор
• Поставщик сервера EJB
• Поставщик контейнера EJB
Каждая роль может выполняться различными индивидуумами или орга-
низациями. Однако часто индивидуум играет более одной роли. Например,
обычно нет четкого разделения между сервером EJB и контейнером EJB, т. е.
они поставляются в одном пакете.
Рассмотрим эти роли.
Разработчик EJB
Разработчик EJB — это программист, который разрабатывает EJB. Разра-
ботчик EJB должен знать бизнес-логику приложения.
Составитель приложения
Приложение EJB обычно состоит из нескольких EJB. В случае больших
приложений создавать EJB могут несколько разработчиков EJB. Составитель
приложения отвечает за объединение всех EJB, написанных разработчиками
EJB, в одном приложении. Составитель приложения создает также дескрип-
тор развертывания.
Настройщик
Настройщик отвечает за развертывание приложения EJB в определенном
контейнере EJB или в контейнерах EJB, если используется более одного кон-
тейнера EJB. Настройщик получает от составителя приложения различные EJB
и дескриптор развертывания. Это человек должен хорошо знать используе-
мый контейнер EJB. Мы познакомимся с этой ролью во время разработки и
развертывания приложения EJB.
Enterprise JavaBeans
687
[Auto deploy] Starting
[Auto deploy] Auto deploy of file:/C:/jboss/deploy/adder. jar
• •
[J2EE Deployer] Create application adder.jar
[J2EE Deployer] Installing EJB package: adder.jar
[J2EE Deployer] Starting module adder.jar
[J2EE Deployer] J2EE application: file:/C:/jboss/deploy/adder.jar is
deployed.
Вот и все. Приложение EJB успешно развернуто.
Для тестирования EJB требуется создать клиентское приложение. Однако
прежде необходимо посмотреть, как работает EJB.
Работа EJB
Как было показано выше, EJВ состоит не из одного класса. Фактически все-
гда класс EJB сопровождают два интерфейса. Эти домашний интерфейс и
удаленный интерфейс. Они служат для реализации коммуникаций между
клиентом и EJB. EJB содержит реализацию бизнес-правил. Конечно, EJB
может иметь и другие классы. Два интерфейса и один класс реализации —
это минимум.
Домашний интерфейс и удаленный интерфейс являются «дверью» в EJB
для клиентского приложения.
Спецификация EJB утверждает, что EJB и поддерживающие его компоненты
пишутся, реализуя или расширяя члены пакеты javax.ejb.
Домашний интерфейс
Домашний интерфейс выполняет такие операции жизненного цикла, как
создание, поиск и удаление EJB. Домашний интерфейс должен расширять
интерфейс EJBHome пакета javax.ejb. Интерфейс javax.ejb.EJBHome задается
следующим образом:
package javax.ejb;
public interface EJBHome extends java.rmi.Remote {
EJBMetaData getEJBMetaDataO
throws java.rmi.RemoteException;
HomeHandle getHomeHandleO
throws java.rmi.RemoteException;
void remove(Handle handle)
throws java.rmi.RemoteException, javax.ejb.RemoveException;
684
Глава 28
требуется, чтобы был установлен JBoss. Подробные инструкции по установке
JBoss можно найти в приложении Е
По завершении работы над приложением необходимо создать клиентское
приложение для вызова EJB.
(Теория написания клиентского приложения представлена в разделе «Со-
здание клиентских приложений».)
Разработка и развертывание приложения EJ В требует следующих действий:
1. Создание EJB.
2. Создание дескриптора развертывания.
3. Создания файла развертывания.
4. Развертывание EJB.
5. Создание клиентского приложения для тестирования EJB.
Создадим приложение EJB, состоящее из одного EJB, который может вы-
полнять сложение двух целых чисел. После компиляции EJB напишем деск-
риптор развертывания и разместим EJB. Затем будет создан сервлет, который
служит в качестве клиента приложения EJB.
Создание и компиляция EJB Adder
Необходимо написать три файла Java, все они являются частью пакета
com.brainysoftware.ejb. Прежде всего нужно создать соответствующую струк-
туру каталогов. На рис. 28.3 показана структура каталогов приложения EJB.
Рис. 28.3 Структура каталогов приложения EJB
В каталоге com/brainysoftware/ejb создаются три файла Java: AdderHome.java,
Adder.java, Adder Bean .java. AdderHome и Adder — это домашний и удаленный
интерфейсы для EJB (см. ниже). В листингах 28.1 — 28.3 приводится код этих
файлов.
Листинг 28.1 Интерфейс AdderHome
package com.brainysoftware.ejb;
import java.rmi.RemoteException;
Enterprise JavaBeans
689
public abstract Object getPrimaryKeyO
throws java.rmi.RemoteException;
public abstract void remove()
throws java.rmi.RemoteException, javax.ejb.RemoveException
public abstract Handle getHandleO
throws java.rmi.RemoteException;
public abstract boolean is!dentical(EJBObject obj)
throws java.rmi.RemoteException;
}
Подобно интерфейсу]ауах.е]Ь.Е1ВНотпе, этот интерфейс расширяет интерфейс
java.rmi. Remote.
В нашем примере удаленный интерфейс называется Adder (см. листинг 28.2).
Как можно видеть, он повторяет метод add, находящийся в реализации EJB
(см. листинг 28.3), используя следующий код:
import javax.ejb.EJBObject;
import java. rmi.RemoteException;
public interface Adder extends EJBObject {
public int add(int a, int b) throws RemoteException;
}
ГПримечаниеМ Экземпляр удаленного интерфейса часто называется
1 М|' ~ объектом EJB.
Класс EJB
Этот класс предоставляет реализации методов жизненного цикла в домашнем
интерфейсе, а также бизнес-логику, определенную в удаленном интерфейсе.
Класс EJB является EJB сеанса, сущности или управляемым сообщения-
ми. Другими словами, EJB сеанса реализует интерфейс javax.ejb.SessionBean, а
EJB сущности реализует интерфейс javax.ejb. Entity Bean. Поэтому класс EJB
должен также предоставить реализацию методов обратного вызова контейне-
ра EJB, определенных в интерфейсе javax.ejb.SessionBean или в интерфейсе
javax.ejb. Entity Bean.
Жизненный цикл методов create и find в домашнем интерфейсе соответствует
методам ejbCreate, ejbPostCreate и ejbFind в классе EJB. Дополнительная инфор-
мация о классе EJB приведена в главах 18 и 19.
Дескриптор развертывания
Приложению EJB требуется дескриптор развертывания, который определя-
ет метаинформацию для контейнера EJB. Дескриптор развертывания является
файлом XML с именем ejb.xml и должен быть расположен в каталоге META-
IN F каталога приложения.
686
Глава 28
Создание дескриптора развертывания
Приложение EJB должно иметь дескриптор развертывания, который опи-
сывает каждый EJB в приложении. Файл дескриптора развертывания называется
ejb.xml. Дескриптор развертывания для примера приложения представлен в
листинге 28.4.
Листинг 28.4 Дескриптор развертывания
<?xml version="1.0” encoding=’’UTF-8”?>
<ejb-jar>
<description>Your first EJB application </description>
<display-name>Adder Application</display-name>
<enterprise-beans>
<session>
<ejb-name>Adder</ejb-name>
<home>com.brainysoftware.ejb.AdderHome</home>
<remote>com.brainysoftware.ejb.Adder</remote>
<ej b-class>com.brainysoftware.ej b.AdderBean</ej b-class>
<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>
Создание файла развертывания
После завершения разработки EJB необходимо упаковать все файлы клас-
сов в один файл .jar. Файлы классов задаются в структуре каталогов, показан-
ной на рис. 28.3. Каталог ejb содержит файлы: Adder.class, AdderHome.class и
AdderBean.class. Каталог МETA-INF содержит один файл: ejb-iar.xml, это дес-
криптор развертывания.
Выполните следующие действия, чтобы создать файл развертывания:
1. Перейдите в родительский каталог каталогов сот и META-INF.
2. При условии, что для jar.exe уже указан путь доступа, введите следующее:
jar cfv adder.jar com/brainysoftware/ejb/* META-INF/ejb-jar.xml
3. Будет создан файл jar с именем adder.jar.
Развертывание
Скопируйте файл adder.jar в каталог развертывания сервера JBoss. Перезапу-
стите JBoss. JBoss должен выдать несколько сообщений. В строках сообщений
найдите следующее:
Enterprise JavaBeans
691
JNDI (Java Naming and Directory Interface)
Доступ к EJB реализуется посредством JNDI. JNDI поставляется как часть
JDK 1.3 или более поздней версии. При использовании JDK 1.2 необходимо
загрузить JNDI и установить эту систему отдельно. JNDI предоставляет две
службы: службу имен и службу каталогов.
Служба имен является важным средством при программировании, так как
она ищет объект, связанный сданным именем. Примером популярной служ-
бы имен является система имен доменов Интернета (DNS), которая связывает
именадоменов (такие, как newriders.com) с 1 P-адресами. В контексте EJВ, служба
имен ищет EJB, если известно его имя.
Если клиентское приложение хочет вызвать EJB, оно должно указать имя
EJB. JNDI найдет EJB для клиента.
Служба каталогов является расширением службы имен. Служба каталогов
связывает имена с объектами и позволяет этим объектам иметь атрибуты, ко-
торые описывают объекты. С помощью этих атрибутов можно искать объект,
если его имя неизвестно. Примером службы каталогов является LDAP
(Lightweight Directory Access Protocol). Службы каталогов не используются для
доступа к EJB и поэтому не рассматриваются в этой книге.
JND1 делится на следующие пять пакетов:
•javax.naming
• javax.naming.directory
• javax.naming.event
• javax.naming.ldap
•javax.naming.spi
Знание пакета javax.naming является достаточным для доступа к EJB. При
написании клиентского приложения, которое использует службу EJB, необ-
ходимо изучить два члена пакета javax.naming: интерфейс Context и класс
Initial Context.
Интерфейс javax.naming.Context
Интерфейс Context представляет контекст имен, состоящий из множества
ассоциаций имя-объект. Эти ассоциации в терминах JNDI называются связы-
ваниями. Интерфейс Context содержит методы для проверки и модификации
связываний имя-объект.
Наиболее часто используемым методом интерфейса Context является lookup.
Он возвращает ссылку на объект с заданным именем. Существуют две перегру-
жаемые версии этого метода:
public Object lookup(javax.naming.Name name) throws
j avax.naming.NamingException
public Object lookup(String name) throws javax.naming.NamingException
688
Глава 28
void remove(Object primaryKey)
throws java.rmi.RemoteException, javax.ejb.RemoveException;
}
Интерфейс EJBHomeявляется производным от интерфейса java.rmi.Remote.
Это первый знак того, что EJB зависят от RML
Сам интерфейс java.rmi.Remote используется для указания интерфейсов,
методы которых могут вызываться с нелокальной виртуальной машины. Лю-
бой удаленный объект должен реализовать этот интерфейс непосредственно
или косвенно.
Домашний интерфейс может определять методы для создания, поиска и уда-
ления EJB. Методы create и find добавлены в определение домашнего интерфей-
са; методы remove наследуются от интерфейса javax.ejb.EJBHome. Например,
домашний интерфейс EJB Adder (интерфейс AdderHome), представленный в
листинге 28.1, определяет в своем теле метод create без аргументов:
import java. rmi.RemoteException;
import j avax.ej b.CreateException;
import javax.ejb.EJBHome;
public interface AdderHome extends EJBHome {
Adder create() throws RemoteException, CreateException;
}
Домашний интерфейс может определять несколько перегружаемых версий
методов create и find. Методы create и find могут порождать дополнительные
исключения, если потребуется. Имя метода поиска всегда начинается с «find».
Методы create и find не являются обязательными. Не всем EJB они необхо-
димы. Например, EJB сеанса может определить один или несколько методов
create, a EJB сущности может не определять их (см. главы 29 и 30).
ГПоимечание^ Экземпляр домашнего интерфейса иногда называют
........... J домашним объектом (home).
Удаленный интерфейс
Удаленный интерфейс дублирует все бизнес-методы, которые должны быть
доступны клиенту. Это происходит потому, что клиент не может обратиться к
EJB напрямую.
Удаленный интерфейс должен расширять интерфейс EJBObject в пакете
javax.ejb. Объект javax.ejb.EJBObject определяется следующим образом:
package javax.ejb;
public interface EJBObject extends java.rmi.Remote {
public abstract EJBHome getEJBHomeO
th rows j ava.rmi.RemoteException;
Enterprise JavaBeans
693
1. Создание объекта java.util. Properties.
2. Добавление необходимых свойств в объект java.util.Properties для созда-
ния начального контекста.
3. Создание объекта javax.naming.InitialContext.
4. Использование метода lookup интерфейса javax.naming.Context для по-
лучения ссылки на домашний объект EJB путем передачи имени EJB.
Это можно записать с помощью кода:
import java.util.Properties;
import javax.naming.*;
Properties properties = new PropertiesO;
properties.put(Context.INITIAL_CONTEXT_FACTORY,
”org. jnp. interfaces.NamingContextFactory");
properties.put(Context.PROVIDERJJRL, "localhost:1099”);
try {
Initialcontext jndiContext = new Initialcontext(properties);
// Получение ссылки на домашний интерфейс
Object ref = jndiContext.lookup(’’Adder");
}
catch (NamingException e) {
}
Создание экземпляра EJB
С помощью JNDI можно получить ссылку на домашний объект EJB. Следую-
щий шаг состоит в создании экземпляра EJB на сервере. Для этого используется
метод create домашнего интерфейса.
Однако ссылка на домашний объект EJB является объектом RMI типа
java.lang.Object. Чтобы вызвать метод create домашнего объекта, необходимо
сначала привести тип объектной ссылки ктипудомашнего интерфейса. В при-
мере EJB Adder код приведения выглядит следующим образом:
AdderHome home = (AddHome) ref;
Здесь ref является ссылкой, полученной при поиске имени в начальном кон-
тексте JNDI.
Для удовлетворения требованиям RMI-IIOP необходимо использовать дру-
гой метод приведения типа. Обычно применяется статический метод narrow класса
javax.rmi.PortableRemoteObject. Этот метод обеспечивает приведение объекта
удаленного или абстрактного интерфейса к желательному типу данных. Сиг-
натура метода выглядит следующим образом:
690
Глава 28
Дескриптор развертывания определяет:
• Имя EJB '
•Тип EJB
• Полностью квалифицированное имя домашнего интерфейса
• Полностью квалифицированное имя удаленного интерфейса
Дескриптор развертывания может определять более одного EJB.
Создание клиентского приложения
EJB является серверным компонентом, который находится на сервере, ожидая
вызова от клиентов. Создание клиента EJB оказывается иногда непростой задачей.
Однако без клиента невозможно протестировать EJB. Получение клиентом до-
ступа и вызов EJB являются важной частью приложения EJB.
Клиент EJB не вызывает методы класса EJB непосредственно. Фактически
клиент может видеть только домашний и удаленный интерфейсы EJB, как
показано на рис. 28.4.
Клиент
Рис. 28.4. Клиент может видеть только домашний и удаленный
интерфейсы EJB
Клиентские приложения обращаются к EJB посредством JNDI (Java Naming
and Directory Interface). Краткое описание JNDI приводится ниже.
В этом примере пишутся два типа клиентских приложений. Первое —
приложение Java, состоящее из одного простого класса с именем BeanClient.
Второй клиентское приложение является страницей JSP, которая демонст-
рирует использование EJB в сервлете и на странице JSP. Первый клиент по-
казывает, какая базовая функциональность должна быть реализована, чтобы
клиент мог вызвать EJB. Второй клиент важен потому, что разработчик Web
должен иметь доступ к приложению EJB из сервлета или страницы JSP.
Enterprise JavaBeans
695
Initialcontext jndiContext = new InitialContext(properties);
System.out.println("Got context");
// Получение ссылки на EJB
Object ref = jndiContext.lookup("Adder");
System.out.println("Got reference");
// Получение из this ссылки на домашний интерфейс EJB
AdderHome home = (AdderHome)
PortableRemoteObject.narrow (ref, AdderHome.class);
// Создание объекта Adder из интерфейса Home
Adder adder = home.create();
System.out.printin ("2 + 5 = " + adder.add(2, 5));
}
catch(Exception e) {
System.out.println(e.toString());
}
}
}
Предполагается, что клиентское приложение будет выполняться на том же
компьютере, что и контейнер EJB. Если оно будет выполняться на другом компь-
ютере, необходимо заменить «localhost: 1099» на URL службы имен на сервере.
При использовании JBossiuib выполнения класса клиента необходимо
включить следующие файлы в путь доступа к классам:
• client/ejb.jar
• client/jboss-client.jar
• client/jnp-client.jar
Каталоги lib и client располагаются в домашнем каталоге J Boss.
Вызов EJB на странице JSP
Вызов EJB в сервлете выполняется так же, как и его вызов из другого клиента.
Однако важно скопировать все необходимые библиотечные файлы в каталог
lib в Tomcat. Требуются следующие файлы из домашнего каталога J Boss:
• client/ejb.jar
• client/jboss-client.jar
• client/jnp-client.jar
Скопируйте Adder.jar в каталог lib каталога CATALIN А_НОМЕ. Либо мож-
но развернуть файлы классов в каталоге WEB-INF/classes/com/brainysoftware/
ejb/ каталога приложения. Нам понадобятся Adder.class и AdderHome.class.
692
Глава 28
Метод lookup используется для получения ссылки на домашний объект EJB.
Вторая перегружаемая версия метода lookup получает строку, представляющую
имя EJB, и возвращает ссылку на домашний объект EJB.
Метод lookup порождает объектjavax.naming.NamingException, если не уда-
ется выполнить разрешение имени.
Класс javax.naming.InitialContext
Операции с именами зависят от контекста. Класс InitialContext реализует
интерфейс Context и предоставляет начальный контекст для разрешения имен.
Необходимо определить ряд свойств для рабочей среды контекста. Например,
разрешение имен может быть ограничено авторизованными пользователями.
В этом случае нужно предоставить в качестве свойств имя пользователя и па-
роль. Обычно создается объект java. util. Properties, и его метод put применяется
для добавления пар ключ/значение, представляющих любые необходимые
имена свойств и их значения.
При доступе к EJB необходимо предоставить два свойства. Первое из них —
свойство среды java.naming.factory.initial. Значением этого свойства является
полностью квалифицированное имя класса, который будет использоваться для
создания начального контекста.
Второе свойство — java.naming.provider.url. Это свойство среды, значение
которого определяет конфигурационную информацию для используемого
поставщика службы.
Имена обоих свойств по соглашению хранятся в двух статических полях в
интерфейсе Context. Это поля context.INITIAL_CONTEXT_FACTORY и
Context.PROVIDER_URL соответственно.
Для доступа к EJB необходимо добавить эти два свойства в объект
java.util.Properties с помощью его метода put, как показано в следующем коде:
import java.util.Properties;
// Создание объекта java.util.Properties
Properties properties = new PropertiesO;
// Добавление двух свойств: java.naming.factory.initial и
// java.naming.provider.url
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org. jnp. interfaces.NamingContextFactory"); ,
properties.put(Context.PROVIDERJJRL, "localhost:1099");
Так как эти свойства необходимы при создании начального контекста,
объект Properties передается в конструктор класса InitialContext следующим
образом:
javax.naming.InitialContext jndiContext = new
javax.naming.InitialContext(properties);
Получение ссылки на EJB предполагает выполнение следующих действий:
29
EJB сеанса
этой главе представлен первый тип EJB, называемый EJB сеанса. Объяс-
няется, что такое EJB сеанса, и обсуждаются два типа EJB сеанса: с поддерж-
кой состояния и без поддержки состояния. После обсуждения API будет раз-
работан проект Tassie — простое приложение EJB, которое демонстрирует
использование EJB сеанса.
Определение EJB сеанса
EJB сеанса реализует бизнес-логику для своих клиентов. Например, EJB се-
анса может выполнять вычисления (см. главу 28), обрабатывать заказы, шиф-
ровать и дешифровать данные, устанавливать соединение с базой данных, ис-
кать данные в базе данных и т. д.
В приложении EJB, где клиентом является web-приложение, бизнес-логи-
ка может располагаться в самом web-приложении — в сервлетах или на стра-
ницах JSP. Применение EJB сеанса как компонентов, которые инкапсулиру-
ют бизнес-логику, делает компоненты повторно используемыми для клиентов
других типов и позволяет им пользоваться преимуществами, предлагаемыми
контейнерами EJB (см. главу 28).
EJB сеанса называются так потому, что они существуют только во время се-
анса использующего их клиента. EJB сеанса создается, когда клиенту требуется
его служба. Когда клиент заканчивает работу с EJB и отсоединяется, EJ В сеанса
может быть уничтожен контейнером EJB.
694
Глава 28
public static Object narrow(Object narrowFrom, Class narrowTo)
throws ClassCastException
Здесь narrowFrom — проверяемый объект, a narrowTo — желательный тип
данных. Если narrowFrom нельзя преобразовать в narrowTo, порождается объект
ClassCastException. Если narrowFrom можно преобразовать в narrowTo, метод
возвращает объект типа Object, который может быть приведен к желательно-
му типу данных. Код будет таким:
AdderHome home = (AdderHome)
PortableRemoteObject.narrow (ref, AdderHome.class);
Имея домашний объект, можно вызвать метод create домашнего интерфейса
для создания EJВ:
Adder adder = home.createO;
Затем можно вызвать любой объявленный метод EJB. В случае примера
Adder можно вызвать его метод add:
int I = adderr.add(2, 5)
Теперь посмотрим, как создается клиентское приложение.
Клиентское приложение Java
Ниже приводится класс BeanClient, который вызывает EJB сеанса Adder.
Код очень простой (см. листинг 28.5).
Листинг 28.5 Класс клиентского приложения
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import java.util.Properties;
import com. brainysoftware.ejb.Adder;
import com.brainysoftware.ejb.AdderHome;
public class BeanClient {
public static void main(String[] args) {
// Подготовка свойств для создания объекта Initialcontext
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER.URL, "localhost:1099");
try {
// Получение начального контекста
EJB сеанса
699
просто. Это связано с тем, что EJB сеанса с состоянием сохраняет специфи-
ческие данные клиента.
Предположим, что EJB сеанса с состоянием обслуживает клиента, и требу-
ется некоторое время, чтобы клиент снова вернулся к активной деятельности.
Если контейнер EJB обслуживает мало клиентов в данный момент, EJB сеанса
с состоянием может подождать, пока клиент вернется к активной деятельнос-
ти. Однако в загруженный день контейнер EJB может иметь больше клиен-
тов, чем число EJB сеанса с состоянием. EJB сеанса с состоянием, простаива-
ющий без дела в ожидании своего клиента, может быть вызван для
обслуживания другого клиента. Но как быть с диалоговым состоянием преды-
дущего клиента? Нельзя просто отбросить диалоговое состояние, так как кли-
ент может вернуться к активным действиям.
Контейнер EJ В решает эту проблему тем, что он вводит два процесса с име-
нами passivation и activation. В passivation диалоговое состояние EJB сеанса с
состоянием, ожидающего, когда клиент вернется к активным действиям, пе-
ресылается во вторичную память, например на жесткий диск.
Процесс activation загружает в EJB сеанса с состоянием диалоговое состоя-
ние клиента, возвращающегося для продолжения своего сеанса. Отметим, что
контейнер EJB не обязан использовать тот же самый экземпляр EJB сеанса с
состоянием, который обслуживал клиента ранее. Если диалоговое состояние
клиента можно загрузить без изменений, то любой экземпляр EJB способен
выполнить работу. Предыдущий экземпляр EJB сеанса с состоянием будет,
вероятно, занят обслуживанием другого клиента.
Сеанс EJB сеанса с состоянием
Контейнер EJB присваивает уникальный идентификатор каждому объекту
EJB сеанса с состоянием при создании EJB. Однако, в отличие от приложения
сервлета/JSP, идентификаторы сеансов в приложении EJB недоступны кли-
енту. Клиент не имеет возможности получить идентификатор сеанса. Клиент
может определить, что две ссылки на объект EJ В сеанса ссылаются на один и
тот же экземпляр, с помощью метода isldentical интерфейса javax.ejb. EJBObject.
Сигнатура этого метода:
public boolean isldentical(javax.ejb.EJBObject object)
throws java.rmi.RemoteException
Создание EJB сеанса
В главе 28 было показано, как написать EJB сеанса без состояния, развернуть
его и вызвать его из клиентского приложения. Рассмотрим этот процесс более
подробно.
696
Глава 28
Код страницы JSP представлен в листинге 28.6.
Листинг 28.6 Вызов EJB на странице JSP
< %@ page import="javax.naming.★"%>
< %@ page import="javax.rmi.PortableRemoteObject"%>
< %@ page import="java.util.Properties'^
< %@ page import="com.brainysoftware.ejb.Adder"%>
<%@ page import="com.brainysoftware.ejb.AdderHome"%>
<%
// Подготовка объекта Properties для создания
// начального контекста
Properties properties = new PropertiesO;
properties. put(Context. INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER_URL, "localhost:1099");
try {
// Получение начального контекста
InitialContext jndiContext = new InitialContext(properties);
System.out.println("Got context");
// Получение ссылки на EJB
Object ref = jndiContext.lookup("Adder");
System.out.println("Got reference");
// Получение из this ссылки на домашний интерфейс
AdderHome home = (AdderHome)
PortableRemoteObject.narrow (ref, AdderHome.class);
// Создание объекта Adder из интерфейса Home
Adder adder = home.create();
out.println ("2 + 5 = " + adder.add(2, 5));
}
catch(Exception e) {
System, out. println(e. toStringO);
}
%>
Заключение
В этой главе рассматривались технология EJB, преимущества использования
EJB, архитектура и шесть ролей жизненного цикла разработки и развертыва-
ния приложений EJB. Было показано, как написать и развернуть приложение
EJB. Мы изучили naKeTjavax.ejb и написали два клиентских приложения. Кроме
того, в главе дано краткое введение в протокол JNDI, который используется
клиентом для доступа к EJB.
EJB сеанса
701
Интерфейс javax.ejb.SessionBean
EJB сеанса должен расширять интерфейс javax.ejb.SessionBean. Этот интер-
фейс расширяет интерфейс javax.ejb. Enterprise Bean и предоставляет четыре
метода, которые вызываются контейнером EJB. Это методы:
• ejbActivate. Контейнер EJB вызывает этот метод, когда экземпляр EJB
сеанса извлекается из пула для обслуживания клиента, диалоговое со-
стояние которого было сохранено во вторичной памяти в предыдущем
процессе passivation. Необходимо написать код в этом методе, который
вновь получает ресурсы, освобожденные при passivation. Сигнатура это-
го метода:
public void ejbActivate() throws javax.ejb.EJBException,
java.rmi.RemoteException;
• ejbPassivate. Контейнер EJB вызывает этот метод, когда экземпляр EJB
сеанса деактивируется. Необходимо реализовать код, который освобож-
дает ресурсы, такие как соединение с базой данных. Этот метод имеет
сигнатуру:
public void ejbPassivate() throws javax.ejb.EJBException,
java.rmi.RemoteException;
• ejbRemove. Этот метод вызывается, когда клиент вызывает метод remove
или когда контейнер EJВ решает закончить работу с объектом сеанса по
причине завершения времени ожидания. Сигнатура этого метода:
public void ejbRemove() throws javax.ejb.EJBException,
java.rmi.RemoteException;
• setSessionContext. Контейнер EJB вызывает этот метод после создания
экземпляра, передавая объект javax.ejb.SessionContext. Если необходимо
использовать объект SessionContext на последующем этапе, необходимо
присвоить его объектной переменной уровня класса EJB. Сигнатура этого
метода:
public void setSessionContext(SessionContext context) throws
javax.ejb. EJBException, java. rmi. RemoteException;
Пример EJB сеанса
EJB сеанса приводится в листинге 29.1. Так как он реализует интерфейс
javax.ejb.SessionBean, необходимо предоставить реализацию для всех методов
этого интерфейса, а также для методов, которые реализуют бизнес-логику.
698
Глава 29
Поскольку создание объекта является дорогой операцией, EJB сеанса мо-
жет в действительности не разрушаться контейнером EJB после того, как EJB
обслужит клиента. Вместо этого контейнер может поместить EJB в пул, чтобы
сделать его быстро доступным для другого клиента, которому он потребуется
позже. Однако это не является проблемой разработчика клиентского прило-
жения; жизненным циклом EJB сеанса управляет контейнер EJB.
Предупреждение! Аналогично другим EJB, метод EJB сеанса не создается
и не вызывается клиентом непосредственно. Клиент
общается с EJB сеанса через домашний и удаленный
интерфейсы EJB сеанса.
EJB сеанса не сохраняются в постоянном хранилище,
таком как диск или база данных. Если сервер
отказывает, EJB сеанса теряется, как и другие данные,
находящиеся во временной памяти.
EJB сеанса с поддержкой и без поддержки
состояния
Мы будем работать с двумя типами EJ В сеанса: поддерживающими состояние
и не поддерживающими состояния. Экземпляр EJ В сеанса, имеющий состоя-
ние, связан с одним клиентом. Контейнер EJB всегда использует один и тот
же экземпляр EJB сеанса с состоянием для обслуживания данного клиента.
EJB сеанса с состоянием сохраняет данные, связанные с клиентом: эти дан-
ные извлекаются из базы данных или вводятся клиентом. Говорят, что экземп-
ляр EJB сеанса с состоянием сохраняет диалоговое состояние клиента. Сеанс
клиента заканчивается, когда клиент указывает, что ему больше не требуется
EJB с состоянием, или когда проходит определенный период времени с мо-
мента последнего вызова метода клиентом. EJB с состоянием аналогичен
объекту javax.servlet.http.HttpSession в приложении сервлетов/JSP.
Объект сеанса без состояния не зависит от клиента. Этот тип EJB не хранит
диалоговое состояние клиента. Контейнер EJB может делегировать вызов ме-
тода клиентом любому доступному в пуле EJB сеанса без состояния.
Деактивация и активация EJB сеанса с
состоянием
Контейнер EJB поддерживает пул EJB с состоянием и без состояния. Это
связано с тем, что создание объекта — дорогая операция, а пул обеспечивает
повторное использование EJB сеанса.
Контейнер EJB может легко поместить EJB сеанса без состояния в пул и
получить его обратно. А вот помещение в пул EJB сеанса с состоянием не так
EJB сеанса
703
Определение интерфейса javax.ejb.SessionContext имеет следующий вид:
public interface javax.ejb.SessionContext extends javax.ejb.EJBContext {
public javax.ejb.EJBLocalObject getEJBLocalObjectO throws
IllegalStateException;
public javax.ejb.EJBObject getEJB0bject() throws IllegalStateException;
}
Использование EJB сеанса с состоянием в
сервлете/странице JSP
Если EJB сеанса с состоянием применяется клиентом не из Web, у него
не должно быть проблем с сохранением ссылки на EJB в течение сеанса.
Web-клиент — это другой случай, так как HTTP является протоколом без
состояния, что затрудняет поддержание сеанса. Для сохранения сеанса в
приложении EJB из сервлета/страницы JSP удобно использовать объект
javax.servlet.http.HttpSession. Можно сохранять сеанс для EJB сеанса с состоя-
нием, следуя двум правилам:
1. При первом получении ссылки на EJB сеанса с состоянием сохраните ее
как атрибут в объекте javax.servlet.http. HttpSession:
HttpSession session = request.getSession(true);
session.setAttribute("cart", cartBean);
Этот код получает объект HttpSession из объекта request и затем поме-
щает объект cartBean в атрибут объекта session.
('Примечание'} Прн создании кода страницы JSP первая строка
1,1 - приведенного выше кода не нужна, так как session является
неявным объектом JSP.
2. При последующих обращениях к объекту EJB сеанса объектную ссылку
получают из объекта HttpSession:
HttpSession session = request.getSession(true);
Cart cartBean = (Cart) session.getAttribute("cart");
Эти фрагменты кода будут использованы в примере приложения.
Дескриптор развертывания
Каждое приложение EJ В должно и меть дескриптор развертывания, который
представляет собой документ XML с именем ejb-jar.xml. Корневым элементом
700
Глава 29
Чтобы EJB сеанса функционировал, необходимо написать как минимум
три класса:
• Домашний интерфейс
• Удаленный интерфейс
• Класс EJB сеанса
Экземпляр домашнего интерфейса называется домашним объектом (home),
а экземпляр удаленного интерфейса называется удаленным объектом (remote).
Причина, почему нужны домашний и удаленный интерфейсы для каждого
EJB сеанса, заключается в том, что клиент напрямую не создает EJB и не вы-
зывает метод EJB сеанса.
Домашний объект требуется клиенту для создания экземпляра EJB сеан-
са. Когда клиентское приложение EJB создает экземпляр EJB сеанса, вызы-
вая один из методов create домашнего объекта, оно обращается к процедуре,
в которой контейнер EJB выделяет клиенту экземпляр запрошенного EJB
сеанса. Будет ли контейнер EJB действительно создавать новый экземпляр
EJB сеанса или возьмет один из пула, не является проблемой клиентского
приложения.
Удаленный объект создается, когда клиентское приложение EJB вызывает
метод create домашнего объекта. Удаленный объект необходим потому, что он
является удаленным представлением EJB сеанса. Напомним, что клиентское
приложение не имеет прямого доступа к EJB сеанса. После получения уда-
ленного объекта можно вызывать любой метод, предлагаемый EJB сеанса.
Следовательно, удаленный интерфейс обязан клонировать все методы в EJB
сеанса, которые должны быть доступны клиенту EJB.
Клиентское представление о EJB сеанса, развернутом в контейнере EJB, пока-
зано на рис. 29.1.
Клиент
Рис. 29.1. Клиентское представление EJB сеанса
EJB сеанса
705
• security-role-ref. Объявление ссылки на роль системы безопасности в коде
EJB.
• security-identity. Определяет, будут ли для выполнения методов EJB ис-
пользоваться идентификационные данные системы безопасности вы-
зывающей стороны или специальные идентификационные данные run-
as (выполнить-как).
• resource-ref. Объявление ссылки на Е.1Вдля внешнего ресурса.
• resource-env-ref. Объявление ссылки на EJB для администрируемого
объекта, связанного с ресурсом в среде EJB.
Приведем пример дескриптора развертывания:
<?xml version=’’1.0” encoding”UTF-8"?>
<ejb-jar>
<description>The Search Bean for Tassie Online Bookstore</description>
<display-name>Search Bean</display-name>
<enterprise-beans>
<session>
<ejb-name>Search</ejb-name>
<home>com.brainysoftware.tassie.ej b.SearchHome</home>
< remote>com.b rai nysoftwa re.tassie.ej b.Sea rch</remote>
<ejb-class>com.brainysoftware.tassie.ejb.SearchBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>
Пример сетевого книжного магазина Tassie
Следующее приложение называется Tassie Online Bookstore. Это неполное при-
ложение е-коммерции, единственным назначением которого является иллю-
страция использования EJB сеанса в приложении EJB, доступном как web-
приложение. Поскольку этот пример — лишь учебный инструмент,
поименяемый здесь код не обеспечивает эффективности и функциональнос-
ти истинного приложения е-коммерции.
В качестве пользовательского интерфейса в приложении применяются че-
тыре сервлета. Они представляют четыре страницы интерфейса пользователя:
Search, Book Details, Add To Cart и Check Shopping Cart. Диаграмма на рис. 29.2
поясняет отношения между этими страницами.
702
Глава 29
Листинг 29.1 EJB сеанса с именем MySessionBean
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class MySessionBean implements SessionBean {
public void ejbCreateO {
}
public void ejbRemoveO {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext sc) {
}
// здесь реализация методов
}
Интерфейсы javax.ejb.SessionContext и
javax.ejb.EJBContext
Как упоминалось выше, контейнер EJB передает javax.ejb.SessionContext в
метод setSessionContext интерфейса javax.ejb.SessionBean. Интерфейс
Session Context расширяет интерфейс javax.ejb.EJBContext, который предостав-
ляет методы для получения информации о вызывающем клиентском прило-
жении, домашнем объекте EJB, рабочей среде и т. д.
Определение интерфейса javax.ejb.EJBContext имеет следующий вид:
public interface javax.ejb.EJBObject extends java.rmi.Remote {
.public javax.ejb.EJBHome getEJBHomeO throws java. rmi.RemoteException;
public java. lang.Object getPrimaryKeyO throws java. rmi. RemoteException;
public void remove() throws java.rmi.RemoteException,
j avax.ej b.RemoteException;
public javax.ejb.Handle getHandleO throws java. rmi. RemoteException;
public boolean isldentical(javax.ejb.EJBObject object) throws
j ava.rmi.RemoteException;
}
EJB сеанса
707
Рис. 29.3. Начальная страница Search
Welcome То Tassie Online Bookstore
ТИс ГТ"
Search Renit
| Ше Anther №Ыиг .!
iJComputer Science: Pmciplei end Techniquei James Robinson i|Un»uper Production ,
!|АмешЫц^ Tour Own Computer Ray Martin '[Arnotts Publishing jDetrab '
Jfouch Ueing Apple Cotnpvten Ron Anderton ‘[LuckyPubfaheg [Details
ifComputer Repain Rocky Bleb i[Lucky РиЫиЫвд iiDetaib
‘(Computers and Internet Tracy Шее i[Abrakadabn _ 'jDetaib j
[(Computer Maintenance Tracy Шег ... ilPffgfe j
'[Tour Frrt Computer Andrew Burite [[Lucky Publishing -.tDetafc i
Рис. 29.4. Страница Search с результатами поиска
• Щелчок на ссылке на странице Add То Cart возвращает пользователя на
страницу Search, где пользователь может продолжить поиск других книг.
Кроме того, пользователь может проверить корзину покупок, щелкнув
мышью на ссылке внизу страницы Search. Он будет направлен на стра-
ницу Shopping Cart Details, которая показана на рис. 29.7.
704
Глава 29
дескриптора является <ejb-jar>, и он содержит узел с именем <enterprise-beans>.
Каждый EJB сеанса регистрируется в этом элементе, и называется элемент
<session>. Следовательно, для каждого EJB сеанса создается один элемент
<session>.
(Примечание^ OTD для дескриптора развертывания приложений EJB 2.0
4 z можно найти по адресу http://java.sun.com/dtd/ejb-
jar_2_0.dtd.
Элемент <session> имеет следующий синтаксис:
<! ELEMENT session (description?, display-name?, small-icon?,
large-icon?, ejb-name, home?, remote?, local-home?, local?, ejb-class,
session-type, transaction-type, env-entry*, ejb-ref*, ejb-local-ref*,
security-role-ref*, security-identity?, resource-ref*,
resource-env-ref*)>
Символ ? обозначает необязательный подэлемент, а * указывает, что подэ-
лемент может повторяться нуль или большее количество раз. Подэлементы
объясняются ниже:
• description. Описание EJB.
• display-name. Краткое имя, которое будет выводиться инструментами,
используемыми для редактирования этого дескриптора развертывания.
• small-icon. Имя небольшого (16x16) значка в формате .gif или .jpg. Имя
файла является относительным путем доступа в файле ejb-jar.
• large-icon. Имя большого (32x32) значка в формате .gif или .jpg. Имя фай-
ла является относительным путем доступа в файле ejb-jar.
• ejb-name. Имя, идентифицирующее EJB сеанса. Имя должно быть уни-
кальным в файле ejb-jar.
• home. Полностью квалифицированное имя домашнего интерфейса.
• remote. Полностью квалифицированное имя удаленного интерфейса.
• local-home. Полностью квалифицированное имя локального домашнего
интерфейса.
• local. Полностью квалифицированное имя локального интерфейса EJB.
• ejb-class. Полностью квалифицированное имя класса EJB.
• session-type. Тип EJ В сеанса. Его значением является Stateless или Stateful.
• transaction-type. Тип транзакции. Его значением будет Bean или Container.
• env-entry. Имя записи среды EJB.
• ejb-ref. Объявление ссылки на home EJB.
• ejb-local-ref. Объявление ссылки на local home EJB.
EJB сеанса
709
Рис. 29.7. Страница Shopping Cart Details
Структура базы данных упрощена. Отметим, что нет поля для указания
номера ISBN,книги. Кроме того, нет необходимости вводить категорию кни-
ги. В реальном приложении е-коммерции потребуется нормализовать табли-
цу, здесь это не делается.
Пример базы данных представлен в формате Microsoft Access, и его можно
найти на сайте www.lory-press.ru. Доступ к базе данных осуществляется с по-
мощью моста JDBC-ODBC. Необходимо создать имя источника данных (DSN,
Data Source Name) для этой базы данных, например TassieDB.
EJB
Пример приложения состоит из двух EJB сеанса без состояния и одного
EJB сеанса с состоянием.
Search — EJB сеанса без состояния
Первый EJB с именем Search ищет все книги, заглавие которых соответ-
ствует ключевому слову поиска. Домашний интерфейс этого EJB сеанса пред-
ставлен в листинге 29.2, а его удаленный интерфейс — в листинге 29.3.
Листинг 29.2 Домашний интерфейс EJB сеанса Search
package com. brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
706
Глава 29
Страница Search
Страница Book Details
Рис. 29.2. Четыре страницы приложения Tassie Online Bookstore
Функциональность проекта заключается в следующем:
• Пользователь Web может искать книгу по ее заглавию, вводя ключевое
слово на странице Search. Все книги, заглавие которых содержит клю-
чевое слово, перечисляются в таблице HTML. Для каждой книги вы-
водятся заглавие, автор и издатель. Кроме того, существует четвертый
элемент, называемый details, который предоставляет дополнительную
информацию о выбранной книге.
Страница Search показана на рис. 29.3 и 29.4. На рис. 29.3 приведен
начальный вид страницы — что видит пользователь, когда запрашивает
страницу.
Рис. 29.4 показывает ту же самую страницу Search после того, как
пользователь выполнил поиск, введя слово «computer» в строке поиска.
Отметим, что в каждой строке имеется ссылка на страницу Book Details.
• Пользователь может щелкнуть мышью на гиперссылке Details, чтобы
перейти на страницу Book Details. На этой странице пользователь мо-
жет ввести число покупаемых экземпляров книги. После этого пользо-
ватель нажимает кнопку Buy. Страница Book Details показана на рис.
29.5.
• После того как пользователь нажмет кнопку Buy на странице Book Detials,
приложение уведомит пользователя о том, что книга помещена в корзину
покупателя (см. рис. 29.6).
EJB сеанса
711
public ArrayList search(String keyword) {
ArrayList retval = new ArrayList(50);
try {
Statement statement = getConnection().createStatement();
String sql = ’’SELECT Id, Title, Author, Publisher" +
FROM Books” +
WHERE Title LIKE '%’’ + keyword + "%'";
ResultSet rs = statement.executeQuery(sql);
while (rs.nextO) {
String[] row = new String[4];
row[0] = rs.getString("Id");
row[1] = rs.getStringC’Title");
row[2] = rs.getString(’’Author");
row[3] = rs.getString("Publisher");
retval.add(row);
}
rs.closeO;
statement.close();
}
catch (Exception e) {
}
return retval;
}
public void ejbCreate() {
}
public void ejbRemoveO {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext sc) {
}
}_______________________________________________________________________
BookDetails — EJB сеанса без состояния
Второй EJ В сеанса также не имеет состояния, и называется он BookDetails.
Его задача состоит в возврате данных о книге, заданной id. Этот EJB вызыва-
ется из сервлета, а также из другого EJB, представляющего удаленного клиен-
та и локального клиента. В этом примере будет показано различие в коде для
удаленного клиента и локального клиента.
Домашний интерфейс EJB представлен в листинге 29.5, а его удаленный
интерфейс — в листинге 29.6.
708
Глава 29
Рис. 29.5. Страница Book Details
Рис. 29.6. Страница Add То Cart
База данных
База данных для этого примера предельно проста. Она состоит только из
таблицы с именем Books. Эта таблица имеет следующие поля:
• Id. Уникальный идентификатор книги.
• Title. Заглавие книги.
• Author. Автор книги.
• Publisher. Компания, опубликовавшая книгу.
• Price. Розничная цена книги.
EJB сеанса
713
catch (Exception e) {
}
return connection;
}
public String[] getBookOetails(String bookid) {
String[] row = null;
try {
Statement statement = getConnection().createStatementO;
String sql = "SELECT Id, Title, Author, Publisher, Price” +
" FROM Books" +
" WHERE Id=" + bookid; '
ResultSet rs = statement.executeQuery(sql);
if (rs.nextO) {
row = new String[5];
row[0] = rs.getString("Id");
row[1] = rs.getStringC’Title");
row[2] = rs.getString("Author");
row[3] = rs.getString("Publisher");
row[4] = rs.getString("Price");
}
rs.closeO;
statement.close();
>
catch (Exception e) {
}
return row;
}
public void ejbCreateO {
)
public void ejbRemoveO {
}
public void ejbActivateO {
)
public void ejbPassivateO {
}
public void setSessionContext(SessionContext sc) {
}
)
Cart — EJB сеанса с состоянием
EJB сеанса Cart имеет состояние, которое позволяет поддерживать корзи-
ну покупок для каждого пользователя. Можно добавлять элемент в EJB Cart и
извлекать ЕЗВдля проверки того, что находится в корзине.
Домашний интерфейс и удаленный интерфейс этого EJB представлены в
листингах 29.8 и 29.9 соответственно.
710
Глава 29
Листинг 29.2 Продолжение
public interface SearchHome extends EJBHome {
Search create() throws RemoteException, CreateException;
}
Л истинг 29.3 Удаленный интерфейс EJB сеанса Search
package com. brainysoftware.tassie.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.util.ArrayList;
public interface Search extends EJBObject {
public ArrayList search(String keyword) throws RemoteException;
}
Судя по коду удаленного интерфейса, клиенту предлагается только один
метод: search. Этот метод получает String, представляющую ключевое слово
поиска, и возвращает ArrayList, содержащий все книги, которые соответству-
ют строке поиска. Каждая книга в ArrayList представлена массивом String.
Класс EJB приведен в листинге 29.4.
Листинг 29.4 Класс EJB Search
package com.brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.sql.*;
import java.util.ArrayList;
public class SearchBean implements SessionBean {
private Connection getConnectionO {
Connection connection = null;
try {
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver”);
connection = DriverManager.getConnection(”jdbc:odbc:TassieDB”);
}
catch (Exception e) {
}
return connection;
EJB сеанса
715
public class CartBean implements SessionBean {
public void addToCart(String bookid, String quantity) {
String[] row = new String[6];
row[0] = bookid;
row[1] = quantity;
try {
InitialContext jndiContext = new InitialContextO;
Object ref = jndiContext.lookup("BookDetails");
System.out.println("Got ref to BookDetails’’);
// не требуется использовать javax.rmi.PortableRemoteObject,
// так как это локальный клиент
BookDetailsHome home = (BookDetailsHome) ref;
System.out.println("Got home to BookDetails");
BookDetails bookDetails = home.create();
System.out.println("Got BookDetails");
String^] details = bookDetails.getBookDetails(bookld);
row[2] = details[1];
row[3] = details[2J;
row[4] = details[3]; /
row[5] = details[4];
}
catch (Exception e) {
System.out.println(e.toSt ring());
}
cart.add(row);
}
public ArrayList getCartO {
return cart;
}
public void ejbCreateO {
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivateO {
}
public void setSessionContext(SessionContext sc) {
712
Глава 29
Листинг 29.5 Домашний интерфейс EJB BookDetails
package com.brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface BookDetailsHome extends EJBHome {
BookDetails createO throws RemoteException, CreateException;
}
Листинг 29.6 Удаленный интерфейс EJB BookDetails
package com.brainysoftware.tassie.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.util.ArrayList;
public interface BookDetails extends EJBObject {
public String[] getBookDetails(String bookid) throws RemoteException;
}
Здесь также клиенту предлагается только один метод: get Book Details. Этот
метод получает строку, представляющую идентификатор книги, и возвращает
массив строк, содержащий id книги, заголовок, автора, издателя и цену. Лис-
тинг 29.7 представляет реализацию класса EJB BookDetails.
Листинг 29.7 Класс BookDetailsBean
package com.brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.sql.*;
import java.util.ArrayList;
public class BookDetailsBean implements SessionBean {
private Connection getConnectionQ {
Connection connection = null;
try {
Class.fоrName("sun.jdbc.odbc.JdbcOdbcDriver");
connection = DriverManager.getConnection("jdbc:odbc:TassieDB");
}
EJB сеанса
717
Создание файла развертывания
После компиляции всех трех EJB сеанса и размещения дескриптора раз-
вертывания в каталоге МЕТА-INF можно создать файл развертывания для
приложения EJB.
Предполагается, что nporpaMMajar.exe доступна из любого каталога компью-
тера. Введите следующую команду для создания файла jar с именем tassie.jar:
jar cfv tassie.jar com/brainysoftware/tassie/ejb/* META-INF/ejb-jar.xml
Развертывание в JBoss
JBoss поддерживает так называемое горячее развертывание (hot deploy). Это
означает, что для развертывания приложения EJB нужно лишь скопировать
файл jar в каталог развертывания. Не требуется даже перезапускать J Boss, если
он уже работает.
Сервлеты
Четыре сервлета — SearchServIet, BookDetailsServlet, AddToCartServlet и
CheckCartServlet — в совокупности представляют клиентское приложение,
которое использует EJB сеанса в приложении Tassie Online Bookstore.
Сервлет SearchServIet
SearchServIet выводит страницу Search. Страница Search является основной
страницей. Это первая страница, которую видит пользователь. Ее можно раз-
делить на три части:
• Форма поиска
• Таблица результатов поиска
• Ссылка Check Shopping Cart (Проверка корзины покупок)
Код сервлета SearchServIet представлен в листинге 29.12.
Л исти н г 29.12 Сервлет SearchServIet
package com. brainysoftware.tassie.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax. rmi. PortableRemoteObject;
import com.brainysoftware.tassie.ejb.Search;
714
Глава 29
Листинг 29.8 Домашний интерфейс EJB Cart
package com.brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CartHome extends EJBHome {
Cart createO throws RemoteException, CreateException;
}
Листинг 29.9 Удаленный интерфейс EJB Cart
package com.brainysoftware.tassie.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.util.ArrayList;
public interface Cart extends EJBObject {
public ArrayList getCartO throws RemoteException;
public void addToCart(String bookid, String quantity) throws
RemoteException;
}
Метод addToCart получает два аргумента: идентификатор bookid книги,
выбранной пользователем, и число экземпляров, приобретаемых пользовате-
лем. Метод addToCart не возвращает никакого значения.
Метод getCart возвращает корзину покупок в форме ArrayList. Отметим, что
для получения текущей корзины покупок в метод не передается идентифика-
тор пользователя или другие маркеры, так как этот EJB сохраняет диалоговое
состояние для каждого пользователя.
Класс EJB представлен в листинге 29.10.
Листинг 29.10 Класс EJB для EJB Cart
package com.brainysoftware.tassie.ejb;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.Sessioncontext;
import java.sql.*;
import java.util.*;
import javax.naming.*;
EJB сеанса
719
out.println("<BR>’’);
out.println("<FORM METHOD=POST>");
out. println(’Title: <INPUT TYPE=TEXT NAME=keyword>");
out. println("<INPUT TYPE=SUBMIT VALUE=Search’’);
out.println("</FORM>");
out.println("<HR>'’);
// Результат поиска
out.println(’’<BR>’’);
out.println("<H3>Search Result</H3>");
out.println(’’<BR>’’);
displaySearchResult(out);
out. println(’’<HR>’’);
// Ссылка на Check Shopping Cart
out.println(’’<BR>’’);
out. println(’’<A
HREF=com.brainysoftware.tassie.servlet.CheckCartServlet>Check Shopping
Cart</A>’’);
// Завершение
out.println("</CENTER>”);
out.println(”</BODY>");
out.printing"</HTML>");
// Вывод формы поиска
}
/**0чистка ресурсов */
private void displaySearchResult(PrintWriter out) {
if (keyword!=null && ! keyword. trim().equals(’”’)) {
// Допустимое ключевое слово; выводим результат
Properties properties = new PropertiesO;
properties. put(Context. INITIAL_CONTEXT_FACTORY,
"org. jnp. interfaces. NamingContextFactory’’);
properties, put (Context. PROVIDER_URL, '’localhost: 1099’’);
try {
// Получение контекста имен
Initialcontext jndiContext = new InitialContext(properties);
// Получение ссылки на EJB
Object ref = jndiContext.lookup("Search");
// Получение из this ссылки на домашний интерфейс EJB
SearchHome home = (SearchHome)
PortableRemoteObject.narrow (ref, SearchHome.class);
// Создание объекта Adder из домашнего интерфейса
716
Глава 29
Дескриптор развертывания приложения EJB
Трем описанным выше EJB требуется дескриптор развертывания (см. лис-
тинг 29.11).
Листинг 29.11 Дескриптор развертывания для приложения EJB
<?xml version=”1.0” encoding="UTF-8"?>
<ejb-jar>
<description>The Search Bean for Tassie Online Bookstore</description>
<display-name>Search Bean</display-name>
<enterprise-beans>
<session>
< ej b-name>Sea rch</ej b-name>
<home>com.brainysoftware.tassie.ejb.SearchHome</home>
<remote>com.brainysoftware.tassie.ejb.Search</remote>
<ejb-class>com.brainysoftware.tassie.ejb.SearchBean</ejb-class>
<session-type>Stateless</session-type>
< t ransaction-type>Bean</t ransaction-type>
</session>
<session>
< ej b-name>BookDetails</ej b-name>
<home>com.brainysoftware.tassie.ej b.BookDetailsHome</home>
<remote>com.brainysoftware.tassie.ejb.BookDetails</remote>
<ejb-class>com.brainysoftware.tassie.ejb.BookDetailsBean</ejb-class>
<session-type>Stateless</session-type>
< t ransaction-type>Bean</t ransaction-type>
</session>
<session>
<ej b-name>Cart</ej b-name>
<home>com.brainysoftware.tassie.ejb.CartHome</home>
<remote>com.brainysoftware.tassie.ejb.Cart</remote>
<ejb-class>com.brainysoftware.tassie.ejb.CartBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Bean</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>
Отметим, что клиентское web-приложение имеет отдельное приложение
развертывания.
EJB сеанса
721
import com.brainysoftware.tassie.ejb.BookDetails;
import com.brainysoftware.tassie.ejb.BookDetailsHome;
public class BookDetailsServlet extends HttpServlet {
/★★Обработка запроса HTTP Get ★/
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
String bookid = request.getParameter("bookid");
response.setContentType("text/html”);
Printwriter out = response.getWriterO;
out.println("<HTML>”);
out.printin(”<HEAD>”);
out.println(”<TITLE>Book Details</TITLE>”);
out.println(”</HEAD>");
out.println("<BODY>");
out.p ri nt1n(”<CENTER>”);
Properties properties = new PropertiesO;
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER_URL, "localhost:1099");
try {
// Получение контекста имен
InitialContext jndiContext = new InitialContext(properties);
// Получение ссылки на EJB
Object ref = jndiContext.lookup("BookDetails");
// Получение из this ссылки на домашний интерфейс EJB
BookDetailsHome home = (BookDetailsHome)
PortableRemoteObject.narrow (ref, BookDetailsHome.class);
11 Создание объекта Adder из домашнего интерфейса
BookDetails bookDetailsBean = home.createO;
String[] row = bookDetailsBean.getBookDetails(bookld);
out.println("<H3>Book Details</H3>");
out.println("<BR>");
out.println("<TABLE BORDER=O>");
out.println("<TR>");
out. println( "<TD><B>Title: </BX/TD>");
out. println("<TD>” + row[1] + "</TD>");
out.println("</TR>");
out.println("<TR>");
out.println("<TD><B>Author:</B></TD>");
out. println("<TD>" + row[2] + "</TD>");
718
Глава 29
Листинг 29.12 Продолжение
import com.brainysoftware.tassie.ejb.SearchHome;
public class SearchServIet extends HttpServlet {
private String keyword;
/★♦Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
displayPage(request, response);
}
/♦♦Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, lOException {
keyword = request.getParameter("keyword");
displayPage(request, response);
}
private void displayPage(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, lOException {
/* В дополнение к заголовку и завершению
на этой странице имеются:
1. Заглавие Welcome
2. Форма поиска
3. Таблица результатов поиска
4. Ссылка на страницу Check Shopping Cart
★ I
response.setContentType("text/html”);
PrintWriter out = response.getWriter();
// Заголовок
out.println("<HTML>");
out.printing"<HEAD>");
out. printing"<TITLE>Welcome to Tassie Online Bookstore</TITLE>");
out. println("</HEAD>");
out.println("<BODY>");
out.println("<CENTER>”);
// Заглавие Welcome
out.println("<H2>Welcome To Tassie Online Bookstore</H2>");
// Форма поиска
EJB сеанса
723
Л исти н г 29.14 Сервлет AddToCartServlet
package com.brainysoftware.tassie. servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax. rmi. PortableRemoteObject;
import com.brainysoftware.tassie.ejb.Cart;
import com.brainysoftware.tassie.ejb.CartHome;
public class AddToCartServlet extends HttpServlet {
/♦♦Обработка запроса HTTP Post */
public void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response. setContentType( "text/html'’);
Printwriter out = response.getWriter();
// Заголовок
out.println("<HTML>");
out.println("<HEAD>");
out.printing"<TITLE>Add to Cart</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.printing"<CENTER>");
String bookid = request.getParameter("bookid");
String quantity = request.getParameter("quantity”);
if (bookid!=null && quantity!=null &&
!bookid.trim().equals("”) && !quantity.trim().equals("")) {
try {
HttpSession session = request.getSession(true);
Cart cartBean = (Cart) session.getAttribute("cart");
if (cartBean==null) { // новый сеанс
Properties properties = new PropertiesO;
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory");
properties.put(Context.PROVIDERJJRL, "localhost:1099");
// Получение контекста имен
Initialcontext jndiContext = new InitialContext(properties);
// Получение ссылки на EJB
Object ref = jndiContext.lookup("Cart");
// Получение из this ссылки на домашний интерфейс EJB
720
Глава 29
Листинг 29.12 Продолжение
Search searchBean = home.create();
ArrayList arrayList = searchBean.search(keyword);
int rowCount = arrayList.size();
out. print1n(”<TABLE BORDER=1>");
out. println(”<TR>’');
out.println("<TH WIDTH=350>Title</TH>");
out.println("<TH WIDTH=150>Author</TH>");
out. println( "<TH WIDTH=150>Publisher</TH>”);
out.println(”<TH WIDTH=50> </TH>”);
out.println("</TR>”);
for (int i=0; i<rowCount; i++) {
String[] s = (String[J) arrayList.get(i);
out. println(’’<TR>");
out.println(”<TD>” + s[1] + ”</TD>”);
out.println(”<TD>" + s[2] + "</TD>");
out.println(’’<TD>” + s[3] + "</TD>”);
out. println(’’<TD><A>
HREF=com.brainysoftware.tassie.servlet.BookDetailsServlet?bookId=" +
s[0] + ">Details</Ax/TD>”);
out. println(’’</TR>’');
}
out.println("</TABLE>”);
}
catch(Exception e) {
System, out. println(e. toStringO);
}
}
}
}
Сервлет BookDetailsServlet
Сервлет BookDetailsServlet является вторым сервлетом клиентского прило-
жения. Его код представлен в листинге 29.13.
Л истинг 29.13 Сервлет BookDetailsServlet
package com.brainysoftware.tassie.servlet:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
EJB сеанса
725
public class CheckCartServlet extends HttpServlet {
/♦♦Обработка запроса HTTP Get */
public void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, lOException {
response. setContentType( ’'text/html");
PrintWriter out = response.getWriter();
// заголовок
out.println("<HTML>");
out.printing"<HEAD>") ;
out.println("<TITLE>Check Shopping Cart</TITLE>");
out.println("</HEAD>");
out.println("<B0DY>");
out.println("<CENTER>");
out.println("<H2>Your shopping cart details</H2>");
out.printing"<BR>");
out.println("<TABLE B0RDER=2>");
out.println("<TR>");
out.println("<TH>Number of Copies</TH>");
out.println("<TH>Title</TH>");
out.println("<TH>Author</TH>");
out.println("<TH>Publisher</TH>");
out.println("<TH>Price</TH>");
out.printing"</TR>");
HttpSession session = request.getSession(false);
if (session!=null) {
try {
Cart cartBean = (Cart) session.getAttribute("cart");
ArrayList arrayList = cartBean.getCartO;
int count = arrayList.size();
for (int i=0; Kcount; i++) {
out.println("</TR>");
String[] row = (String[]) arrayList.qet(i);
out.println(”<TD>” + row[1] + "</TD>");
out.println("<TD>" + row[2] + "</TD>");
out.printing"<TD>" + row[3] + "</TD>");
out.println("<TD>" + row[4] + "</TD>");
out. println("<TD>" + row[5] + "</TD>");
out.printing"</TR>");
}
}
catch(Exception e) {
System.out.println(e.toString());
}
}
out.println("</TABLE>");
out.println("<BR>");
722
Глава 29
Листинг 29.13 Продолжение
out.println("</TR>”);
out.println("<TR>”);
out. printing '’<TD><B>Publisher: </B></TD>");
out.println(”<TD>” + row[3] + '’</TD>’’);
out.println(”</TR>’’);
out.println(”<TR>");
out. printin ("<TDxB>Price: </Bx/TD>");
out. println(,,<TD>$” + row[4] + ”</TD>”);
out.println("</TR>");
out. println( ”</TABLE>");
out.println(”<BR>”);
out.println(”<BR>");
out. println(’’<HR>’’);
out.println("<BR>");
out.println("<BR>”);
out.println(”<B>Put this book in the shopping cart</B>");
out. println(’’<BR>’’);
out.println(”<FORM METHOD=POST
ACTI0N=com.brainysoftware.tassie.servlet.AddToCartServlet>");
out.println("<INPUT TYPE=HIDDEN Name=bookld VALUE=” + row[0] + ”>”);
out.println(”I want to purchase ”);
out.println(”<INPUT TYPE=TEXT SIZE=1 Name=quantity VALUE=1> copies
of this book”);
out.println(” ”);
out. println(”<INPUT TYPE=SUBMIT VALUE=Buy>”);
out.println("</FORM>");
out.println(”<BR>");
out.println(”<HR>");
}
catch(Exception e) {
System.out.println(e. toStringO);
}
out. println(”</CENTER>");
out.println("</BODY>”);
out.println(”</HTML>");
}
}
Сервлет AddToCartServlet
Сервлет AddToCartServlet вызывается после того, как пользователь нажмет
кнопку Buy в сервлете BookDetails. Код представлен в листинге 29.14.
30
EJB сущности
1 ом и мо EJB сеанса, описанного в предыдущей главе, существуют еще
два TwnaEJB: EJB сущности и EJB, управляемые сообщениями. В этой главе
мы изучим EJB сущности и напишем приложение EJB сущности. EJB, управ-
ляемые сообщениями, рассматриваются в главе 33.
Понятие о EJB сущности
EJB сущности является компонентом данных, который постоянно хранит дан-
ные во вторичной памяти, такой как база данных. Подобно другим классам
Java, EJB сущности имеет поля и методы. Он использует поля для хранения
данных и методы для выполнения операций над полями.
EJB сущности является представлением записей таблицы базы данных.
Вместо прямого манипулирования данными в базе данных, как это делается в
других приложениях, вы манипулируете EJB сущности, который представляет
часть данных.
Контейнер EJB выполняет синхронизацию и другие задачи по поддержанию
данных. EJB сущности существует до тех пор, пока существуют данные, кото-
рые он представляет; поэтому EJB сущности должен храниться недели, месяцы
или даже годы. Например, если EJ В сущности представляет банковский счет, то
велика вероятность того, что он будет храниться до тех пор, пока банковский
счет будет существовать в базе данных банка. Поскольку банковские счета обычно
724
Гпава 29
Листинг 29.14 Продолжение
CartHome home = (CartHome)
PortableRemoteObject.narrow (ref, CartHome.class);
// Создание объекта Adder из домашнего интерфейса
cartBean = home.create();
}
cartBean.addToCart(book!d, quantity);
session.setAttribute("cart", cartBean);
out.println("<B>The book has been added to the shopping cart</B>");
}
catch(Exception e) {
out. println(e. toStringO);
}
}
out.println("<BR>");
out.println("<BR>");
out.println("<A HREF=com.brainysoftware.tassie.servlet.SearchServlet>" +
"Go back to the Search page</A>");
out.println("</CENTER>");
out.println("</BODY>");
out.println("</HTML>");
}
}
Сервлет CheckCartServlet
Когда пользователь щелкает мышью на ссылке Check Shopping Cart на стра-
нице Search, вызывается сервлет CheckCartServlet. Этот сервлет извлекает кор-
зину пользователя и выводит ее содержимое. Код сервлета CheckCartServlet
представлен в листинге 29.15.
Л истин г 29.15 Сервлет CheckCartServlet
package com. brainysoftware.tassie.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
//import javax.naming.*;
//import javax.rmi.PortableRemoteObject;
import com.brainysoftware.tassie.ejb.Cart;
//import com.brainysoftware.tassie.ejb.CartHome;
EJB сущности
729
• Получение домашнего интерфейса для объекта сущности
• Удаление объекта сущности
• Получение первичного ключа объекта сущности
• Получение обработчика объекта сущности
(^Примечанием Объект сущности ссылается на экземпляр EJB сущности.
Домашний интерфейс
Клиент EJB сущности получает домашний объект, т. е. экземпляр домашнего
интерфейса, из удаленного объекта EJB сущности. Клиент использует домаш-
ний объект для выполнения следующих операций:
• Создание объекта сущности
• Удаление объекта сущности
• Поиск объекта сущности
• Получение интерфейса javax.ejb. EJ В Meta Data для EJB сущности
Для выполнения этих задач домашний интерфейс предоставляет клиенту
методы create, finder и remove.
Методы create
Домашний интерфейс может иметь нуль или больше методов create. При
наличии нескольких методов create домашний интерфейс способен предоста-
вить различные способы создания объекта сущности. Например, следующий
метод create домашнего интерфейса EJB сущности с именем Product дает
пользователю возможность создавать EJB сущности Product, передавая иден-
тификатор продукта, название продукта, описание и цену:
public interface ProductHome extends javax.ejb.EJBHome {
public Product create(int productld, String productName,
String description, double price)
throws java.rmi.RemoteException, javax.ejb.CreateException;
}
Метод create может порождать исключения
java. rmi. RemoteException и javax.ejb.CreateException.
726
Глава 29
Листинг 29.15 Продолжение
out.printin("<BR>");
out.println("<A HREF=com.brainysoftware.tassie.servlet.SearchServlet>" +
"Go back to the Search page</A>”);
out.println("</CENTER>");
out.println("</BODY>");
out.println("</HTML>");
}
}
Развертывание клиентского приложения
Чтобы развернуть клиентское приложение, необходимо откомпилировать
четыре представленных выше сервлета и создать дескриптор развертывания
(см. главу 16).
Заключение
В этой главе представлен EJB сеанса. Существуют два типа EJB сеанса: с
состоянием и без состояния. Было показано, как написать EJB сеанса и
дескриптор развертывания для приложения EJB.
В качестве примера приложения EJB, доступного из web-приложения, рас-
сматривалось приложение Tassie Online Bookstore. Вы узнали,как получить
доступ к EJB сеанса с состоянием и без состояния и поддерживать сеансы с
помощью объектов javax.servlet.http. HttpSession.
EJB сущности
731
Класс первичного ключа
Аналогично таблице базы данных, которая может иметь первичный ключ для
уникальной идентификации строк таблицы, EJB сущности также имеет пер-
вичный ключ. Тип класса первичного ключа EJB сущности должен быть од-
ним из типов, определенных в RM1-I1OP.
EJB сущности
Класс EJB сущности должен реализовывать интерфейс javax.ejb. Entity Bean.
Этот интерфейс содержит методы, которые определяют жизненный цикл EJB,
а также методы обратного вызова, используемые контейнером EJB.
Интерфейс javax.ejb. EntityBean
Этот интерфейс расширяет интерфейс javax.ejb. Enterprise Bean и предостав-
ляет методы обратного вызова, которые вызываются контейнером EJB. Ин-
терфейс EntityBean имеет следующие методы:
• ejbActivate. Контейнер EJB вызывает этот метод, когда экземпляр EJB
сущности извлекается из пула для ассоциации с определенным объек-
том сущности. Сигнатура этого метода:
public void ejbActivate() throws
javax.ejb.EJBException, java.rmi.RemoteException;
• ejb Passivate. Контейнер EJB вызывает этот метод, когда экземпляр EJB
сущности, ассоциированный со своим объектом сущности, должен быть
уничтожен. Этот метод имеет сигнатуру:
public void ejbPassivate() throws
javax.ejb.EJBException, java.rmi.RemoteException;
• ejbLoad. Этот метод вызывается контейнером EJB для синхронизации
состояния EJB сущности путем загрузки его состояния из хранимых дан-
ных. Метод ejbLoad имеет сигнатуру:
public void ejbLoadO throws javax.ejb.EJBException,
java.rmi.RemoteException;
• ejbStore. Вызывается контейнером EJB для синхронизации состояния EJB
сущности с хранимыми данными. Этот метод используется для записи
состояния в хранимые данные. Сигнатура этого метода:
public void ejbStoreO throws javax.ejb.EJBException,
java.rmi.RemoteException;
728
Глава 30
открываются и поддерживаются банком годами, то представляющий его EJB
сущности также будет сохраняться годами.
Зачем применяется такой объект, как EJB сущности, для хранения данных?
Почему бы не обращаться непосредственно к базе данных и не манипулировать
данными прямо в ней? Или почему бы не написать EJ В сеанса для доступа к дан-
ным, как это делалось в главе 29? Ответ на эти вопросы имеет две составляющие.
Первое, управление данными в форме объектов проще. Второе, контейнер EJB
предоставляет службы, которые облегчают обработку данных, содержащихся в
EJB сущности.
Чем отличаются EJ В сеанса и EJB сущности? Зачем нужны различные типы
EJB? Говоря коротко, EJB сеанса моделируют бизнес-процессы, a EJB сущнос-
ти представляют бизнес-данные. Контейнер EJ В знает, как синхронизировать
данные в EJB сущности с данными в базе данных. Кроме того, EJB сущности
устойчивы к отказу сервера.
Существуют два типа EJB сущности: EJB сущности, живучесть данных
которых обеспечивает EJB (BMP, bean-managed persistence), и EJB сущности,
живучесть данных которых обеспечивает контейнер EJB (CMP, container-
nmanagedpersistence). Каки вслучае EJBсеанса, для сопровождения EJBcyuj-
ности пишутся дополнительные файлы. Необходимые файлы:
• Удаленный интерфейс
•Домашний интерфейс
• Класс первичного ключа
• EJB сущности
•Дескриптор развертывания
Удаленный интерфейс
Удаленный интерфейс EJ В сущности позволяет клиенту получить доступ к
экземпляру EJB сущности. Удаленный интерфейс EJB сущности должен рас-
ширять интерфейс javax.ejb.EJBObject. В удаленном интерфейсе разработчик
EJB сущности определяет методы, которые могут вызываться клиентом EJB.
Например, удаленный интерфейс EJB сущности, представляющего продукт,
может иметь два метода — getPrice и getCategory:
public interface Product extends javax.ejb.EJBObject {
double getPrice(int productld) throws java.rmi.RemoteException;
int getCategory(int productld) throws java.rmi.RemoteException;
}
Метод getPrice возвращает цену продукта, а метод getCategory возвращает
идентификатор категории продукта. Клиент может использовать удаленный
интерфейс для выполнения следующих задач:
EJB сущности
733
С другой стороны, активация EJB сущности включает в себя получение ресур-
сов и извлечение данных из базы данных. Для загрузки данных из базы данных
контейнер EJB вызывает после активации метод ejbLoad EJB сущности.
Методы create
Каждый метод create домашнего интерфейса должен иметь соответствую-
щий метод create в классе EJB сущности. Соответствующий метод имеет те же
самые аргументы, что и метод create домашнего интерфейса; однако имена
методов create в EJB сущности содержат префикс ejb. Это означает, что в клас-
се EJ В сущности имеется нуль или больше методов ejbCreate, а в домашнем
интерфейсе имеется такое же количество соответствующих методов create.
Однако методы ejbCreate возвращают значение типа класса первичного
ключа. Кроме того, метод ejbCreate обязан быть открытым и не должен объяв-
ляться как final или static.
Например, EJB сущности с именем Product имеет следующий метод
ejbCreate:
public class ProductBean implements javax.ejb.EntityBean {
public String productld;
public String productName;
public String description;
public double price;
public String ejbCreate(int productld, String productName,
String description, double price)
throws java.rmi.RemoteException, javax.ejb.CreateException {
this.productld = Integer.toString(productld);
this.productName = productName;
this.description = description;
this.price = price;
return Integer.toString(productld);
}
}
Методы Finder
Класс EJB сущности должен содержать соответствующие методы finder для
всех методов finder в домашнем интерфейсе. Так же как и методы create, метод
finder в классе EJВ сущности имеет префикс ejb и такой же список аргументов,
что и соответствующий метод finder в домашнем интерфейсе.
730 Глава 30
Методы Finder
Поскольку EJB сущности представляет часть данных, одной из наиболее
используемых операций является поиск EJB сущности. Это делается с помо-
щью одного из методов finder. Отметим, что EJ В сеанса не имеют метода finder.
Домашний интерфейс должен определить метод findByPrimaryKey, позво-
ляющий клиенту искать объект сущности по первичному ключу. Домашний
интерфейс может иметь и другие методы finder. Например, домашний интер-
фейс EJВ сущности Product может также определить метод findByName или
findByPrice, чтобы пользователь мог искать объект сущности по его имени или
цене. Метод findByPrimaryKey всегда возвращает нуль или один EJB сущности,
удовлетворяющий критерию; другие методы finder могут возвращать несколько
значений.
В случае методов finder, способных возвращать несколько значений, воз-
вращаемые значения имеют тип java.util.Collection или java.util.Enumeration.
В качестве примера следующий домашний интерфейс EJB сущности с име-
нем Product определяет три метода finder:
public interface ProductHorae extends javax.ejb.EJBHome {
public Product findByPrimaryKey(String productld)
throws java.rmi.RemoteException, javax.ejb.FinderException;
public Enumeration findByProductName(String productName)
throws java. rmi.RemoteException, javax.ejb.FinderException;
public Collection findAllO
throws java. rmi.RemoteException, javax.ejb.FinderException;
Методы Remove
Метод remove используется для удаления объекта сущности. Домашний
интерфейс EJB сущности является производным от интерфейса
javax.ejb.EJBHome, который определяет два метода remove. Сигнатуры этих
методов:
void remove(javax.ejb.Handle handle)
throws java.rmi.RemoteException, javax.ejb.RemoveException;
void remove(Object primaryKey)
throws java. rmi.RemoteException, javax.ejb.RemoveException;
EJB сущности
735
Другие методы
EJB сущности предоставляет реализации методов, объявленных в удален-
ном интерфейсе. Эти методы в классе EJB сущности должны иметь те же са-
мые возвращаемый тип и список аргументов, что и соответствующий метод в
удаленном интерфейсе. Эти методы должны быть открытыми и не могут быть
static или final. Например, рассмотрим следующий удаленный интерфейс:
public interface Product extends javax.ejb.EJBObject {
double getPrice(int productld) throws java.rmi.RemoteException;
int getCategory(int productld) throws java.rmi.RemoteException;
}
EJB сущности имеет следующие методы:
public class ProductBean implements javax.ejb;EntityBean {
double getPrice(int productld)
throws java.rmi.RemoteException {
}
int getCategory(int productld)
throws java.rmi.RemoteException {
}
Интерфейс javax. ejb. Entity Context
Интерфейс EntityContext является производным от интерфейса
javax.ejb.EJBContext. Он определяет два метода: getEJBObject и getPrimaryKey.
Метод getEJBObject возвращает ссылку на объект EJB, ассоциированный с
экземпляром EJB сущности.
Сигнатура этого метода:
j avax.еj b.EJ BObject get EJ BO bject()
throws IllegalStateException
732
Глава 30
• ejbRemove. Вызывается контейнером EJB перед тем, как контейнер EJB
удалит объект сущности. Этот метод применяется, когда клиент вызы-
вает метод remove домашнего или удаленного интерфейса EJB сущности.
Сигнатура метода:
public void ejbRemoveO throws javax.ejb.EJBException,
java.rmi.RemoteException;
• setEntityContext. Контейнер EJB вызывает этот метод после создания
экземпляра, устанавливая соответствующий контекст сущности. Сигна-
тура этого метода:
public void setEntityContext(EntityContext context) throws
javax.ejb.EJBException, java.rmi.RemoteException;
• unsetEntityContext. Контейнер EJB вызывает этот метод перед удалением
экземпляра EJB сущности. Этот метод сбрасывает соответствующий
контекст сущности. Сигнатура метода:
public void unsetEntityContext( ) throws javax.ejb.EJBException,
java.rmi.RemoteException;
Г Примечание^ Контейнер EJB поддерживает синхронизацию данных полей
- EJB сущности и базы данных с помощью двух методов
интерфейса javax.ejb. EntityBean: ejbLoad и ejbStore.
Благодаря синхронизации во время вызова методов ejbLoad
и ejbStore, если данные, с которыми ассоциирован EJB
сущности, изменяются непосредственно в базе данных, то
состояние EJB сущности также изменится. Данные в базе
данных могут изменяться другой системой, которая
манипулирует данными напрямую.
Активация и деактивация
Подобно EJB сеанса с состоянием, EJB сущности может подвергаться про-
цессам активации и деактивации. Однако для EJB сущности эти процессы
сложнее, чем для EJB сеанса с состоянием. Для EJB сеанса с состоянием акти-
вация включает в себя получение предыдущего состояния и ресурсов для дан-
ного клиента, а деактивация требует, чтобы EJB освободил используемые им
ресурсы и сохранил диалоговое состояние клиента во вторичной памяти.
Для EJB сущности деактивация предполагает освобождение ресурсов, а так-
же сохранение данных, которые он содержит, в базе данных до начала процес-
са деактивации. Чтобы сохранить состояние EJB сущности, контейнер EJB
вызывает метод ejbStore перед деактивацией.
EJB сущности
737
CREATE TABLE Products
(Productld int,
ProductName VarChar(50),
Description VarChar(IOO),
Price double
)
2. Получите драйвер JDBC для базы данных. Если используется mysql, то
драйвер JDBC для mysql можно найти в каталоге software/mmmysql на
сайте www.lory-press.ru. Файл называется mm.mysql-2.0.8-bin.jar.
Если требуется более современная версия, можно посетить
http://sourceforge.net/project/
showfiles. php?groupjd=15923&releaseJd=63046.
Во время подготовки книги самой последней версией была 2.0.8, и
она была упакована в файл с именем mm.mysql-2.0.8-you-must-unjar-
me.jar.
Файл jar необходимо распаковать с помощью следующей команды
(предполагается, 4Tojar.exe находится в пути доступа):
jar -xf mm.mysql-2.0.8-you-must-unjar-me.jar
Эта команда создает подкаталог с именем mm.mysql-2.0.8, в котором
имеется файл .jar с именем mm.mysql-2.0.8-bin.jar.
3. Сконфигурируйте драйвер JDBC в JBoss:
а. Скопируйте драйвер JDBC (файл mm.mysql-2.0.8-bin.jar, если исполь-
зуется mysql с драйвером, содержащимся Hawww.lory-press.ru) в ка-
талог JBOSSDist/lib/ext.
b. Откройте файл jboss.jcml в каталоге JBossDist/conf/default и найдите
следующие строки кода:
<mbean code="org. jboss. jdbc. JdbcProvider"
name="Def aultDomain: service=JdbcProvider’’>
<attribute name=”Drivers'’>
org.hsqldb.jdbcDriver
</attribute>
</mbean>
Затем добавьте «org.gjt.mm.mysql.Driver» (или другой используемый
драйвер) следующим образом:
<mbean code="org.jboss.jdbc.JdbcProvider"
name="DefaultDomain:service=JdbcProvider”>
attribute name="Drivers">
org.hsqldb.jdbcDriver,org.gjt.mm.mysql.Driver
</attribute>
</mbean>
734
Глава 30
9
Например, класс EJB сущности может содержать два метода finder —
ejbFindByPrimaryKey и ejbFindByName:
public ProductPK ejbFindByPrimaryKey( ProductPK primaryKey)
throws RemoteException, FinderException {
}
public Enumeration ejbFindByName(String name)
throws RemoteException, FinderException {
}
В этом примере метод ejbFindByPrimaryKey возвращает тип ProductPK, ко-
торый является классом первичного ключа (см. ниже).
Методы remove
Класс EJB сущности может также определить метод remove. Подобно ме-
тодам create и finder, имя метода remove имеет префикс ejb. Например, метод
remove для EJ В сущности Product:
public void ejbRemoveO
throws java.rmi.RemoteException, javax.ejb.RemoveException {
СПримечаниеМ сущности является представлением части данных базы
н 1данных. Поэтому вызов методов ejbCreate и ejbRemove
оказывает иное влияние на EJB сущности, нежели на EJB
сеанса. Когда контейнер EJB вызывает метод ejbCreate из
EJB сущности, он создает некоторые данные в базе данных,
которые соответствуют экземпляру EJB сущности в памяти.
При вызове метода ejbRemove из EJB сущности контейнер
EJB удаляет также данные, ассоциированные с EJB
сущности, из базы данных.
EJB сущности
739
Домашний интерфейс
Домашний интерфейс представлен в листинге 30.2. Он определяет метод
create, который имеет четыре аргумента, и два метода finder. Метод
findByPrimaryKey получает объект первичного ключа и возвращает удаленный
объект. Метод findByName получает название продукта и возвращает
Enumeration, содержащее все продукты, название которых соответствует ар-
гументу.
Листинг 30.2 Домашний интерфейс (ProductHome.java)
package com.brainysoftware.ejb;
import java. rmi.RemoteException;
import javax.ejb.FinderException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
import java.util.Enumeration;
public interface ProductHome extends EJBHome {
public Product create(int productld, String productName,
String description, double price)
throws RemoteException, CreateException;
public Product findByPrimaryKey(ProductPK key)
throws RemoteException, FinderException;
public Enumeration findByName(String name)
throws RemoteException, FinderException;
}
Класс первичного ключа
EJB сущности имеет первичный ключ. Классом первичного ключа для EJB
сущности Product является com.brainysoftware.ejb.ProductPK. Код класса пер-
вичного ключа представлен влистинге 30.3. Отметим, что он имеет открытое
поле с именем productld.
Листинг 30.3 ProductPK.java
package com. brainysoftware.ejb;
import java.io. Serializable;
736
Глава 30
Метод getPrimaryKey возвращает первичный ключ объекта сущности. Этот
метод имеет сигнатуру:
Object getPrimaryKey()
throws IllegalStateException
Два типа EJB сущности
EJ В сущности содержит данные, и в зависимости от того, как сохраняются его
данные, EJB сущности может быть одного из двух типов:
• EJB сущности, живучесть данных которых обеспечивает EJB (BMP)
• EJB сущности, живучесть данных которых обеспечивает контейнер
(BMP)
EJB сущности типа BMP сам заботится о сохранении данных. Для обеспе-
чения этого программист EJB может использовать команды SQL.
Сохранение данных в EJB сущности типа СМР обеспечивает контейнер
EJB. В дескрипторе развертывания приложения EJB контейнеру EJB даются
инструкции о том, как это выполнить. EJB сущности типа СМР легче про-
граммировать, так как не нужно писать код доступа к данным.
Создание EJB сущности типа BMP
EJB сущности типа BMP сам управляет своей живучестью. На базовом уровне
разработчик EJB должен предоставить в классе EJB сущности метод для со-
единения с базой данных. Альтернативно, если аналогичный метод доступен
в другом EJB, программист EJB сущности должен написать метод, который
возвращает объект java.sql.Connection, полученный из другого EJB.
Однако задача соединения с базой данных несколько облегчена в EJB сущ-
ности. Контейнер EJB загружает драйвер JDBC базы данных. Это означает,
что не требуется использовать метод Class.forName() для загрузки драйвера.
Надо лишь сообщить контейнеру EJB, как он может найти драйвер JDBC.
Каким образом это делается, зависит от используемого сервера приложений.
Рассмотрим способ, применяемый в JBoss.
Следующий пример демонстрирует, как можно написать EJB сущности типа
ВМРсименем Product, данные которого хранятся втаблице Products базы дан-
ных. Этот пример разработан с помощью базы данных mysql и использует сво-
бодно загружаемый драйвер JDBC.
Прежде всего необходимо выполнить следующие действия:
1. Создайте в базе данных таблицу Products, содержащую четыре столбца
(Productld, ProductName, Description и Price), с помощью следующего
оператора SQL:
EJB сущности
741
import javax.naming.Context;
import javax.naming.NamingException;
public class ProductBean implements EntityBean {
EntityContext context;
int productld; x
String productName;
String description;
double price;
public int getProductldO {
System, out.println("getProductId");
return productld;
}
public String getProductNameO {
System.out.println("getProductName");
return productName;
}
public String getDescription() {
System.out.println("getDescription");
return description;
}
public double getPrice() {
System.out.println("getPrice");
return price;
}
public ProductPK ejbCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
System.out.printIn("ej bCreate");
this.productld = productld;
this.productName = productName;
this.description = description;
this.price = price;
Connection con = null;
PreparedStatement ps = null;
try {
String sql = "INSERT INTO Products” +
" (Productld, ProductName, Description, Price)" +
" VALUES" +
’’ (?f ?)";
con = getConnection();
738
Гпава 30
4. Подготовьте структуру каталогов для проекта. Классы, которые будут здесь
написаны, являются частью пакета com.brainysoftware.ejb; поэтому необ-
ходимо иметь каталог с именем сот в рабочем каталоге, каталог с именем
brainysoftware в каталоге сот и каталог ejb в каталоге brainysoftware. Необ-
ходим также каталог МЕТА-IN Нерабочем каталоге для дескриптора раз-
вертывания. Структура каталогов показана на рис. 30.1.
Рис. 30.1. Структура каталогов для приложения
META-INF
С Примечание') Все Файлы .class должны помещаться в каталог сот/
brainysoftware/ejb.
Удаленный интерфейс
Первым рассмотрим удаленный интерфейс EJB сущности Product типа BMP.
Файл класса называется Product.java и представлен влистинге 30.1.
Л исти нг 30.1 Удаленный интерфейс (Product.java)
package com.brainysoftware.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
/* это удаленный интерфейс для Product ★/
public interface Product extends EJBObject {
public int getProductldO throws RemoteException;
public double getPrice() throws RemoteException;
public String getProductNameO throws RemoteException;
public String getDescription() throws RemoteException;
}
Удаленный интерфейс определяет четыре бизнес-метода: getProductld,
getPrice, get ProductName и get Description.
EJB сущности
743
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
rs = ps.executeQueryO;
if (rs.nextO) {
rs.closeO;
ps.closeO;
con.close();
return primaryKey;
}
}
catch (SQLException e) {
System, out. println(e. toStringO);
}
finally {
try {
if (rs!=null)
rs.closeO;
if (ps!=null)
ps.closeO;
if (con!=null)
con.closeO;
}
catch (SQLException e) {
}
}
throw new ObjectNotFoundExceptionO;
}
public Enumeration ejbFindByName(String name)
throws RemoteException, FinderException {
System.out.printIn("ej bFindByName”);
Vector products = new Vector();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT Productld " +
" FROM Products" +
" WHERE ProductName=?";
con = getConnectionO;
ps = con.prepareStatement(sql);
ps.setString(1, name);
rs = ps.executeQueryO;
740
Глава 30
Листинг 30.3 Продолжение
public class ProductPK implements Serializable {
public String productld;
public ProductPK(String productld) {
this.productld = productld;
}
public ProductPKO {
}
public boolean equals(Object o) {
return true;
public int hashCodeO {
return 0;
Г Примечание^ Методы equals и hasCode не имеют здесь реальной
1 реализации.
EJB сущности
Класс EJB сущности является наиболее важным классом. Он содержит ре-
ализации методов обратного вызова и бизнес-методов (см. листинг 30.4).
Л исти н г 30.4 Класс EJB сущности (ProductBeanJava)
package com. brainysoftware.ejb;
import java.sql.*;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Vector;
import java.rmi.RemoteException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.ObjectNotFoundException;
import javax.naming.Initialcontext;
EJB сущности
74S
try {
if (ps!=null)
ps. closeO;
if (con!=null)
con. closeO;
}
catch (SQLException e) {
}
}
}
public void ejbActivate() {
System.out.println("ej bActivate");
}
public void ejbPassivateO {
System.out.println("ej bPassivate");
}
public void ejbLoadO {
System.out.println("ej bLoad");
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT ProductName, Description, Price" +
" FROM Products" +
" WHERE Productld=?";.
con = getConnectionO;
ps = con.prepareStatement(sql);
ps.setlnt(1, this.productld);
rs = ps. executeQuery ().;
if (rs.nextO) {
this.productName = rs.getString(l);
this.description = rs.getString(2);
this.price = rs.getDouble(3);
}
}
catch (SQLException e) {
System.out.println(e.toString());
}
finally {
try {
742
Глава 30
Листинг 30.4 Продолжение
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
ps.setString(2, productName);
ps.setString(3, description);
ps.setDouble(4, price);
ps.executeUpdateO;
>
catch (SQLException e) {
System.out.println(e.toSt ring());
}
finally {
try {
if (ps!=null)
ps.closeO;
if (con!=null)
con.close();
}
catch (SQLException e) {
}
>
return new ProductPK(Integer.toString(productId));
>
public void ejbPostCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
System.out.println("ej bPostCreate”);
}
public ProductPK ejbFindByPrimaryKey(ProductPK primaryKey)
throws RemoteException, FinderException {
System, out. println("ejbFindByPrimaryKey");
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT ProductName" +
" FROM Products" +
” WHERE Productld=?";
int productld = Integer.parse!nt(primaryKey.productld);
con = getConnection();
EJB сущности
747
public void setEntityContext(EntityContext context) {
System.out.println("setEntityContext");
this.context = context;
}
public void unsetEntityContextO {
System.out.println("unsetEntityContext");
context = null;
}
public Connection getConnection() {
String dbUrl = null;
String userName = null;
String password = null;
Context initialcontext;
Context environment;
Connection connection = null;
try {
initialcontext = new InitialContextO;
environment = (Context) initialcontext.lookup("java:comp/env");
dbllrl = (String) environment.lookup("dbUrl");
userName = (String) environment.lookup("dbUserName");
password = (String) environment.lookup("dbPassword");
}
catch (NamingException e) {
System, out. println(e. toStringO);
}
try {
connection = DriverManager.getConnection(dbUrl, userName, password);
}
catch (SQLException e) {
System.out.println(e.toSt ring());
}
return connection;
}
}
Класс EJB сущности имеет четыре поля для хранения данных: productld,
productName, description и price. Кроме того, он содержит реализации четырех
бизнес-методов, объявленных в удаленном интерфейсе:
getProductld, getProductName, getDescription и getPrice.
public int getProductldO {
System.out.println("getProductld");
return productld;
}
744
Глава 30
Листинг 30.4 Продолжение
while (rs.nextO) {
int productld = rs.getlnt(l);
products.addElement(new ProductPK(Integer.toString(productld)));
}
}
catch (SQLException e) {
System.out.println(e.toString());
}
finally {
try {
if (rs!=null)
rs.closeO;
if (ps!=null)
ps. closeO;
if (con!=null)
con. closeO;
}
catch (SQLException e) {
}
}
return products.elementsO;
}
public void ejbRemoveO throws RemoteException {
System.out.println("ej bRemove”);
Connection con = null;
PreparedStatement ps = null;
try {
String sql = "DELETE FROM Products" +
" WHERE Productld=?";
ProductPK key = (ProductPK) context.getPrimaryKeyO;
int productld = Integer.parselnt(key.productld);
con = getConnectionO;
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
ps.executeUpdateO;
}
catch (SQLException e) {
System, out. println(e. toStringO);
}
finally {
EJB сущности
749
Затем можно просмотреть среду EJB, передав строку «java:comp/env» в ме-
тод lookup начального контекста:
environment = (Context) initialcontext.lookup("java:comp/env");
Используем объект environment для получения URL базы данных, имени
пользователя и пароля:
dbUrl = (String) environment.lookup("dbUrl");
userName = (String) environment.lookup("dbUserName");
password = (String) environment.lookup("dbPassword");
Эти значения передаются в метод getConnection объекта java.sql.DriverMa-
nager для получения объекта Connection:
connection = DriverManager.getConnection(dbUrl, userName, password);
Для каждого метода create EJ В сущности реализует один метод ejbCreate и один
метод ejb PostCreate.
Метод ejbCreate имеет то же множество аргументов, что и метод create до-
машнего интерфейса, и возвращает объект первичного ключа EJ В. Его сигнату-
ра задана следующим образом:
public ProductPK ejbCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
Метод ejbCreate выполняет две операции. Первое, он заполняет внутрен-
ние поля переданными ему значениями аргументов:
this.productld = productld;
this.productName = productName;
this.description = description;
this.price = price;
Затем метод вставляет запись в базу данных. Для получения объекта
Connection вызывается метод getConnection.
Connection con = null;
PreparedStatement ps = null;
try {
String sql = "INSERT INTO Products" +
" (Productld, ProductName, Description, Price)" +
"VALUES" +
746
Глава 30
Л исти н г 30.4 Продолжение
if (rs!=null)
rs.closeO;
if (ps!=null)
ps.closeO;
if (con!=null)
con.closeO;
}
catch (SQLException e) {
}
}
}
public void ejbStore() {
System.out.println("ej bStore”);
Connection con = null;
PreparedStatement ps = null;
try {
String sql = "UPDATE Products" +
" SET ProductName=?, Description=?, Price=?" +
" WHERE Productld=?";
ProductPK key = (ProductPK) context. getPrimaryKeyO;
int productld = Integer.parselnt(key.productld);
con = getConnection!);
ps = con. prepareStatement(sql);
ps.setString(1, this.productName);
ps.setString!2, this.description);
ps.setDouble(3, this.price);
ps.setlnt(4, productld);
ps.executeUpdate!);
}
catch (SQLException e) {
System.out.println(e.toString!));
}
finally {
try {
if (ps!=null)
ps.closeO;
if (con!=null)
con.closeO;
)
catch (SQLException e) {
}
)
}
EJB сущности
751
Идентификатор продукта получают из объекта первичного ключа:
int productld = Integer.parseInt(primaryKey.productld);
Затем метод ejbFindByPrimaryKey получает объект Connection, создает объект
PreparedStatement и вызывает метод executeQuery объекта PreparedStatement:
con = getConnection();
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
rs = ps.executeQueryO;
Метод executeQuery возвращает объект ResultSet. Метод next() объекта
ResultSet выдает true, если существует следующая запись, иначе он возвращает
false. Если метод next() возвращает true, метод ejbFindByPrimaryKey возвращает
объект первичного ключа:
if (rs.nextO) {
rs.closeO;
ps. closeO;
con. closeO;
return primaryKey;
1
Иначе метод порождает исключение ObjectNotFoundException:
throw new ObjectNotFoundException();
Другой метод поиска называется ejbFindByName. Он возвращает объект
Enumeration, содержащий все совпадающие продукты. Сначала создается эк-
земпляр объекта Vector с именем products:
Vector products = new VectorO;
Метод ejbFindByName открывает соединение для получения всех продук-
тов, имеющих указанное имя:
try {
String sql = "SELECT Productld " +
"FROM Products" +
" WHERE ProductName=?";
con = getConnectionO;
ps = con.prepareStatement(sql);
ps.setString(1, name);
rs = ps.executeQueryO;
748
Глава 30
public String getProductName() {
System. out. println( ’’getProductName”);
return productName;
}
public String getDescription() {
System.out.println("getDescription");
return description;
}
public double getPriceO {
System.out.println("getPrice”);
return price;
}
Важным методом, используемым несколькими другими методами в классе
EJB сущности, является метод getConnection, который возвращает действи-
тельный объект java.sql.Connection.
Как обычно, для соединения с базой данных необходимо передать URL базы
данных, имя пользователя и пароль в следующий метод getConnection класса
java.sql.DriverManager. Можно жестко закодировать эту информацию в классе
EJB сущности, однако это будет не слишком разумным решением. Имя и па-
роль пользователя изменяются довольно часто, и жесткое кодирование этой
информации в EJВ сущности означает, что придется перекомпилировать и по-
вторно разворачивать EJB всякий раз, когда она изменяется. Лучшим спосо-
бом является сохранение этой информации в дескрипторе развертывания и
получение значений с помощью JND1, как представлено в методе getConnection
в EJB сущности. Позже будет показано, как сохранить эту информацию в дес-
крипторе развертывания. Пока же предположим, что дескриптор развертыва-
ния содержит URL базы данных, имя пользователя и пароль для соединения с
базой данных.
В методе getConnection в EJB сущности сначала определяются три объект-
ные ссылки String и две объектные ссылки javax.naming.Context с именами
initialContext и environment:
String dbUrl = null;
String userName = null;
String password = null;
Context initialContext;
Context environment;
Переменная initialContext используется для ссылки на объект InitialContext,
получаемый с помощью следующего конструктора:
initialContext = new InitialContextO;
EJB сущности
753
this.productName = rs.getString(l);
this.description = rs.getString(2);
this.price = rs.getDouble(3);
}
Метод ejbStore копирует значения внутренних полей в базу данных:
String sql = "UPDATE Products" +
SET ProductName=?, Description=?, Price=?" +
" WHERE Productld=?";
ProductPK key = (ProductPK) context.getPrimaryKeyO;
int productld = Integer.parselnt(key.productld);
con = getConnection();
ps = con.prepareStatement(sql);
ps.setString(1, this.productName);
ps.setString(2, this.description);
ps.setDouble(3, this.price);
ps.setlnt(4, productld);
ps.executeUpdateO;
Дескриптор развертывания
Каждое приложение EJB должно сопровождаться дескриптором разверты-
вания, который хранится в каталоге МЕТА-INF проекта в файле с именем ejb-
jar.xml. Дескриптор развертывания для EJB сущности Product представлен в
листинге 30.5.
Листинг 30.5 Дескриптор развертывания (ejb-jar.xml)
<?xml version='’1.0" encoding=”UTF-8”?>
<ejb-jar>
<description>Your first EJB application </description>
<display-name>Products Application</display-name>
<enterprise-beans>
<entity>
<e j b-name>BMPProdiict</ej b-name>
<home>com.brainysoftware.ejb.ProductHome</home>
<remote>com.brainysoftware.ejb.Product</remote>
<ej b-class>com.brainysoftware.ejb.ProductBean</ejb-class>
<persistence-type>Bean</persistence-type>
<prim-key-class>com.brainysoftware.ejb.ProductPK</prim-key-class>
<reentrant>false</reentrant>
<env-entry>
750
Глава 30
" (?, ?. ?, ?)";
con = getConnection();
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
ps.setString(2, productName);
ps.setString(3, description);
ps.setDouble(4, price);
ps. execute(Jpdate();
}
catch (SQLException e) {
System.out.println(e. toString());
}
finally {
try {
if (ps!=null)
ps. closeO;
if (con!=null)
con. closeO;
>
catch (SQLException e) {
}
)
Наконец, метод возвращает новый объект ProductPK:
return new ProductPK(Integer.toString(productId));
Метод ejbPostCreate не возвращает значения, он лишь печатает свое имя на
консоли:
public void ejbPostCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
System.out.printIn("ej bPostCreate");
)
Класс EJB сущности предлагает два метода поиска: find By Primary Key и
findByName. Метод findByPrimaryKey получает объект первичного ключа и про-
веряет, что база данных содержит запись с этим ключом. Если соответствующая
запись найдена, возвращается тот же самый первичный ключ; иначе метод
findByPrimaryKey порождает исключение javax.ejb.ObjectNotFoundException.
Метод ejbFindByPrimaryKey начинается с составления оператора SQL, ис-
пользуемого для выбора записи с указанным идентификатором продукта:
String sql = "SELECT ProductName" +
"FROM Products" +
" WHERE Productld2?";
EJB сущности
755
Клиентское приложение
EJB сущности установлен, и теперь можно написать клиентское приложе-
ние для тестирования EJB. Клиентское приложение представлено в листинге
30.6. Проверьте, что сервер базы данных работает.
Л исти н г 30.6 Клиентское приложение
import javax.naming.*;
import javax. rmi. PortableRemoteObject;
import java.util.Properties;
import java.util.Enumeration;
import com.brainysoftware.ejb.Product;
import com.brainysoftware.ejb.ProductHome;
public class BeanClient {
public static void main(String[] args) {
// Подготовка свойств для создания объекта InitialContext
Properties properties = new PropertiesO;
properties. put(Context. INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory");
properties, put (Context. PROVIDERJJRL, ’’localhost: 1099”);
// Получение начального контекста
InitialContext jndiContext = new InitialContext(properties);
System.out.println(”Got context”);
/I Получение ссылки на EJB
Object ref = jndiContext:lookup(”BMPProduct”);
System.out.println("Got reference”);
// Получение из this ссылки на домашний интерфейс EJB
ProductHome home = (ProductHome)
PortableRemoteObject.narrow (ref, ProductHome.class);
// Создание объекта Interest из домашнего (Home) интерфейса
home.create(11, "Franklin Spring Water", "400ml", 2.25);
home.create(12, "Franklin Spring Water", "600ml", 3.25);
home.create(13, "Choco Bar", "Chocolate Bar 200g", 2.95);
home.create(14, "Timtim Biscuit", "Biscuit w. mint flavor, 300g", 9.25);
Product product = home.create(15, "Supermie", "Instant Noodle", 1.05);
product. removeO;
752
Глава 30
Получаемый ResultSet содержит все совпадающие продукты. Используя
цикл while, метод помещает каждую запись в Vector:
while (rs.nextO) {
int productld = rs.getlnt(l);
products.addElement(new ProductPK(Integer.toString(produetld)));
}
}
Затем он возвращает Enumeration для Vector:
return products. elementsO;
EJB сущности предоставляет также реализацию метода ejbRemove. При
вызове он удаляет соответствующую запись из базы данных:
PreparedStatement ps = null;
try {
String sql = "DELETE FROM Products” +
" WHERE Productld=?";
ProductPK key = (ProductPK) context.getPrimaryKeyO;
int productld = Integer.parselnt(key.productld);
con = getConnection();
ps = con.prepareStatement(sql);
ps.setlnt(1, productld);
ps.executeUpdate();
}
catch (SQLException e) {
System, out. println(w. toStringO);
}
Для синхронизации данных в EJB сущности и данных в базе данных необ-
ходимо предоставить реализации методов ejbStore и ejbLoad.
Метод ejbLoad копирует данные из базы данных в поля EJB сущности следу-
ющим образом:
String sql = "SELECT ProductName, Description, Price" +
" FROM Products" +
" WHERE Productld=?";
con = getConnectionO;
ps = con.prepareStatement(sql);
ps.setlnt(1, this.productld);
rs = ps.executeQueryO;
if(rs.nextO) {
EJB сущности
757
зовать по умолчанию встроенную базу данных или он может использовать
файлы. Для выбора того или иного способа можно сконфигурировать некото-
рые настройки на сервере приложений.
В JBoss, например, если ничего не определить, то контейнер EJB создаст
таблицу в применяемой по умолчанию базе данных. Однако можно использо-
вать также свой собственный сервер базы данных.
JBoss предоставляет следующие три метода поиска:
• findByPrimaryKey
• findAll
• 1пи1Ву<поле>(<тип-поля> значение)
Мы используем эти методы поиска в клиентском классе, сопровождающем
приложение EJB сущности.
Кроме того, можно определить пользовательский метод поиска (см. доку-
ментацию JBoss).
Следующий пример представляет EJB сущности типа СМ Р. Все файлы клас-
сов имеют префикс СМР, а типом класса первичного ключа является
java.Iang.String.
Удаленный интерфейс
Удаленный интерфейс EJB CMP Product похож на удаленный интерфейс
EJB Product типа BMP (см. листинг 30.1). Код удаленного интерфейса пред-
ставлен влистинге 30.7.
Листинг 30.7 Удаленный интерфейс (CMPProduct.java)
package com.brainysoftware.ejb;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
/* это удаленный интерфейс для Product */
public interface CMPProduct extends EJBObject {
public String getProductldO throws RemoteException;
public double getPriceO throws RemoteException;
public String getProductNameO throws RemoteException;
public String getDescription() throws RemoteException;
}
Домашний интерфейс
Домашний интерфейс определяет один метод create и три метода поиска.
Домашний интерфейс представлен влистинге 30.8.
754
Глава 30
Листинг 30.5 Продолжение
<env-ent ry-name>dbU rl</env-ent ry-name>
<env-ent ry-type>j ava.lang.St ring</env-ent ry-type>
<env-entry-value>jdbc:mysql://localhost/MyDB</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>dbllserName</env-entry-name>
<env-entry-type>java.lang.St ring</env-ent ry-type>
<env-ent ry-value>yena</env-ent ry-value>
</env-entry>
<env-entry>
<env-entry-name>dbPassword</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-ent ry-value>langO128934</env-ent ry-value>
</env-entry>
</entity>
</enterprise-beans>
•</ejb-jar>
Необходимо определить полностью квалифицированные имена для уда-
ленного интерфейса, домашнего интерфейса и EJВ сущности. Кроме того,
нужно сообщить контейнеру EJB, какой тип сохранения использует EJB сущ-
ности. В данном случае элемент <persistence-type> имеет значение «Веап».
Существуют также три элемента <env-entry>, каждый из которых опреде-
ляет переменную окружения. Эти значения получает метод getConnection клас-
са EJB сущности для соединения с базой данных.
Упаковка EJB сущности
Теперь можно упаковать EJB сущности. Выполните следующее:
1. Откомпилируйте все классы .java и поместите файлы .class в каталог сот/
brainysoftware/ejb/ каталога проекта.
2. Перейдите в каталог проекта.
3. Введите команду:
jar cfv product.jar com/brainysoftware/ejb/Product*.» META-INF/ejb-
jar.xml
Эта команда создаст файл jar с именем product.jar в каталоге проекта. Ско-
пируйте этот файл в каталог deploy в JBoss. Если JBoss не выполняется, запус-
тите его. Если JBoss уже работает, не нужно ничего делать. Средство горячего
развертывания JBoss автоматически попытается развернуть любой пакетный
файл, помещенный в каталог deploy.
EJB сущности
759
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.ObjectNotFoundException;
import javax.naming.Initialcontext;
import javax.naming.Context;
import javax.naming.NamingException;
public class CMPProductBean implements EntityBean {
EntityContext context;
public String productld;
public String productName;
public String description;
public double price;
public String getProductldQ {
System.out.println("getProductld");
return productld;
}
public String getProductNameO {
System.out.println("getProductName");
return productName;
}
public String getDescription() {
System.out.println("getDescription");
return description;
}
public double getPrice() {
System.out.println("getPrice");
return price;
}
public String ejbCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
System.out.println("ej bCreate");
this.productld = Integer.toString( productld);
this.productName = productName;
this.description = description;
this.price = price;
756
Глава 30
Листинг 30.6 Продолжение
Enumeration enum = home.findByName("Franklin Spring Water");
while (enum.hasMoreElementsO) {
product = (Product) enum.nextElementO;
System.out.println("Id: ” + product.getProductldO);
System.out.println("Product Name: " + product.getProductName())\
System.out.println("Description: " + product.getDescriptionO);
System.out.println("Price: ” + product.getPriceO);
}
}
catch(Exception e) {
System, out. println(e. toStringO);
}
}
}
Это клиентское приложение похоже на клиентское приложение, которое
использовалось для доступа к EJ В сеанса (см. главы 28 и 29). Приложение встав-
ляет пять записей, вызывая метод create домашнего объекта, и удаляет после-
днюю запись. Затем оно вызывает метод findByName и просматривает данные
каждого продукта, имеющего указанное имя. Проверьте таблицу Products базы
данных и убедитесь в том, что эти записи вставлены правильно.
Создание EJB сущности типа СМР
EJB сущности типа BMP является прекрасным объектом для представления
данных, но EJB сущности типа СМР писать еще легче. В EJB сущности типа
BMP пишутся собственные реализации для методов создания и поиска, а так-
же методов ejbStore и ejbLoad. Кроме того, необходим метод, который возвра-
щает объект java.sql.Connection, служащий для соединения с базой данных.
EJB сущности типа СМР полагается на контейнер EJB в выполнении зада-
чи, связанной с базой данных. Не требуется писать метод getConnection, не
нужно соединяться с базой данных в методах create и remove, не нужно предо-
ставлять реализацию методов ejbStore и ejbLoad. Фактически, не требуется
писать ни одного оператора SQL.
Не нужно даже писать реализацию метода поиска, объявленного в удаленном
интерфейсе. Все это делает контейнер EJB. Однако существует один недостаток.
Необходимо определять больше конфигурационных данных в дескрипторе раз-
вертывания и в конфигурационном файле, специфическом для используемого
сервера приложений.
Отметим также, что способ сохранения данных полностью зависит от кон-
тейнера EJB. Для сохранения данных EJB сущности контейнер может исполь-
EJB сущности
761
this.description = description;
this.price = price;
return Integer.toString(productld);
Дескриптор развертывания
Дескриптор развертывания представлен в листинге 30.10.
Л исти нг 30.10 Дескриптор развертывания (ejb-jar.xml)
<?xml version=’’1.0’’?>
<! DOCTYPE ejb-jar PUBLIC ’’-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN"
’’http: //j ava. sun. com/dtd/ej b-j a r_2_0. dt d ’’>
<ejb-jar>
<enterprise-beans>
<entity>
<ej b-name>CMPProduct</ej b-name>
<home>com.brainysoftwa re.ej b.CMPProductHome</home>
<remote>com.brainysoftware.ejb.CMPProduct</remote>
<ejb-class>com.brainysoftware.ejb.CMPProductBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field><field-name>productld</field-name></cmp-field>
<cmp-fieldxfield-name>productName</field-namex/cmp-field>
<cmp-fieldxfield-name>description</field-namex/cmp-field>
<cmp-fieldxfield-name>price</field-namex/cmp-field>
<primkey-field>productld</primkey-field>
</entity>
</enterprise-beans>
</ejb-jar>
Этот дескриптор развертывания похож на дескриптор развертывания для
EJB сущности типа BMP. Однако он имеет элементы <cmp-field> для всех по-
лей, которые должны быть устойчивыми. В данном примере это поля productld,
productName, description и price. Элемент <primkey-field> определяет поле пер-
вичного ключа.
Значением элемента <persistence-type> будет Container, а не
Bean, как в EJB сущности типа BMP.
758
Глава 30
Листинг 30.8 Домашний интерфейс (CMPProductHomejava)
package com.brainysoftware.ejb;
import java.rmi.RemoteException;
import javax.ejb.FinderException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
import java.util.Collection;
import java.util.Enumeration;
public interface CMPProductHome extends EJBHome {
public CMPProduct create(int productld, String productName,
String description, double price)
throws RemoteException, CreateException;
public CMPProduct findByPrimaryKey(String productld)
throws RemoteException, FinderException;
public Enumeration findByProductName(String productName)
throws RemoteException, FinderException;
public Collection findAHO
throws RemoteException, FinderException;
}
Класс EJB сущности
По сравнению с классом EJB сущности типа BMP, класс EJВ сущности для
CMPProduct значительно проще и короче. Отметим, что он не имеет реализа-
ции для соединения с базой данных. Класс EJB сущности представлен в лис-
тинге 30.9.
Листинг 30.9 Класс EJB сущности (CMPProductBean.java)
package com.brainysoftware.ejb;
import java.sql.*;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Vector;
import java.rmi.RemoteException;
EJB сущности
763
// Получение начального контекста
Initialcontext jndiContext = new Initialcontext(properties);
System.out.println("Got context");
// Получение ссылки на EJB
Object ref = jndiContext.lookup("CMPProduct");
System.out.println("Got reference");
// Получение из this ссылки на домашний (Home) интерфейс EJB
CMPProductHome home = (CMPProductHome)
PortableRemoteObject.narrow(ref, CMPProductHome.class);
// Создание объекта Interest из домашнего (Home) интерфейса
home.create(61, "Franklin Spring Water", "400ml", 2.25);
home.create(62, "Franklin Spring Water", "600ml", 3.25);
home.create(63, "Choco Bar", "Chocolate Bar 200g”, 2.95);
home.create(64, "Timtim Biscuit", "Biscuit w. mint flavor, 300g", 9,25);
CMPProduct product = home.create(65, "Supermie", "Instant Noodle", 1.05);
product. removeO;
Collection allProducts = home.findAll();
Iterator iterator = allProducts.iterator();
while (iterator.hasNextO) {
product = (CMPProduct) iterator.nextO;
System.out.println("Id: " + product.getProductldO);
System.out.println("Product Name: " + product.getProductNameO);
System.out.println("Description: " + product.getDescriptionQ);
System.out.println("Price: " + product.getPriceO);
}
product = home.findByPrimaryKey("53");
System.out.println("Displaying product with id=63");
System.out.println("Product Name: " + product.getProductNameO);
System.out.println("Description: " + product.getDescriptionQ);
System.out.println("Price: " + product.getPriceQ);
Enumeration enum = home.findByProductName("Franklin Spring Water");
while (enum.hasMoreElementsO) {
product = (CMPProduct) enum.nextElementO;
System.out.println("Product Id: " + product.getProductldO);
System.out.println("Description: " + product. getDescriptionQ);
System.out.println("Price: " + product.getPriceQ);
}
760
Глава 30
Листинг 30.9 Продолжение
return Integer.toString(productld);
}
public void ejbPostCreate(int productld, String productName,
String description, double price)
throws RemoteException, CreateException {
System.out.printIn(”ej bPostCreate");
}
public void ejbRemove() throws RemoteException {
System.out.printin("ej bRemove");
}
public void ejbActivateO {
System.out.println("ej bActivate");
}
public void ejbPassivateO {
System.out.println("ejbPassivate");
}
public void ejbLoad() {
System.out.println("ej bLoad");
}
public void ejbStoreO {
System. out. println( "ej bStore”);
}
public void setEntityContext(EntityContext context) {
System.out.println("setEntityContext");
this.context = context;
}
public void unsetEntityContextO {
System. out. print ln( "unset EntityContext");
context = null;
}
}
Класс EJB сущности имеет четыре следующие бизнес-метода: get Product Id,
getProductName, getDescription и getPrice. Метод ejbCreate заполняет поля класса
значениями аргументов, переданными ejbCreate. Однако нет кода для соеди-
нения с базой данных, как в методе ejbCreate в EJB сущности типа BMP:
String description, double price)
throws RemoteException, CreateException {
System.out.println("ej bCreate");
this, productld = Integer. toString(productld);
this. productName = productName;
31
Язык запросов EJB
предыдущей главе рассказывалось о двух типах ЫВсущности: EJ В сущ-
ности, живучесть данных которых обеспечивает EJB (BMP, bean-managed
persistence), и EJB сущности, живучесть данных которых обеспечивает кон-
тейнер EJB (CMP, container-managed persistence). При использовании EJB
сущности типа СМР не нужно беспокоиться о сохранении данных EJB. Это делает
контейнер.
Автоматическое сохранение данных контейнером — хорошее свойство EJB
сущности типа СМР, но EJB сущности типа BMP являются иногда предпоч-
тительнее, так как они обеспечивают большую гибкость при определении
сложных методов поиска. С помощью EJB сущности типа BMP можно напи-
сать сложный оператор SQL для метода поиска и выбрать любое необходимое
множество записей.
EJB сущности типа СМР менее гибкие, так как приходится использовать
встроенные методы поиска из контейнера EJB. В JBoss таких методов три:
findByPrimaryKey, findALL и findBy<«oje>.
Чтобы обойти эти ограничения, EJB 2.0 определяет язык запросов EJ В (EJВ
QL, EJB Query Language). Он похож на язык SQL, который разработчики EJB
могут использовать для выборки данных в методах поиска EJB сущности типа
СМР. Для оптимизации производительности можно компилировать EJB QL в
целевой язык базы данных или другого хранилища данных, можно также
создать хранимую процедуру, которая инкапсулирует оператор SQL для уско-
рения его выполнения.
762
Глава 30
Упаковка EJB сущности типа СМР
Теперь можно упаковать EJB сущности. Выполните следующие действия:
1. Откомпилируйте все файлы .java и поместите файлы .class в каталог сот/
brainysoftware/ejb/ каталога проекта.
2. Перейдите в каталог проекта.
3. Введите следующую команду:
jar cfv cmpproduct.jar com/brainysoftware/ejb/CMPProduct*.*
META-INF/ejb-jar. xml
Эта команда создаст файл .jar с именем cmpproduct.jar в каталоге проекта.
Скопируйте этот файл в каталог deploy в JBoss. Если JBoss не выполняется,
запустите его. Если JBoss уже работает, то ничего делать не надо. Средство го-
рячего развертывания JBoss автоматически попытается развернуть любой файл
пакета, который сохранен в каталоге deploy.
Клиентское приложение
Клиентское приложение для EJB сущности типа СМР представлено в лис-
тинге 30.11. Отметим, что оно похоже на клиентское приложение листинга 30.6.
Л исти н г 30.11 Клиентское приложение
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import java.util.Properties;
import java.util.Enumeration;
import com.brainysoftware.ejb.CMPProduct;
import com.brainysoftware.ejb.CMPProductHome;
import java.util.Collection;
import java.util.Iterator;
public class BeanClient {
public static void main(String[] args) {
// Подготовка свойств для создания объекта Initialcontext
Properties properties = new PropertiesO;
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.j np.interfaces.NamingContextFactory”);
properties.put(Context.PROVIDER_URL, '’localhost: 1099”);
try {
Язык запросов EJB
767
select_clause from_clause [where_clause]
В этом запросе where_clause является необязательным.
Предложение SELECT
Предложение SELECT определяет выходные данные запроса EJB QL. Оно
может содержать один из следующих элементов:
• Одномерную переменную в диапазоне типа абстрактной схемы EJB
сущности
• Однозначное выражение пути доступа
Предложение SELECT в методе поиска содержит либо одномерную пере-
менную в диапазоне типа абстрактной схемы EJ В сущности, для которого оп-
ределен метод поиска, либо поле сгпг, заданное однозначным выражением пути
доступа, приводимым к типу абстрактной схемы EJB сущности, для которого
определен метод поиска.
В противоположность этому, предложение SELECT запроса метода выбора
может возвращать типы абстрактной схемы других EJB сущности или значе-
ния полей стр.
Синтаксис предложения SELECT:
SELECT [DISTINCT] {single_valued_path_expression |
OBJECT(identification_variable)}
Если в предложении SELECT присутствует ключевое слово DISTINCT, оди-
наковые значения удаляются из результата запроса EJB QL.
Если в предложении SELECT отсутствует ключевое слово DISTINCT, но
запрос определен для метода, результат которого имеет тип java.util.Set, все
дублирующие значения также удаляются из результата запроса.
В качестве примера приведем простой оператор EJB QL, который находит
все разные продукты:
SELECT DISTINCT OBJECT(p)
FROM Product AS p
Предложение FROM
Предложение FROM определяет область действия запроса, объявляя иден-
тификационные переменные. Область действия запроса может быть ограничена
выражениями пути доступа.
Предложение FROM может содержать несколько идентификационных
переменных, разделенных запятой. Оно имеет следующий синтаксис:
764
Гпава 30
Листинг 30.11 Продолжение
}
catch(Exception е) {
System, out. println(e. toStringO);
>
}
}
Заключение
В этой главе был представлен второй тип EJB: EJB сущности. Используются
два типа EJB сущности: EJB сущности, живучесть данных которых обеспечи-
вает EJB (BMP, bean-managed persistence), и EJB сущности, живучесть данных
которых обеспечивает контейнер EJB (СМР, container-nmanaged persistence).
EJB сущности типа BMP предоставляют больше гибкости для обработки
и сохранения данных в базе данных. Для EJB сущности типа BMP необходи-
мо предоставить информацию о том, как загрузить драйвер JDBC для базы
данных.
EJB сущности типа СМР очень легко писать, так как контейнер EJB заботится
о сохранении данных. Для этого EJB не требуется писать ни одного оператора
SQL.
Язык запросов EJB
769
single_valued_navigation::=
identification_variable.[single_valued_cmr_field.]*
single_valued_cmr_field
Синтаксис для выражений пути доступа со значением в совокупности:
collection_valued_path_expression::= identification_variable.
[single_valued_cmr_field.]*collection_valued_cmr_field
single_valued_cmrJleld указывается полем cmr в отношении один-к-одному или
много-к-одному. Тип выражения — это тип абстрактной схемы соответствую-
щего EJB сущности. Тип collection_valued_cmrJield является совокупностью
значений типа абстрактной схемы соответствующего EJB сущности.
Рассмотрим оператор EJB QL, который содержит предложение FROM для
поиска всех заказов на продукты категории Ъоок':
SELECT DISTINCT OBJECT(o)
FROM Order о, IN(o.lineltems) 1
WHERE 1. product.category = ‘book’
Предложение WHERE
Предложение WHERE запроса EJB QL определяет выборку данных. Оно
имеет синтаксис:
WHERE conditional_expression
Условное выражение (conditional expression) может содержать следующие
элементы:
• Литерал
• Идентификационную переменную
• Выражение пути доступа
• Входные параметры
• Составное условное выражение
• Операторы и приоритет операторов
• Выражения between
• Выражения In
• Выражения Like
• Выражения сравнения с null
• Выражения сравнения с пустой совокупностью
• Выражения члена совокупности
• Функциональные выражения
766
Глава 31
Запросы EJB QL можно использовать двумя различными способами:
• Для выбора объектов сущности, как определено в методах поиска в до-
машнем интерфейсе.
• Для выбора объектов сущности или других значений, производных от
типа абстрактной схемы EJB сущности.
Операторы EJB рЬддя методов поиска и выборки определяются в дескрип-
торе развертывания, в элементе <query>. Например, следующий дескриптор
развертывания определяет метод поиска с именем findByDescription, который
имеет один параметр типа java.lang.String:
<?xml version=”1.0’’?>
<!DOCTYPE ejb-jar PUBLIC ’’ -//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN"
’’http: //j ava. sun.com/dtd/ejb-jar_2_0.dtcT>
<ejb-jar>
<enterprise-beans>
<entity>
<query>
<query-method>
<method-name>findByDescription</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<ejb-ql>
<! [CDATA[SELECT OBJECT(product) FROM CMPProduct
AS product WHERE Description=?1]]>
</ejb-ql>
</query>
</entity>
</enterprise-beans>
</ejb-jar>
Имя метода поиска определяется в элементе <method-name>, а параметры —
в элементах <method-param>. Сам оператор EJB-QL задан в элементе <ejb-ql>.
Синтаксис EJB QL
Рассмотрим наиболее важные конструкции этого языка запросов.
Запрос EJB QL должен иметь следующую форму:
Язык запросов EJB
771
Следующий оператор EJB QL является примером запроса, использующего
входной параметр. Оператор находит все продукты, цена которых ниже Значе-
ния, передаваемого параметром:
SELECT DISTINCT OBJECT(o)
FROM Product о
WHERE o.price < ?1
Составное условное выражение
Составное условное выражение содержит другие условные выражения,
операторы сравнения, логические операции и выражения пути доступа, кото-
рые приводятся к булевым значениям и булевым литералам.
Условные выражения имеют следующий синтаксис:
conditional_expression:: = conditional_term | conditional-expression OR
conditional-term
conditional-term::= conditional-factor | conditional-term AND
conditional-factor
conditional-factor: := [NOT] conditional-test
conditional-primary: := simple_cond_expression | conditional-expression
simple_cond_expression::= comparison_expression | between.expression |
like.expression | in_expression | null_comparison_expression |
empty_collection_comparison_expression | collection_number_expression
Операторы и приоритет операторов
В предложении WHERE могут использоваться следующие операторы. Они
перечислены в порядке уменьшения приоритета:
• Оператор навигации (.)
• Арифметические операторы: унарный, умножение и деление (*,/), сло-
жение и вычитание (+,-)
• Операторы сравнения: =, >, >=, <, <=, <>
• Логические операторы: NOT, AND, OR
Выражения between
Синтаксис использования оператора сравнения [NOT] BETWEEN в услов-
ном выражении:
arithmetic_expression [NOT] BETWEEN arithmetic-expr AND
arithmetic-expr
768
Глава 31
FROM identification_variable_declaration
[,identification_variable_declaration]*
В этом предложении звездочка указывает, что значение в квадратных скоб-
ках может повторяться нуль или несколько раз. Более того, объявление иден-
тификационной переменной определяется следующим образом:
identification_variable_declaration ::= collection_jnember_declaration |
range_variabl е_ dec1 а гаtion
collection_member_declaration :: = IN (collection_valued_path_expression)
[AS] identifier
range_variable_declaration ::= abstract_schema_name [AS] identifier
Идентификатор является независимой от регистра символов последователь-
ностью символов любой длины. Первым символом идентификатора должен
быть действительный начальный символ идентификатора Java, а все другие
символы должны быть законными символами идентификатора Java. Кроме
того, идентификатором не могут быть: SELECT, FROM, WHERE, DISTINCT,
OBJECT, NULL, TRUE, FALSE, NOT, AND, OR, BETWEEN, LIKE, IN, AS,
UNKNOWN, EMPTY, MEMBER, OF и IS.
Идентификационная переменная действует как идентификатор в пред-
ложении FROM. Она объявляется с помощью специальных операторов IN
и необязательного AS. Идентификационная переменная не зависит от ре-
гистра символов и не должна быть зарезервированным идентификатором.
Кроме того, она не должна иметь имя, совпадающее с именем абстрактной
схемы или с ejb-именем.
Объявление идентификационной переменной является либо объявлением
переменной диапазона, либо объявлением члена совокупности. В случае
объявления переменной диапазона оно распространяется на тип абстрактной
схемы EJB сущности. Синтаксис объявления идентификационной переменной
как переменной диапазона аналогичен SQL. При желании можно использо-
вать оператор AS.
В случае объявления члена совокупности объявленная идентификационная
переменная соответствует значениям совокупности, полученным при навигации
с помощью выражения пути доступа. Объявление члена совокупности выполня-
ется с помощью оператора IN.
Выражение пути доступа — это идентификационная переменная, за которой
следует оператор навигации (.) и поле стр или поле стг.
Ниже приведен синтаксис однозначных выражений пути доступа:
single_valued_path::= {single_valued_navigation |
identification_variable}. cmp_field | single_valued_navigation
Язык запросов EJB
773
• Арифметические функции:
ABS(number) возвращает int, float или double.
SQRT(double) возвращает double.
BNF EJB QL
EJB QL в нотации BNF определяется следующим образом:
EJB QL ::= select_clause from_clause [where_clause]
from_clause ::= FROM identification_variable_declaration
[, identification_variable_declaration]*
identification_variable_declaration :: = collectionjnemberjjeclaration |
range_variable_declaration
collection_member_declaration ::= IN (collection_valued_path_expression)
[AS ] identifier
range_variable_declaration ::= abstract_schema_name [AS ] identifier
single_valued_path_expression : : =
{single_valued_navigation | identification_variable}.cmp_field |
single_valued_navigation
single_valued_navigation ::=
identification_variable.[single_valued_cmr_field.]*
single_valued_cmr_field
collection_valued_path_expression : : =
identification_variable.[single_valued_cmr_field.]*collection_valued_cmr_field
select_clause ::= SELECT [DISTINCT ] {single_valued_path_expression |
OBJECT (identification_variable)}
where_clause ::= WHERE conditional-expression
coditional_expression ::= conditional-term |
coditional_expression OR conditional-term
conditional-term ::= conditional-factor |
conditional-term AND conditional-factor
conditional-factor ::= [NOT ] conditional-test
conditional-test ::= conditional-primary
conditional-primary ::= simple_cond_expression |
(conditional-expression)
simple_cond_expression ::= comparison_expression |
between_expression | like_expression |
in_expression | null_comparison_expression |
empty_collection_comparison_expression |
collection_member_expression
between_expression ::=
arithmetic_expression [NOT ] BETWEEN
arithmetic_expression AND arithmetic_expression
in_expression ::=
single_valued_path_expression [NOT ] IN (string_literal [,
string_litaral]*)
like_expression ::=
770
Глава 31
Литерал
Литерал можетбытьстроковым, точным числовым, приблизительным чис-
ловым или булевым. Строковый литерал заключается в одиночные кавычки,
например ’java’. Если строковый литерал содержит символ одиночной кавыч-
ки, то этот символ должен быть экранирован с помощью еще одного символа
одиночной кавычки. Таким образом, строковый литерал, содержащий
O'Connor, представляется как ’О"Соппог'.
Точный числовой литерал — это числовое значение без десятичной точки в
диапазоне, определяемом long Java. Например: 123, +80 и -99.
Приблизительный числовой литерал — это числовое значение в диапазо-
не, определяемом float Java; представляется в научной нотации или числовым
значением с десятичной точкой. Например: 9Е2, -9Е2, +6.6 и -98.98.
Булев литерал — это TRUE или FALSE.
Идентификационная переменная
Идентификационная переменная описывалась выше. Любая идентификация,
которая появляется в предложении WHERE, должна объявляться в предложении
FROM.
Выражение пути доступа
Выражение пути доступа также описывалось выше. Нельзя использовать
выражение_пути_доступа_со_значением_в_совокупности
(collection_valued_path_expression) в предложении WH ERE как часть условного
выражения, за исключением выражения_сравнения_с_пустой_совокупностью
или выражения_члена_совокупности.
Входные параметры
Входные параметры подчиняются следующим правилам:
• Входной параметр создается префиксом в виде знака вопроса (?), за ко-
торым следует целое число, например ?1.
• Входные параметры нумеруются, начиная с 1.
• Число различных входных параметров в запросе EJB QL не должно быть
больше числа входных параметров в методе поиска или выборки.
• Входные параметры должны появляться только в условных выражениях,
включающих в себя однозначные выражения пути доступа.
• Входной параметр приводится к типу соответствующего параметра, оп-
ределенного в сигнатуре метода поиска или выборки, связанного с зап-
росом.
• Если входной параметр в методе поиска или выборки соответствует
EJBObject или EJBLocalObject, контейнер отображает входной параметр
в соответствующее значение абстрактной схемы.
Язык запросов EJB
775
LOCATE (string_expression, string_expression
[, arithmetic_expression]) |
ABS (arithmetic_expression) |
SORT (arithmetic_expression)
Здесь применяются следующие обозначения:
• — группирование.
• — необязательные конструкции.
• Ключевые слова печатаются полужирным шрифтом.
Заключение
EJB 2.0 определяет язык запросов EJB (EJB QL) — похожий на SQL язык, ко-
торый разработчики EJB могут использовать для выборки данных в методах
поиска EJВ сущности типа СМР.
Запросы EJB QL можно применять для выбора объектов сущности, опре-
деленных в методах поиска в домашнем интерфейсе, и для выбора объектов
сущности или других значений, производных от типа абстрактной схемы EJB
сущности.
772
Глава 31
Выражения IN
Синтаксис использования оператора сравнения [NOT] IN в условном вы-
ражении:
single_valued_path_expression [NOT] IN (string-literal [, string-
literal]*)
Выражения LIKE
Синтаксис использования оператора сравнения [NOT] LIKE в условном
выражении:
single_valued_path_expression [NOT] LIKE pattern-value
[ESCAPE escape-character]
Выражения сравнения c null
Синтаксис использования оператора сравнения IS NULL в условном вы-
ражении:
single_valued_path_expression IS [NOT] NULL
Выражения сравнения с пустой совокупностью
Синтаксис использования оператора сравнения IS EMPTY в выражении
сравнения с пустой совокупностью:
collection_valued_path_expression IS [NOT] EMPTY
Выражения членов совокупности
Синтаксис использования оператора сравнения MEMBER OF в выраже-
нии члена совокупности:
single_valued_path_expression [NOT] MEMBER [OF]
collection_valued_path_expression
Функциональные выражения
Ниже представлены встроенные функции EJB QL:
• Строковые функции:
CONCAT(String, String) возвращает String.
SUBSTRING(String, start, length) возвращает String.
LOCATE(String, String [,start]) возвращает int.
LENGTH(String) возвращает int.
Служба сообщений Java
777
• Пользователь может посылать личное сообщение другому пользователю,
которое невидимо остальным пользователям chat.
JMS предоставляет две аналогичные службы.
Однако служба обмена сообщениями более развита, чем приложение chat.
В приложении chat отправитель и получатель должны быть соединены во вре-
мя обмена сообщениями. В службе обмена сообщениями отправитель может
послать сообщение, не требуя от получателя соединения в этот момент. От-
правитель посылает сообщение по месту назначения, а затем получатель
забирает его оттуда.
JMS разъединяет своих клиентов. Отправителю не требуется знать ничего
о получателе, и наоборот.
API JMS
Компания Sun объединила усилия с несколькими партнерскими компаниями для
создания API JMS, позволяющего приложениям Java создавать, посылать и чи-
тать сообщения, а также общаться с другими средствами обмена сообщениями.
Исходная версия JMS была выпущена в августе 1998 г., и она использовалась в
основном для того, чтобы приложения могли получать доступ к существующим
ориентированным на сообщения системам связующего программного обеспече-
ния (MOM, messaging-oriented middleware), таким как MQSeries компании IBM.
Самой последней версией API JMS является 1.0.2, которую можно загрузить по
адресу http://java.sun .com/products/jms/docs.html.
Приложения Java, использующие JMS, называются клиентами JMS, а си-
стема обмена сообщениями, управляющая маршрутизацией и доставкой
сообщений, называется провайдером JMS. Клиент JMS, который посылает
сообщение, называется производителем (producer), а клиент JMS, который по-
лучает сообщение, называется потребителем (consumer). Один клиент JMS
может быть одновременно производителем и потребителем. Приложение J MS
состоит из нескольких клиентов JMS и одного или нескольких провайдеров.
Следующие два свойства являются важной особенностью JMS:
• Асинхронность. Клиент JMS не должен запрашивать сообщения, чтобы
получить их. Провайдер JSM доставляет сообщения клиенту по мере их
поступления.
• Надежность. Система JMS может гарантировать, что сообщение будет
доставлено только один раз.
JMS удобно применять в тесно связанных системах сообщений в следую-
щих условиях:
• Клиенты должны быть разъединены.
774
Глава 31
single_valued_path_expression [NOT ] LIKE pattern_value [ESCAPE excape-
character]
null_comparison_expression ::= single_valued_path_expression IS [NOT ]
NULL
empty_collection_comparison_expression : : =
collection_valued_path_expression IS [NOT] EMPTY
collectionjnember_expression :: =
single_valued_path_expression [NOT ] MEMBER [OF ]
collect ion_valued._path_expression
comparison_expression : : =
string_value { =|<>} string_expression |
boolean_value { =|<>} boolean_expression} |
datetime_value { = | <> | > | < } datetime_expression |
entity_bean_value {=)<>} entity_bean_expression |
arithmetic_value comparison_operator single_value_designator
arithmetic_value ::= single_valued_path_expression
| functions_returning_numerics
single_value_designator ::= scalar_expression
comparison_operator ::=
= | > | >= | < | <= | <>
scalar_expression ::= arithmetic_expression
arithmetic_expression ::= arithmetic_term | arithmetic_expression
{ + | - } arithmetic_terin
arithmetic_term ::= arithmetic_factor | arithmetic_term
{ * | / } arithmetic_factor
arithmetic_factor ::= { + | - } arithmetic_primary
arithmetic_primary ::= single_valued_path_expression | literal |
(arithmetic_expression) |
input-parameter | functions_returning_numerics
string_value ::= single_valued_path_expression |
function_returning_strings
string-expression ::= string_primary | input_expression
string.primary ::= single_valued_path_expression | literal |
(string.expression) |
functions_returning_strings
datetime_value : := single_valued_path_expression
datetime.expression ::= datetime_value | input-parameter
boolean-value ::= single-valued_path_expression
boolean_expression ::= single_valued_path_expression | literal
| input_parameter
entity_bean_value ::= single_valued_path_expression |
identification_variable
entity_bean_expression ::= entity_bean_value | input.parameter
functions_retirning_strings ::=CONCAT (string-expression,
string-expression) |
SUBSTRING (string.expression, arithmetic_expression,
arithmetic_expression)
functions_returning_numerics ::=
LENGTH (string_expression) |
Служба сообщений Java
779
Объектная модель JMS
Наиболее важные объекты объектной модели JMS представлены следующими
интерфейсами пакетаjavax.jms:
• Connection Factory
• Destination
• Connection
• Session
• Message Producer
• MessageConsumer
• Message
Эти объекты обсуждаются ниже.
ConnectionFactory
Объект ConnectionFactory служит для создания соединения с провайдером
JMS. Этот объект поддерживает одновременное использование и содержит
конфигурационные параметры соединения, которые определяются админи-
стратором. В пакете javax.jms объект ConnectionFactory моделируется с помо-
щью интерфейса ConnectionFactory, сигнатура которого имеет следующий вид:
public interface ConnectionFactory
Интерфейс ConnectionFactory не определяет никаких методов и имеет два не-
посредственных подынтерфейса: TopicConnection Factory и QueueConnection Factory.
Интерфейс QueueConnectionFactory используется для создания соединения
с J MS-провайдером точка-точка, а интерфейс TopicConnectionFactory служит
для создания соединения с JMS-провайдером pub/sub.
Обычно в клиентской программе JMS выполняется JNDI-поиск фабрики
соединений. Например, для получения соединения с провайдером JMS точ-
ка-точка используется код:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context. lookup('’QueueConnectionFactory");
А для получения соединения с JMS-провайдером pub/sub служит код:
Context context = new InitialContextO;
TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory) context.lookup("TopicConnectionFactory");
32
Служба
сообщений Java
В предыдущих главах были изучены два типа EJB — EJB сеанса и EJB сущ-
ности. В спецификацию EJB 2.0 были добавлены EJB, управляемые сообщени-
ями. Работа с EJB, управляемыми сообщениями, требует хорошего знания служ-
бы сообщений Java (JMS, Java Message Service), которая рассматривается в этой
главе. Сами EJB, управляемые сообщениями, описываются в главе 33.
1лава начинается с изучения обмена сообщениями и API JMS. Затем при-
водится несколько примеров, которые используют JMS для отправки и полу-
чения сообщений.
Введение в обмен сообщениями
Служба обмена сообщениями обеспечивает коммуникацию между приложениями
или между программными компонентами. Любое приложение или программный
компонент, который использует службу обмена сообщениями, называется
клиентом обмена сообщениями. Втипичной службе обмена сообщениями клиент
обмена сообщениями может посылать и получать сообщения.
Приложение chat является хорошей иллюстрацией службы обмена сооб-
щениями. Обычно сервер chat предоставляет два метода коммуникации своим
пользователям:
• Пользователь может посылать сообщение chat, которое передается всем
другим пользователям chat.
Язык запросов EJB
781
Соединения
Объект Connection представляет собой активное соединение клиента] MS с про-
вайдером J MS. Обычно это сокетТСР/I Р между клиентом J MS и провайдером J MS.
Объекты Connection поддерживают одновременное использование и представ-
ляются интерфейсом javax.jms.Connection. Этот интерфейс имеет сигнатуру:
public interface Connection
Создание объекта Connection включает в себя настройку аутентифика-
ции и коммуникации.
В JMS существуют два типа соединен ий: QueueConnection и TopicConnection.
Объект QueueConnection используется для получения соединения с областью
действия РТР, а объект TopicConnection служит для получения соединения с
областью действия pub/sub. Эти два типа соединений представлены объекта-
ми, которые реализуют интерфейсы QueueConnection и TopicConnection со-
ответственно. QueueConnection и TopicConnection являются прямыми подын-
терфейсами интерфейса Connection и содержат метод для создания
соответствующего объекта Session, подходящего для своей области действия.
При первом создании соединение находится в остановленном режиме, оз-
начающем, что никакие сообщения не доставляются. Обычно соединение ос-
тается в этом режиме до завершения настройки. Клиент может затем вызвать
метод start объекта соединения.
В области действия РТР объект QueueConnection можно получить с помо-
щью метода createQueueConnection объекта QueueConnectionFactory:
QueueConnection queueConnection =
queueConnectionFactory.createQueueConnection();
В области действия pub/sub объект TopicConnection получают, вызывая ме-
тод createTopicConnection объекта TopicConnectionFactory:
TopicConnection topicConnection =
topicConnectionFactory.createTopicConnection();
После получения объекта соединения можно вызвать метод start интерфейса
Connection, чтобы разрешить приложению JMS получать сообщения. Не за-
будьте освободить ресурсы, закрыв соединение по окончании его использова-
ния. Соединение закрывают, вызывая метод close интерфейса Connection.
Сеанс
Объект Session является однопоточным контекстом для создания и получе-
ния сообщений. Объект Session представлен интерфейсом Session, который
использует следующую сигнатуру:
public interface Session extends java.lang.Runnable
778
Глава 32
• Обмен сообщениями может происходить, даже если не все клиенты вклю-
чены и работают в одно время.
• Бизнес-модель требует, чтобы компоненты посылали сообщения друг
другу и продолжали действовать, не получая немедленный ответ.
Области действия обмена сообщениями
API JMS
Области действия (домены) обмена сообщениями являются моделями обмена
сообщениями. API JMS предоставляет следующие две области действия обме-
на сообщениями: издатель/подписчик и точка-точка.
Издатель/подписчик (pub/sub)
В этой модели клиент посылает сообщение по теме, и сообщение получает
любой заинтересованный клиент, подписавшийся на эту тему. Это модель
один-много, где отправитель называется также издателем, а получатели на-
зываются подписчиками. Действие по отправке сообщения называется изданием.
Сообщения по теме посылаются всем подписчикам и несохраняются. Клиент
сможет получать сообщения только после того, как он подпишется на тему, и
подписчик должен быть активным, чтобы получать опубликованные сообще-
ния. Однако это ограничение несколько ослаблено в JMS: клиент может со-
здать длительные подписки, и он будет получать посланные сообщения, даже
если не является активным.
Можно сравнить модель pub/sub с радиостанцией, которая вещает новости
все время. Новости может получать любой радиоприемник, настроенный на
частоту радиостанции. Чтобы получить определенные новости, радио должно
быть включено в то время, когда эти новости передаются.
Точка-точка (РТР)
В этой модели клиент посылает сообщение, которое получает только один
клиент. Отправитель посылает сообщение в очередь, а получатель, соединив-
шись с системой, извлекает это сообщение из очереди в любое удобное время.
Очередь сохраняет все поступающие сообщения, пока они не будут приняты
или пока не истечет строк хранения.
Модель РТР аналогична обычной почтовой службе, где адресат имеет по-
чтовый ящик в определенном почтовом отделении. Отправитель посылает
письмо, а адресат получает его в почтовом отделении. Письмо может полу-
чить только один получатель.
Служба сообщений Java
783
public interface MessageProducer
Интерфейс MessageProducer имеет два непосредственных подынтерфейса:
• QueueSender. Этот интерфейс используется в области действия РТР.
• TopicPublisher. Этот интерфейс используется в области действия pub/sub.
Объект MessageProducer создается при передаче объекта Destination в ме-
тод создания сообщения (message-producer) соответствующего объекта сеанса
(session). Например, объект QueueSender создается при вызове метода
createSender интерфейса QueueSession с передачей ему Queue. Приведем фраг-
мент кода, который создает объект QueueSender:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory)
context. lookup( ’’QueueConnectionFactory”);
Queue queue = (Queue) context.lookup(queueName);
QueueConnection queueConnection =
queueConnectionFactory.createQueueConnection();
QueueSession queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
QueueSender queueSender = queueSession.createSender(queue);
В качестве другого примера создания объекта MessageProducer приведем
код, создающий объект TopicPublisher:
Context context = new InitialContextO;
TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory)
context.lookup("TopicConnectionFactory”);
Topic topic (Topic) context.lookup(topicName);
TopicConnection topicConnection =
topicConnectionFactory.createTopicConnection();
TopicSession topicSession = topicConnection.createTopicSession(false.
Session.AUTO_ACKNOWLEDGE);
TopicPublisher topicPublisher = topicSession.createPublisher(topic);
Messageconsumer
Объект MessageConsumer используется клиентом JMS для получения сообще-
ния из места назначения. В пакете javaxjms этот объект представлен интерфейсом
MessageConsumer, который имеет следующую сигнатуру:
public interface MessageConsumer
Интерфейс MessageConsumer имеет два непосредственных подынтерфейса:
780
Глава 32
Затем можно использовать метод createQueueConnection интерфейса
QueueConnectionFactory для получения объекта QueueConnection или метод
createTopicConnection интерфейса TopicConnectionFactory для получения
объекта TopicConnection.
Фабрика соединений является объектом, управляемым JMS.
ГПримечаниеЛ Объект, управляемый JMS, — это объект, который содержит
конфигурационную информацию, созданную
администратором, и который будет использоваться
клиентами JMS.
Destination
Объект Destination инкапсулирует определяемый провайдером адрес.
Объект Destination поддерживает одновременное использование и в пакете
javax.jms представлен интерфейсом Destination, сигнатура которого имеет вид:
public interface Destination
Интерфейс Destination не имеет никаких методов.
В области действия pub/sub местом назначения является тема. В области
действия РТР местом назначения является очередь. Для реализации этого ин-
терфейс Destination имеет два непосредственных подынтерфейса: Topic и
Queue.
Подобно объекту Connection Factory, объект Destination можно получить с
помощью поиска JNDL В зависимости от используемой области действия
понадобится объект Topic или объект Queue. Ниже приводится код для получения
Queue в области действия РТР:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context.lookup("QueueConnectionFactory");
Queue queue = (Queue) context.lookup(queueName);
Теперь покажем, как получить объект Topic в области действия pub/sub:
Context context = new InitialContextO;
TopicConnectionFactory topicConnectionFactory =
(TopicConnectionFactory) context. lookup("TopicConnectionFactory");
Topic topic = (Topic) context.lookup(topicName);
(Примечанием Объект Destination является объектом, управляемым JMS.
Служба сообщений Java
785
Все эти сообщения представлены интерфейсами, которые являются про-
изводными от интерфейса javax.jms.Message. Интерфейс Message определяет
заголовок сообщения и метод подтверждения для всех сообщений.
Сообщения JMS состоят из следующих частей:
• Заголовок (Header). Все сообщения поддерживают одно и то же множе-
ство полей заголовка. Поля заголовка содержат значения, используемые
как клиентом, так и провайдером для идентификации и маршрутиза-
ции сообщений.
• Свойства (Properties). Каждое сообщение содержит встроенное средство
для поддержки определенных приложением значений свойств. Свойства
предлагают эффективный механизм для поддержки фильтрации сооб-
щений, определяемой приложением.
• Тело (Body). API JMS описывает несколько типов тела сообщения, кото-
рые определяют тип сообщения.
Чтобы послать сообщение, необходимо сначала создать объект Message,
вызвав один из методов создания сообщения из интерфейса Session. Можно
создать сообщение любого типа с помощью одного из следующих методов
интерфейса Session: create Bytes Message, create Map Message, createObject Message,
createStreamMessage и createTextMessage. Например, ниже приводится код,
который создает объект Text Message из объекта QueueSession, задает в нем текст
и посылает его в очередь:
TextMessage message = queueSession. createTextMessageO;
message.setText("This is a TextMessage");
queueSender.send(message);
Для получения сообщения используется соответствующий метод интерфей-
са MessageConsumer:
Message message = queueReceiver. receiveO;
При необходимости можно использовать instanceof, чтобы запросить тип
возвращаемого сообщения:
if(message instanceof ObjectMessage) {
ObjectMessage ObjectMessage = (ObjectMessage) message;
}
Создание клиента JMS
Теперь напишем два клиентских приложения JMS. Первое приложение (см.
листинг 32.1) предоставляет код для отправки сообщения в очередь с именем
782
Глава 31
Интерфейс Session имеет два непосредственных подынтерфейса:
TopicSession и QueueSession. Они представляют сеанс в области действия pub/
sub и в области действия РТР соответственно.
Чтобы создать объект TopicSession, вызывается метод createTopicSession интер-
фейса TopicConnection. Для создания объекта QueueConnection используется ме-
тод createQueueSession интерфейса QueueConnection. Сигнатуры этих методов:
public TopicSession createTopicSession(boolean transacted, int
acknowledgeMode) throws JMSException
public QueueSession createQueueSession(boolean transacted, int
acknowledgeMode) throws JMSException
Методы createTopicSession и createQueueSession имеют следующие параметры:
• transacted. Указывает, что сеанс является транзакционным.
• acknowledgeMode. Указывает, что потребитель или клиент будет подтвер-
ждать сообщения, которые он получает. Его значением может быть одно
из трех полей интерфейса Session.
• AUTO_ACKNOWLEDGE. Сеанс автоматически подтверждает получение
клиентом сообщения, когда сеанс успешно завершает вызов получения
либо когда приемник сообщений, который вызывается сеансом для об-
работки сообщения, успешно завершает работу.
• CLIENT_ACKNOWLEDGE. Клиент подтверждает получение сообщения,
вызывая метод acknowledge сообщения.
• DUPS_OK_ACKNOWLEDGE. Этот режим подтверждения просит сеанс
подтвердить доставку сообщений.
Параметр acknowledgeMode игнорируется, если сеанс является транзак-
ционным. Следующий код создает нетранзакционный объект TopicSession с
режимом подтверждения AUTO_ACKNOWLEDGE:
TopicSession topicSession =
topicConnection.createTopicSessioh(false, Session.AUTO_ACKNOWLEDGE);
Теперь создадим нетранзакционный объект QueueSession с режимом под-
тверждения CL1ENT_ACKNOWLEDGE:
QueueSession queueSession = queueConnection.createQueueSession
(false, Session.CLIENT_ACKNOWLEDGE);
MessageProducer
Объект MessageProducer используется клиентом JMS для отправки со-
общений по месту назначения. В пакете javax.jms этот объект представлен
интерфейсом MessageProducer, который имеет следующую сигнатуру:
Служба сообщений Java
787
Код начинается с выполнения поиска JNDI для получения
QueueConnection Factory:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context.lookup("QueueConnectionFactory");
После этого можно получить объект Queue, выполнив другой поиск JNDI:
String queueName = "MyQueue";
Queue queue = (Queue) context.lookup(queueName);
Затем используется метод createQueueSession объекта
QueueConnectionFactory для создания объекта QueueConnection:
queueconnection = queueConnectionFactory.createQueueConnection();
Объект QueueConnection позволяет создать объект QueueSession. Для этого
нужно вызвать метод createQueueSession:
QueueSession queueSession =
queueconnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
После получения объекта QueueSession создают объект QueueSender, вы-
зывая метод createSender объекта QueueSession с передачей ему объекта Queue,
полученного при поиске JNDI:
QueueSender queueSender = queueSession.createSender(queue);
Теперь можно послать сообщение. Код листинга 32.2 создает объект
TextMessage с именем message с помощью метода createTextMessage объекта
QueueSession, задает его текст и вызывает метод send объекта QueueSender, что-
бы послать сообщение:
TextMessage message = queueSession.createTextMessageO;
message.setText("This is a TextMessage");
queueSender.send(message);
Листинг 32.2. Получатель сообщения клиентского приложения JMS
package com.brainysoftware.ejb;
import javax.jms.*;
import javax.naming.*;
public class MessageReceiver {
public static void main(String[] args) {
QueueConnection queueconnection = null;
784
Глава 32
• QueueReceiver. Этот интерфейс используется в области действия РТР.
• TopicSubscriber. Этот интерфейс используется в области действия pub/sub.
Объект MessageConsumer создается при передаче объекта Destination в ме-
тод получения сообщения соответствующего объекта сеанса. Например, объект
QueueReceiver создается при вызове метода create Receiver интерфейса
QueueSession с передачей Queue:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory)
context.lookup("QueueConnectionFactory”);
Queue queue (Queue) context.lookup(queueName);
QueueConnection queueconnection =
queueConnectionFactory.createQueueConnection();
QueueSession queueSession = queueconnection.createQueueSession(false,
Session.AUTH_ACKNOWLEDGE);
QueueReceiver queueReceiver = queueSession.createReceiver(queue);
Для создания объекта TopicSubscriber вызывается метод createSubscriber
интерфейса TopicSession с передачей ему Topic. Следующий код демонстрирует,
как можно создать объект TopicSubscriber:
Context context = new InitialContextO;
TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory)
context.lookup("TopicConnectionFactory");
Topic topic = (Topic) context.lookup(topicName);
TopicConnection topicconnection =
topicConnectionFactory.createTopicConnection();
TopicSession topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
TopicSubscriber topicSubscriber = topicSession.createSubscriber(topic);
Message
В JMS существуют различные типы сообщений:
• TextMessage. Сообщение, содержащее строку.
• MapMessage. Сообщение, содержащее пары имя/значение. Каждое имя
является объектом String, а каждое значение является примитивным ти-
пом Java.
• BytesMessage. Сообщение, содержащее поток неинтерпретированных
байтов.
• StreamMessage. Сообщение, содержащее поток примитивных значений
Java.
• ObjectMessage. Сообщение, содержащее сериализованный объект.
Служба сообщений Java
789
Queue queue = (Queue) context.lookup(queueName);
Используя объект QueueConnectionFactory, мы вызываем его метод
createQueueConnection для создания объекта QueueConnection:
queueConnection = queueConnectionFactory.createQueueConnection();
Затем можно получить объект QueueSession, вызвав метод createQueueSession
объекта QueueConnection:
QueueSession queueSession = queueConnection.createQueueSession
(false, Session.AUTO.ACKNOWLEDGE);
Чтобы получить сообщение из очереди, потребуется объект Queue Receiver.
Он создается в методе create Receiver объекта QueueSession, куда передается
объект Queue, полученный при втором поиске JNDI:
QueueReceiver queueReceiver = queueSession.createReceiver(queue);
Для получения сообщения вызывается метод receive объекта QueueReceiver:
Message message = queueReceiver.receive(1);
Затем проверяется, что сообщение не null и что сообщение является объек-
том TextMessage. Если это так, выводится текст объекта TextMessage с помо-
щью вызова его метода getText:
if (message != null) {
if(message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System, out. println(textMessage. getTextO);
}
}
Заключение
В этой главе вы познакомились с основами JMS, в том числе с архитектурой и
объектной моделью JMS. Были разработаны два клиентских приложения.
Теперь можно перейти к созданию EJB, управляемых сообщениями.
786
Глава 32
MyQueue. Код листинга 32.2 получает сообщение и выводит его на консоли
(Console). Перед выполнением второго клиентского приложения необходимо
выполнить первое.
Л истинг 32.1 Отправитель сообщения клиентского приложения JMS
package com.brainysoftware.ejb;
import javax.jms.*;
import javax.naming.*;
public class MessageSender {
public static void main(String[] args) {
QueueConnection queueConnection = null;
try {
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context.lookup("QueueConnectionFactory”);
String queueName = "MyQueue";
Queue queue = (Queue) context.lookup(queueName);
queueConnection =
queueConnectionFactory.createQueueConnection();
QueueSession queueSession =
queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender queueSender = queueSession.createSender(queue);
TextMessage message = queueSession.createTextMessageO;
message.setText("This is a TextMessage");
queueSender.send(message);
System.out.println("Message sent.");
}
catch (NamingException e) {
System.out.println("Naming Exception");
}
catch (JMSException e) {
System.out.println("JMS Exception");
}
finally {
if (queueConnection != null) {
t ry {
queueConnection.close();
}
catch (JMSException e) {}
}
}
}
}______________________________________________________________________
EJB, управляемые сообщениями
791
приложения получают доступ к EJB, управляемому сообщениями, через JMS,
посылая сообщения по месту назначения JMS.
Интерфейс прикладного программирования
Класс EJB, управляемого сообщениями, должен быть открытым классом и
должен реализовать следующие два интерфейса:
• javax.ejb. Message Driven Bean
• javax.jms.MessageListener
В дополнение к этому необходим интерфейс MessageDrivenContext, который
предоставляется контейнером EJB для экземпляра EJB, управляемого сообще-
ниями. Ниже рассматриваются интерфейсы javax.ejb.MessageDrivenBean и
javax. MessageDrivenContext. Ojavax.jms.MessageListenerpaccKa3biB^ocbBMaee 32.
Интерфейс javax.ejb.MessageDrivenBean
Интерфейс javax.ejb. Message Driven Bean имеет следующую сигнатуру:
public interface MessageDrivenBean extends EnterpriseBean
Он определяет два метода: set Message Driven Context и ejbRemove. Сигнату-
ры этих методов: y
public void setMessageDrivenContext(MessageDrivenContext context)
throws EJBException
public void ejbRemoveO throws EJBException
Метод setMessageDrivenContext задает связанный контекст, управляемый
сообщениями, а метод ejbRemove вызывается контейнером EJB перед завер-
шением жизни объекта EJB, управляемого сообщениями.
Интерфейс javax.ejb.MessageDrivenContext
Интерфейс javax.ejb. MessageDrivenContext обеспечивает доступ к управля-
емому сообщением контексту среды выполнения, который предоставляется
контейнером экземпляру EJB, управляемому сообщениями. Контейнер пере-
дает интерфейс MessageDrivenContext в экземпляр после создания экземпля-
ра. Контекст, управляемый сообщениями, остается связанным с экземпляром
в течение жизненного цикла экземпляра.
MessageDrivenContext является производным от интерфейса EJBContext и
имеет следующую сигнатуру:
788
Глава 32
Листинг 32.2. Продолжение
try {
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context. lookup(’’QueueConnectionFactory”);
String queueName = ’’MyQueue’’;
Queue queue = (Queue) context.lookup(queueName);
queueConnection =
queueConnectionFactory.createQueueConnection();
QueueSession queueSession = queueConnection.createQueueSession(false,
Session.AUTO.ACKNOWLEDGE);
QueueReceiver queueReceiver = queueSession.createReceiver(queue);
queueConnection.start();
Message message = queueReceiver.receive(1);
if (message •= null) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println(textMessage.getText());
}
}
}
catch (NamingException e) {
System.out.println("Naming Exception");
}
catch (JMSException e) {
System.out.println("JMS Exception");
}
finally {
if (queueConnection != null) {
try {
queueConnection.close();
}
catch (JMSException e) {}
}
}
}
}
Код листинга 32.2 используется для получения сообщения от провайдера
JMS. Как и в листинге 32.1, сначала выполняются два поиска JNDI для полу-
чения объектов QueueConnectionFactory и Queue:
Context context = new InitialContextO;
QueueConnectionFactory queueConnectionFactory =
(QueueConnectionFactory) context.lookup("QueueConnectionFactory");
String queueName = "MyQueue";
EJB, управляемые сообщениями
793
Класс управляемого сообщениями EJB
В отличие от EJB сеанса и EJB сущности, управляемый сообщениями EJB
не имеет домашнего или удаленного интерфейса. Единственным классом Java,
который необходимо написать для управляемого сообщениями EJB, является
сам класс EJB. Код управляемого сообщениями EJB с именем MyMDB пред-
ставлен в листинге 33.1.
Листинг 33.1 Управляемый сообщениями EJB MyMDB
package com.brainysoftware.ejb;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class MyMDB implements MessageDrivenBean, MessageListener {
MessageDrivenContext context = null;
QueueConnection connection;
QueueSession session;
public MyMDBO {
System.out.printin("Const ructing MyMDB");
}
public void setMessageDrivenContext(MessageDrivenContext context) {
this.context = context;
System.out.println("setMessageDrivenContext");
}
public void ejbCreateO throws EJBException {
System.out.printIn("ejbCreate");
try {
InitialContext initContext = new InitialContextO;
QueueConnectionFactory qcf = (QueueConnectionFactory)
initContext.lookup("j ava:comp/env/j ms/QCF");
33
EJB, управляемые
сообщениями
главе 32 говорилось о службе обмена сообщениями и службе сообще-
ний Java (JMS). Эти базовые сведения позволяют работать с третьим типом
EJB: EJB, управляемыми сообщениями (MDB, message-driven bean). EJB, уп-
равляемые сообщениями, — новый тип EJB, добавленный в спецификацию
EJB 2.0. Модель MDB создана для обеспечения асинхронного вызова EJB с
целью управления обработкой входящих сообщений JMS. С помощью двух
других типов EJB можно посылать и получать сообщения только синхронно.
Эта глава начинается с определения EJB, управляемого сообщениями. Под-
робно рассматривается объектная модель. Затем приводится пример EJB, уп-
равляемого сообщением, и даются инструкции по его развертыванию в JBoss.
\
Введение в EJB, управляемые
сообщениями
Для клиента (и соответственно для службы сообщений) EJB, управляемый
сообщениями, является асинхронным потребителем сообщений. Можно за-
метить, что EJB, управляемый сообщениями, аналогичен EJB сеанса без со-
стояния в том, что EJB, управляемый сообщениями, не имеет диалогового
состояния. EJB, управляемый сообщениями, создается и управляется кон-
тейнером EJB. Однако, в отличие от EJB сеанса и ЕЛВсущности, EJB, управляемые
сообщениями, не имеют домашнего или удаленного интерфейса. Клиентские
EJB, управляемые сообщениями
795
После получения объекта QueueConnectionFactory используем его метод
createQueueConnection для создания объекта QueueConnection:
connection = qcf .createQueueConnection();
С помощью метода createQueueSession интерфейсаQueueConnection создаем
объект QueueSession:
session = connection.createQueueSession(false,
QueueSession.AUTO_ACKNOWLEDGE);
Отметим, что методу createQueueSession в качестве первого аргумента пе-
редается false для указания, что не создается транзакционный объект сеанса.
Наконец запускаем объект QueueConnection, вызывая его метод start:
connection.sta rt();
Важным является также метод onMessage, который наследуется от интер-
фейса Message Listener. Этот метод получает объект javax.jms. Message, представ-
ляющий поступающее сообщение:
public void onMessage(Message msg) {
}
Код предполагает, что сообщение является объектом TextMessage, и пыта-
ется преобразовать сообщение в объект TextMessage:
try {
TextMessage message = (TextMessage) msg;
Интерфейс Message имеет метод getJMSReplyTo, который возвращает
объект javaxjms.Destination. Так как это область действия точка-точка, объект
Destination должен быть объектом Queue:
Queue queue = (Queue) msg.getJMSReplyToO;
Затем можно вызвать метод createSender интерфейса QueueSession, передав
ему объект Queue. Этот метод возвращает создателя сообщения, который яв-
ляется QueueSender:
QueueSender sender = session.createSender(queue);
792
Глава 33
public interface MessageDrivenContext extends EJBContext
Интерфейс MessageDrivenContext предлагает методы:
• getRollbackOnly. Проверяет, что транзакция была помечена «только для
отката». Этот метод может вызываться только управляемым сообщени-
ями EJB с управляемым контейнером разграничением транзакций.
• getUserTransaction. Получает интерфейс разграничения транзакций. Толь-
ко EJB с транзакциями, управляемыми EJB, могут использовать интер-
фейс UserTransaction.
• isCallerlnRole. Проверяет, что вызывающая сторона имеет заданную роль
системы безопасности.
• setRollbackOnly. Помечает транзакцию «только для отката». Этот метод
может вызываться только EJB, управляемыми сообщениями, с управляе-
мым контейнером разграничением транзакций.
• getCallerPrincipal, getEJBHome, getEJBLocalHome. Эти методы наследуются
от интерфейса EJBContext и не должны вызываться экземпляром управ-
ляемого сообщениями EJB.
Доступ к управляемому сообщениями EJB
Для получения доступа к управляемому сообщениями EJB клиентское при-
ложение извлекает объект javax.jms.Queue при выполнении поиска JNDI. Сле-
дующий код иллюстрирует, как можно получить ссылку на очередь с именем
MyQueue:
Context context = new InitialContextO;
Queue queue = (Queue) context.lookup("java:com/env/jms/MyQueue”);
Создание управляемого сообщениями EJB
Следующий пример (см. листинг 33.1) является управляемым сообщениями
EJB с именем MyMDB. Это простой управляемый сообщениями EJB, кото-
рый прослушивает очередь и при поступлении сообщения извлекает сообще-
ние и посылает другой объект TextMessage в очередь. Пример состоит из пяти
частей:
• Определение класса управляемого сообщениями EJB
• Создание дескриптора развертывания
• Конфигурирование JBoss для развертывания управляемого сообщения-
ми EJB
• Упаковка EJB
• Создание клиентского приложения
EJB, управляемые сообщениями
797
Отметим, что в элементе <tnessage-driven> содержится следующий элемент
<acknowledge-mode>, который имеет значение AUTO_ACKNOWLEDGE.
Конфигурирование файла jboss.xml
Специфическим для развертывания управляемого сообщениями EJB в JBoss
является необходимость конфигурирования файлаjboss.xml (см. листинг 33.3).
Л истин г 33.3 Файл jboss.xml
<?xml version=”1.0" encoding=”UTF-8”?>
<jboss>
<enterprise-beans>
<message-driven>
<ej b-name>MyMDB</ej b-name>
<destination-jndi-name>queue/MyQueue</destination-jndi-name>
<resource-ref>
<res-ref-name>jms/QCF</res-ref-name>
<j ndi-name>QueueConnectionFactory</j ndi-name>
</resource-ref>
</message-driven>
</enterprise-beans>
</jboss>
Файл jboss.xml необходим по двум причинам. Первая, дескриптор развер-
тывания не определяет имени очереди, прослушиваемой EJB MyMDB. Эта
информация должна помещаться в файл jboss.xml. Управляемые сообщения-
ми EJB всегда должны сопровождаться файлом jboss.xml для спецификации
имени места назначения, из которого EJB будет получать сообщения. Вторая
причина состоит в том, что необходимо отобразить ссылку JMS
QueueConnectionFactory в дескрипторе развертывания на развернутое JNDI-
имя JBossMQ QueueConnectionFactory.
Упаковка управляемого сообщениями EJB
Теперь можно упаковать EJB. Для этого:
1. Откомпилируйте все классы Java и поместите файлы .class в каталог сот/
brainysoftware/ejb/ каталога проекта.
2. Перейдите в каталог проекта.
3. Введите следующую команду:
jar cfv mdb.jar com/brainysoftware/ejЬ/MyMDB.class META-INF/ejb-jar.xml
794
Глава 33
Листинг 33.1 Продолжение
connection = qcf.createQueueConnection();
session = connection.createQueueSession(false,
QueueSession.AUTO.ACKNOWLEDGE); 4
connection.start();
}
catch(Exception e) {
throw new EJBException("Failed to initialize MyMDB", e);
}
}
public void ejbRemoveO {
System.out.printin("ej bRemove");
context = null;
try {
if( session != null )
session.close();
if( connection != null )
connection.close();
}
catch(JMSException e) {
e.printStackTraceO;
}
}
public void onMessage(Message msg) {
System.out.println("onMessage");
try {
TextMessage message = (TextMessage) msg;
Queue queue = (Queue) msg.getJMSReplyToO;
QueueSender sender = session.createSender(queue);
TextMessage message2 = session.createTextMessage(message.getTextO);
sender.send(message2);
sender.close();
}
catch(Exception e) {
e. printStackTraceO;
}
}
}
Рассмотрим метод ejbCreate класса управляемого сообщениями EJB. Пер-
вой строкой кода после вывода имени метода на консоль является блок try,
который пытается получить JNDI-объект InitialContext и выполнить поиск
JNDI, чтобы получить объект javax.jms.QueueConnectionFactory:
try {
InitialContext initContext = new InitialContextO;
QueueConnectionFactory qcf = (QueueConnectionFactory)
initContext.lookup("java:comp/env/j ms/QCF");
IV
Приложения
А Установка и конфигурирование Tomcat
В Справочник по пакету javax.servlet
С Справочник по пакету javax.servlet.http
D Справочник по пакету javax.servlet.jsp
Е Справочник по пакету javax.servlet.jsp.tagext
F Установка и конфигурирование JBoss
G Дополнительные ресурсы
796
Глава 33
На основе полученного сообщения создаем объект TextMessage, передавая
текст для TextMessage в метод createTextMessage объекта QueueSession:
TextMessage message? = session.createTextMessage(message.getTextO);
Теперь все готово для отправки нового сообщения. Это делается с помо-
щью метода send объекта QueueSender:
sender.send(message?);
Наконец вызывается метод close для закрытия объекта QueueSender:
sender.close();
Дескриптор развертывания
Подобно другим приложениям EJB, реализующим EJB сеанса и сущности,
для развертывания управляемого сообщениями EJB требуется дескриптор
развертывания. Дескриптор развертывания (файл ejb-jar.xml) для этого при-
ложения представлен в листинге 33.2.
Листинг 33.2 Дескриптор развертывания
<?xml version="1.0"?>
<! DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ej b-jar_2_0.dtd">
<ejb-jar>
<enterprise-beans>
<message-driven>
<ej b-name>MyMDB</ejb-name>
<ejb-class>com.brainysoftware.ejb.MyMDB</ejb-class>
<transaction-type>Container</transaction-type>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<message-driven-destination>
<destination-type>javax.jms:Queue</destination-type>
</message-d riven-destination>
<resource-ref>
<res-ref-name>jms/QCF</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</message-driven>
</enterprise-beans>
Установка и
конфигурирование
Tomcat
-I omcal является наиболее популярной эталонной реализацией для сервлетов
и JSP. Во время создания этой книги последней версией Tomcat была версия 4.0.3,
которая реализует API Servlet 2.3 и JSP 1.2. Для выполнения сервлетов и страниц
JSP необходимо использовать Tomcat или другую эталонную реализацию.
В этом приложении рассматриваются установка и конфигурирование Tomcat в
Windows NT/2000, UNIX и Linux.
Установка Tomcat
Tomcat написан на Java и поэтому требует компилятора Java. Первый шаг состоит в
загрузке и установке J DK. Tomcat работает с J DK 1.2 и более поздней версии. Каталог,
где установлен JDК, обозначается %JAVA_HOME%. Следующие шаги варьируются
в зависимости от операционной системы, где устанавливается Tomcat.
ГПоимечание^ Если вы знакомы с предыдущими версиями Tomcat, то увидите,
.... 1 " что ycTanoBKaTomcat 4.0.x значительно проще по сравнению с
предыдущими версиями.
Установка в Windows NT/2000
Ключевым моментом при установке Tomcat 4.0.x в Windows NT/2000 является
настройка переменной окружения JAVA_HOME. Чтобы установить Tomcat в
Windows NT/2000, выполните следующие действия:
798
Глава 33
Эта команда создает файл .jar с именем mdb.jar в каталоге проекта. Скопи-
руйте этот файл в каталог deploy в JBoss. Если JBoss не выполняется, запустите
его. Если J Boss уже работает, то ничего делать не надо. Средство горячего раз-
вертывания JBoss автоматически попытается развернуть любой файл пакета,
помещенный в каталог deploy.
Создание клиентского приложения
Для вызова развернутого EJB, управляемого сообщениями, требуется
клиентское приложение. Для этого можно использовать приложение, раз-
работанное в главе 32.
Заключение
В этой главе вы узнали, как создавать управляемые сообщениями EJB, ре-
ализуя интерфейсы javax.ejb. MessageDrivenBean и javax.jms. Message Listener.
Говорилось также о том, как разработать приложение EJB, которое использует
управляемый сообщениями EJB, и как развернуть его на сервере приложений
JBoss.
Установка и конфигурирование Tomcat
803
Рис. А.2. Диалоговое окно переменных окружения
Рис. А.З. Диалоговое окно New System Variable
Чтобы запустить Tomcat, перейдите в каталогЫп в %CATAL1NA_HOME%, вве-
дите sturtup и нажмите Enter. Л ибо можно использовать команду:
%CATALINA_HOME%\bin\startup
Tomcat запускается в новом консольном окне. Для остановки Tomcat закройте
консольное окно.
При тестировании приложений сервлетов и JSP нужно будет часто запускать и
останавливать Tomcat. Для удобства можно создать следующий пакетный файл и
поместить его на рабочий стол:
cd С:\
cd InstallDirectory\bin
startup
В этом коде Install Directory является каталогом, где установлен Tomcat.
Теперь можно запускать Tomcat двойным щелчком мыши назначке пакетного
файла.
Установка и конфигурирование Tomcat
805
2. Поместите файл установки в каталог %CATAL1NA_HOME%.
3. Добавьте переменную окружения JAVA_HOME. Если используется оболочка
bash, введите следующую команду:
JAVA_HOME=/usr/local/jdk1.3
export JAVA.HOME
Если используется tcsh, введите команду:
setenv JAVA_HOME /usr/local/jdkl.3
Установка Tomcat завершена. Чтобы запустить Tomcat, выполните файл startup.sh
в каталоге bin в %CATALINA_HOME%. Например, находясь в каталоге bin, можно
ввести ./startup.sh.
Для остановки Tomcat выполните файл shutdown.sh в каталоге bin в
%CATALINA_HOME%. Например, находясь в каталоге bin, введите ./shutdown.sh.
Чтобы проверить, правильно ли установлен Tomcat, откройте браузер на ком-
пьютере, где был установлен Tomcat, и направьте его по URL:
http://localhost:8080
Отметим, что нельзя опускать часть http:// в URL. Должно появиться изобра-
жение, аналогичное представленному на рис. А.4.
Если компьютер соединен с сетью, можно открыть браузер на другом компью-
тере и направить его по URL:
http://hostName:8080
Здесь hostHame — имя компьютера, где установлен Tomcat.
Каталоги Tomcat
При установке Tomcat программа создает ряд каталогов в %CATALINA__HOM Е%.
Понимание того, какие функции выполняют эти каталоги, является важным для
конфигурирования Tomcat и развертывания приложений сервлетов и JSR
Каталоги в %CATALINA_HOME% приведены в таблице А. 1.
Таблица А.1. Подкаталоги в %CATALINA HOME%
Каталог Описание
bin Сценарии запуска и останова и другие файлы.
classes Распакованные классы, глобальные для web-приложений
conf Конфигурационные файлы, включая server.xml (основной
конфигурационный файл Tomcat) и глобальный файл
web.xml (дескриптор развертывания)
server Архивные файлы Tomcat
lib Общие классы в файлах .jar
802
Приложение А
1. Загрузите двоичный код Tomcat с web-сайта Apache Software Foundation по
адресу http://jakarta.apache.Org/builds/jakarta-tomcat-4.0/release/.
Загружайте самую последнюю рабочую версию. При установке в Windows
проще всего загрузить файл zip. Можно распаковать этот файл с помощью
любого архиватора, такого как WinZip. При желании можно загрузить файл
любого другого формата.
2. Распакуйте сжатый файл в каталог %CATALINA_HOME%.
3. Теперь необходимо добавить переменную окружения JAVA_HOME. Открой-
те панель управления (Control Panel) и дважды щелкните мышью на апплете
System.
4. Перейдите на вкладку Advanced (см. рис. А.1).
Рис. А. 1. Вкладка Advanced апплета System в панели управления
5. Нажмите кнопку Environment Variables. Должен появиться экран, показанный
нарис. А.2. Он содержит два подраздела: список переменных окружения для
текущего пользователя и список системных переменных окружения. Добавьте
новую переменную во второй список, чтобы другие пользователи могли ра-
ботать с Tomcat.
6. Нажмите кнопку New в разделе системных переменных. Появится диалого-
вое окно New System Variable (Новая системная переменная) (см. рис. А.З).
7. В поле Variable Name (Имя переменной) введите JAVA_HOME. В поле Variable
Value (Значение переменной) введите каталог установки JD К. Например, если
JDK установлен в каталог C:\jdkl .3, введите C:\jdkl .3.
8. Нажмите ОК.
Tomcat готов к работе.
Справочник
по пакету javax.servlet
пецификация сервлетов 2.3 определяет два пакета расширения Java для раз-
работчиков, использующих технологию сервлетов: пакет javax.servlet и пакет
javax.servlet.http. Первый пакет предоставляет базовые классы и интерфейсы, неза-
висимые от протокола, а второй пакет содержит классы и интерфейсы, специфи-
ческие для HTTP.
Это приложение предоставляет полный справочник по пакету javax.servlet. Па-
кет javax.servlet.http рассматривается в приложении С. Для краткости класс или
интерфейс, который принадлежит пакету javax.servlet, записывается без указания
полностью квалифицированного имени.
naKeTjavax.servlet содержит двенадцать интерфейсов, семь классов и два исклю-
чения. Нарис. В.1 показана диаграмма иМЬддя этого пакета.
Таблицы В.1, В.2 и В.З содержат описания интерфейсов, классов и исключений
пакета.
Таблица В.1. Интерфейсы пакета javax.servlet
Интерфейс
Filter
Описание
Этот интерфейс представляет фильтр. Фильтр
перехватывает запрос, прежде чем он будет
обработан, и может выполнять задания
применительно к запросу, ответу или к обоим.
804
Приложение А
Чтобы проверить, правильно ли установлен Tomcat, откройте браузер натом же
компьютере, который использовался для установки Tomcat, и введите в нем следу-
ющий URL:
http://localhost:8080
Требуется 8080, так как Tomcat выполняется на порту 8080
по умолчанию. Отметим, что нельзя опускать часть http:// в
URL.
Должно появиться изображение, показанное на рис. А.4.
$
http^Aocrfvtt:«»Q№dKc.HM
Tomcat
Version 4X)J-b1
The Jakarta Project
http://jikarta.ap «ch «.org*
If you're seeing this page via a web browser, It means you've
setup Tomcat successfully. Congratulations!
As you may have guessed by now, ttiis is the default Tomcat home page.
It can be found on the local filesystem at
SCATALINA_HOME/wabappa/ROOT/index. htal
where "$CATALINA_HOME" is the root of the Tomcat installation
directory. V you're seeing this page, and you don't think you should be.
then either you're either a user who has arrived at new installation of
Tomcat, or you're an administrator who hasn't got his/her setup quite
right. Providing the latter is the case, please refer to the Tomcat
Documentation for more detailed setup and administration information
than is found in the INSTALL file.
Included with this release are a host of sample Servlets and JSPs (with
associated source code), extensive documentation (including the Servlet
Рис. A.4. Успешная установка Tomcat
Если компьютер соединен с сетью, можно открыть браузер на другом компью-
тере и направить его по URL:
http://hostName:8080
Здесь hostName — имя компьютера, на котором установлен Tomcat.
Установка в UNIX/Linux
При установке в UNIX/Linux ключевым моментом также является создание
переменной окружения JAVA_HOME. Предположим, что JDK установлен в каталоге
/usr/local/jdkl .3. Если JDK и Tomcat устанавливаются в другие каталоги, необходимо
соответственно изменить код. Следующие шаги определяют процесс установки Tomcat
на компьютере UNIX/Linux:
1. Загрузите Tomcat с web-сайта Apache Software Foundation по адресу
http://j aka rta.apache.org.
Справочник по пакету javax.servlet
809
Таблица В.2. Классы пакета javax.servlet
Класс
Описание
GenericServlet Абстрактный класс, который реализует
интерфейс Servlet и интерфейс ServletConfig.
Этот класс расширяют для создания
независимого от протокола сервлета.
ServletContextAttributeEvent Класс события для уведомления об изменениях
атрибутов объекта ServletContext.
ServletContextEvent Класс события для уведомления об изменениях
в объекте ServletContext.
Servlet 1 nputStream
ServletOutputStream
ServletRequestWrapper
ServletResponse Wrapper
ServletException
U navailable Exception
Представляет входящий поток для чтения
двоичных данных из запроса клиента.
Представляет выходящий поток для отправки
двоичных данных клиенту.
Предоставляет оболочку для удобства работы
с интерфейсом ServletRequest.
Предоставляет оболочку для удобства работы
с интерфейсом ServletResponse.
Базовое исключение, порождаемое сервлетом.
Исключение, порождаемое, когда сервлет или
фильтр недоступен.
Интерфейсы
Ниже представлены интерфейсы пакетаjavax.servlet. Описываются каждый из ин-
терфейсов, его сигнатура и методы, если они есть.
Filter
Интерфейс Filter представляет фильтр. Этот интерфейс реализуется для создания
фильтра, который перехватывает запрос и выполняет задание применительно к зап-
росу и/или ответу.
Сигнатура
public interface Filter
806
Приложение А
Таблица А. 1. Продолжение
Каталог Описание
logs Файлы журналов Tomcat
common Общие классы для Catalina и web-приложений
webapps Приложения сервлетов и JSP
work Сервлеты, получающиеся при трансляции страниц JSP
Смена портов
По умолчанию Tomcat выполняется на порту 8080. Пользователи должны вводить
номер порта :8080 после части домена в URL. Фактически пользователи должны
всегда вводить номер порта, за исключением случая, когда Tomcat выполняется на
порту 80 — порт HTTP по умолчанию.
Можно отредактировать конфигурационный файл Tomcat server.xml, располо-
женный в каталоге conf в %CATAL1NA_HOME%.
Сначала нужно найти строки:
<!- Define a non-SSL НТТР/1.1 Connection on port 8080 ->
<Connectior className="org.apache.cataline.connector.http.HttpConnector”
port=”8080" minProcessors=',5" maxProcessors=”75”
enableLookups=”true” redirectPort="8443"
acceptCount=’'10" debug=”O" connectionTimeout="60000"
В третьей строке имеется атрибут port со значением 8080. Измените это значе-
ние на 80 и сохраните файл.
Необходимо перезапустить Tomcat, чтобы эти изменения вступили в действие.
Теперь можно вызывать любую страницу, не включая в URL номер порта.
На платформе UNIX/Linux для выполнения процесса с использованием номе-
ров порта меньше 1024 необходимо зарегистрироваться как суперпользователь.
Отметим, что вы не должны изменять порт Tomcat на 80, если собираетесь вы-
полнять Tomcat вместе с другими web-серверами (см. ниже).
Справочник по пакету javax.servlet
811
Сигнатура
public interface FilterChain
Методы
public void doFilter(ServletRequest request, ServletResponse response)
throws java.io.lOException, ServletException
Приводит к вызову следующего фильтра в цепочке. Если вызывающий фильтр
является последним в цепочке, он обращается к ресурсу в конце цепочки.
FilterConfig
Объект конфигурации фильтра, который передает информацию от web-контейне-
ра фильтру.
Сигнатура
public interface FilterConfig
Методы
public java.lang.String getFilterNameO
Возвращает имя фильтра, определенного в дескрипторе развертывания прило-
жения.
public java.lang.String get!nitParameter(java.lang.String name)
Возвращает знамен неуказанного инициализационного параметра или null, если
параметр не существует.
public java. util. Enumeration getlnitParameterNamesO
Возвращает все имена инициализационных параметров фильтра.
public ServletContext getServletContext()
Возвращает объект ServletContext.
RequestDispatcher
Определяет объект, который направляет запрос другому динамическому ресур-
су, такому как сервлет или страница JSP.
Сигнатура
public interface RequestDispatcher
Методы
public void forward(ServletRequest request, ServletResponse response)
throws java.io.lOException, ServletException
Пересылает запрос сервлета другому статическому или динамическому ресурсу.
808
Приложение В
Таблица В.1. Продолжение
FilterChain
Контейнер сервлетов создает объект FilterChain
для создания представления цепочки вызовов
фильтруемого запроса ресурса.
FilterConfig
Контейнер сервлетов создает объект FilterConfig,
с помощью которого можно получить такую
информацию, как имя фильтра и начальные
параметры.
RequestDispatcher
Определяет объект, который направляет запрос
другому динамическому ресурсу, такому как
сервлет или страница JSR
Servlet
Основной интерфейс, который должны
реализовывать все сервлеты прямо или косвенно.
ServletConfig
Представляет объект конфигурации сервлета,
с помощью которого можно получить такую
информацию, как имя сервлета, начальные
параметры и объект ServletContext.
ServletContext
Объект ServletContext является интерфейсом
между контейнером сервлетов и сервлетом.
Один объект ServletContext существует для
каждого web-приложения в виртуальной
машине Java. Если все приложение
располагается водной виртуальной машине Java,
объект ServletContext может обеспечить
совместное использование глобальной
информации, которая будет доступна любому
ресурсу в приложении.
ServletContextAttributeListener Класс, реал изующи й этот и нтерфейс,
используется для получения уведомления
об изменении атрибута объекта ServletContext.
ServletContextListener
Класс, реализующий этот интерфейс,
используется для получения уведомления
об изменениях в объекте ServletContext.
ServletRequest
ServletResponse
SingleThreadModel
Представляет запрос web-клиента.
Представляет ответ сервлета web-клиенту.
Этот интерфейс реализуется сервлетом
для обеспечения того, что сервлет обрабатывает
только один запрос за раз.
Справочник по пакету javax.servlet
813
Методы
public java.lang.String get!nitParameter(java.lang.String name)
Возвращает значение определенного инициализационного параметра или null,
если параметр не существует.
public java.util.Enumeration getlnitParameterNamesO
Возвращает Enumeration, содержащее все имена параметров сервлета.
public ServletContext getServletContextO
Возвращает объект ServletContext.
public java.lang.String getServletNameO
Возвращает имя сервлета, как указано в дескрипторе развертывания приложения.
ServletContext
Объект ServletContext является интерфейсом между контейнером сервлетов и
сервлетом. Существует один объект ServletContext для каждого web-приложения
виртуальной машины Java (JVM). Втех случаях, когда все приложение располагается в
одной JVM, объект ServletContext можно использовать для общего доступа к
глобальной информации, которая будет доступна любому ресурсу в приложении.
Сигнатура
public interface ServletContext
Методы
public java.lang.Object getAttribute(java.lang.String name)
Возвращает атрибут объекта ServletContext, имя которого определено в качестве
аргумента.
public java.util.Enumeration getAttributeNamesO
Возвращает Enumeration, содержащее все имена атрибутов в ServletContext.
public ServletContext getContext(java.lang.String uri)
Возвращает объект ServletContext, соответствующий указанному URI.
public java.lang.String get!nitParameter(java.lang.String name)
Возвращает значение начального параметра в ServletContext по указанному имени
параметра.
public java.util.Enumeration getlnitParameterNamesO
Возвращает Enumeration, содержащее все имена параметров в ServletContext.
810
Приложение В
Рис. В.1. Диаграмма UML для пакета javax.servlet
Методы
public void destroyO
Контейнер Web вызывает этот метод, когда фильтр должен быть исключен из
службы. Перед вызовом этого метода контейнер Web будет ждать, пока все потоки
выполнения в методе doFilter фильтра не закончатся или пока не истечет некоторый
интервал ожидания. После вызова этого метода контейнер Web не будет больше
вызывать метод doFilter данного экземпляра фильтра.
Можно использовать этот метод для выполнения очистки, например для осво-
бождения объекта, закрытия файла и т. д.
public void doFilter(ServletRequest request, ServletResponse response,
^FilterChain chain) throws java.io.lOException, ServletException
Контейнер Web вызывает этот метод для передачи пары запрос/ответ по цепочке
к ресурсу, путь доступа к которому указан в запросе клиента в конце цепочки:
public void init(FilterConfig filterConfig) throws ServletException
Контейнер Web вызывает этот метод, когда фильтр должен приступить к работе.
FilterChain
Контейнер сервлетов создает объект FilterChain, чтобы обеспечить представление
цепочки вызова фильтруемого запроса ресурса.
Справочник по пакету javax.servlet
815
public java.lang.String getServletContextNameO
Возвращает значение элемента display-name, как определено в дескрипторе раз-
вертывания.
public java.util.Enumeration getServletNamesO
Этот метод опротестован. В API Servlet 2.3 он всегда возвращает пустой объект
Enumeration. Первоначально использовался для получения всех имен сервлетов.
public java.util.Enumeration getServletsO
Этот метод опротестован. Первоначально он извлекал все сервлеты приложе-
ния. В API Servlet 2.3 он всегда возвращает пустой объект Enumeration.
public void log(java.lang.Exception exception, java.lang.String message)
Этот метод опротестован. Он использовался первоначально для записи трасси-
ровки стека исключения и сообщения об ошибке в файл журнала сервлета.
public void log(java.lang.String message)
Записывает сообщение в файл журнала сервлета.
public void log(java.lang.String message, java. lang.Th rowable throwable)
Записывает сообщение об ошибке и трассировку стека для объекта Throwable,
определенного в качестве аргумента.
public void removeAttribute(java.lang.String name)
Удаляет атрибут, имя которого определено в качестве аргумента, из объекта
ServletContext.
public void setAttribute(java.lang.String name, java.lang.Object object)
Связывает объект с именем в объекте ServletContext. Если атрибут с таким име-
нем уже существует, значение атрибута будет заменено новым объектом.
ServletContextAttributeListener
Класс, реализующий этот интерфейс, используется для получения уведомле-
ния об изменении атрибута объекта ServletContext.
Сигнатура
public interface ServletContextAttributeListener extends
java.util.EventListener
Методы
public void attributeAdded(ServletContextAttributeEvent scae)
Этот метод вызывается после добавления атрибута в объект ServletContext.
812
Приложение В
public void include(ServletRequest request, ServletResponse response)
throws java.io.lOException, ServletException
Вставляет обработанные выходные данные другого статического или динамичес-
кого ресурса. По сути, делает то же, что и программный серверный подключаемый
модуль.
Servlet
Интерфейс Servlet является центральной абстракцией технологии сервлетов Java.
Все сервлеты должны реализовывать этот интерфейс прямо или косвенно.
Сигнатура
public interface Servlet
Методы
public void destroyO
Это один из методов жизненного цикла, который вызывается контейнером
сервлетов, когда сервлет исключается из службы.
public ServletConfig getServletConfigO
Возвращает объект ServletConfig сервлета, содержащий параметры инициали-
зации и запуска.
public java.lang.String getServletlnfoO
Возвращает информацию о текущем сервлете.
public void init(ServletConfig config)
Это один из методов жизненного цикла, вызываемый контейнером сервлетов,
когда сервлет приступает к работе.
public void service(ServletRequest request, ServletResponse response)
throws java.io.lOException, ServletException
Это один из методов жизненного цикла, вызываемый контейнером сервлетов
при запросе сервлета; позволяет сервлету ответить на запрос.
ServletConfig
Этот интерфейс представляет объект конфигурации сервлета, который передает
информацию из контейнера сервлетов в сервлет во время инициализации сервлета.
Сигнатура
public interface ServletConfig
Справочник по пакету javax.servlet
817
public java.lang.String getContentType()
Возвращает тип MIME тела запроса или null, если тип содержимого неизвестен.
public ServletlnputStream getlnputStreamO
Возвращает тело запроса как двоичные данные. Метод не должен вызываться,
если был вызван метод get Reader, и наоборот.
public java. util. Locale getLocaleO
Возвращает предпочтительный объект Locale для содержимого, которое будет
приниматься клиентом.
public java.util.Enumeration getLocalesO
Возвращает все объекты Locale, начиная с наиболее предпочтительной лока-
лизации, которые будет принимать клиент. Locale определяются в заголовке
Accept-Language. Если запрос не содержит этого заголовка, метод возвращает
локализацию для сервера.
public java.lang.String getParameter(java.lang.String name)
Возвращает значение параметра, имя которого определено в аргументе.
\
public java.util.Map getParameterMapO
Возвращает все параметры запроса в объекте java.util.Мар.
public java. util. Enumeration getParameterNamesO
Возвращает все имена параметров в запросе.
public java.lang.String[] getParameterValues(java.lang.String name)
Возвращает значения параметра, имя которого задано в качестве аргумента.
public java.lang.String getProtocolO
Возвращает имя и номер версии протокола, используемого для отправки запроса.
public java.io.BufferedReader getReader()
Возвращает тело запроса как сим вольные данные. Этот метод не должен вызы-
ваться после вызова метода getlnputStream, и наоборот.
public java.lang.String getRealPath(java.lang.String path)
Этот метод опротестован. Вместо него следует использовать метод getRealPath
и нтерфейса ServletContext.
public java.lang.String getRemoteAddr()
Возвращает IP-адрес компьютера клиента.
814
Приложение В
public int getMajorVersion()
Возвращает старшую версию API Servlet, которую поддерживает контейнер
сервлетов.
public java.lang.String getMimeType(java.lang.String file)
Возвращаеттип MIME файла, указанного в аргументе, или null, если тип MIME
неизвестен.
public int getMinorVersion()
Возвращает младшую версию API Servlet, которую поддерживает контейнер
сервлетов.
public RequestDispatcher getNamedDispatcher(java.lang.String name)
Возвращает объект оболочки RequestDispatcher для сервлета, имя которого
указано в аргументе.
public java.lang.String getRealPath(java.lang.String path)
Возвращает реальный путь доступа для виртуального пути, определенного
аргументом.
public RequestDispatcher getRequestDispatcher(java.lang.String path)
Возвращает объект оболочки RequestDispatcher для ресурса, путь доступа к
которому указан в качестве аргумента.
public java.net.URL getResource(java.lang.String path)
Возвращает URL ресурса, который отображается в путь доступа, определенный
в качестве аргумента. Путь доступа начинается с «/» и интерпретируется относи-
тельно корня текущего контекста.
public java.io.InputStream getResourceAsStream(java.lang.String path)
Возвращает ресурс, путь доступа к которому указан в качестве аргумента.
public java.util.Set getResourcePaths(java.lang.String path)
Возвращает список, содержащий все пути доступа к ресурсам в приложении,
самый длинный подпуть доступа которых соответствует пути доступа, определен-
ному аргументом.
public java.lang.String getServerlnfoO
Возвращает имя контейнера сервлетов и номер версии.
public Servlet getServlet(java.lang.String name) throws ServletException
Этот метод опротестован. В API Servlet 2.3 он возвращает null. В предыдущих
версиях этот метод возвращает сервлет из объекта ServletContext.
Справочник по пакету javax.servlet
819
public java.lang.String getCharacterEncodingO
Возвращает набор символов для тела MIME ответа.
public java.util.Locale getLocale()
Возвращает локализацию ответа.
public ServletOutputStream getOutputStreamO throws java.io.lOException
Возвращает объект ServletOutputStream для записи двоимиых данных в ответ. Этот
метод не должен вызываться после вызова метода getWriter, и наоборот.
public java.io.Printwriter getWriterO throws java.io.lOException
Возвращает объект PrintWriter для записи символьного текста, который будет
послан клиенту.
public boolean isCommittedO
Указывает, был ли завершен ответ. В завершенном ответе установлен код статуса
и записаны заголовки.
public void reset()
Очищает содержимое буфера, код статуса и заголовки.
public void resetBuffer()
Очищает содержимое буфера, не очищая код статуса и заголовки.
public void setBufferSize(int size)
Задает размер буфера ответа.
public void setContentLength(int length)
Задает длину тела содержимого ответа в сервлетах HTTP, устанавливая НТТР-
заголовок Content-Length.
public void setContentType(java.lang.String type)
Задает тип содержимого ответа.
public void setLocale(java.util.Locale locale)
Задает локализацию ответа.
SingleThreadModel
Этот интерфейс реализуется сервлетом для гарантии того, что сервлет обраба-
тывает только один запрос за раз. Этот интерфейс не имеет методов.
Сигнатура
public interface SingleThreadModel
816
Приложение В
public void attributeRemoved(ServletContextAttributeEvent scae)
Этот метод вызывается после удаления атрибута из объекта ServletContext.
public void attributeReplaced(ServletContextAttributeEvent scae)
Этот метод вызывается после замены атрибута в объекте ServletContext.
ServletContextListener
Класс, реализующий этот интерфейс, используется для получения уведомле-
ния об изменениях в объекте ServletContext.
Сигнатура
public interface ServletContextListener extends java.util.EventListener
Методы
public void contextDestroyed(ServletContextEvent see)
Этот метод вызывается как уведомление о том, что объект ServletContext будет
скоро разрушен, т. е. контейнер сервлетов будет отключен.
public void context!nitialized(ServletContextEvent see)
Этот метод вызывается как уведомление о том, что объект ServletContext будет
инициализирован, т. е. web-приложение готово обслуживать клиентские запросы.
Servlet Request
Представляет запрос web-клиента.
Сигнатура
public interface ServletRequest
Методы
public java.lang.Object getAttribute(java.lang.String name)
Получает атрибут, имя которого определено в аргументе, из ServletRequest. Этот
метод возвращает null, если не существует атрибута с указанным именем.
public java.lang.Enumeration getAttributeNamesO
Возвращает все имена атрибутов в объекте ServletRequest.
public java.lang.String getCharacterEncodingO
Возвращает кодировку, используемую в теле запроса.
public int getContentLength()
Возвращает длину тела запроса в байтах.
Справочник по пакету javax.servlet
821
public void log(java.lang.String message)
Записывает сообщение в файл журнала сервлета.
public void log(java.lang.String message, java.lang.Throwable throwable)
Записывает сообщение об ошибке и трассировку стека объекта Throwable,
переданного в качестве аргумента, в файл журнала сервлета.
public abstract void service(ServletRequest request, ServletResponse
response)
Контейнер сервлетов вызывает этот метод, чтобы разрешить сервлету обслужи-
вать клиента.
ServletContextAttributeEvent
Класс события, который служит для уведомления об изменениях в атрибутах
объекта ServletContext.
Сигнатура
public class ServletContextAttributeEvent extends ServletContextEvent
Методы
public java.lang.String getNameO
Возвращает имя изменившегося атрибута в ServletContext.
public java.lang.Object getValueO
Возвращает значение изменившегося атрибута в ServletContext.
ServletContextEvent
Класс события, который служит для уведомления об изменениях в ServletContext.
Сигнатура
public class ServletContextEvent extends java.util.Eventobject
Методы
public ServletContext getServletContextO
Возвращает изменившийся объект ServletContext.
ServletInputStream
Представляет входящий поток для чтения двоичных данных из запроса клиента.
Сигнатура
public abstract class ServletInputStream extends java.io.InputStream
818
Приложение В
public java.lang.String getRemoteHost()
Возвращает имя DNS компьютера клиента.
public RequestDispatcher getRequestDispatcher(java.lang.String path)
Возвращает объект оболочки RequestDispatcher для ресурса, путь Доступа к ко-
торому задан в качестве аргумента.
public java.lang.String getSchemeO
Возвращает протокол, используемый для этого запроса; например, http, https
или ftp.
public java.lang.String getServerName()
Возвращает имя хоста сервера, получающего запрос.
public int getServerPort()
Возвращает номер порта, на котором был получен запрос.
public boolean isSecureO
Указывает, был ли послан запрос с помощью защищенного канала, такого как
HTTPS.
public void removeAttribute(java.lang.String name)
Удаляет атрибут, имя которого определено в аргументе, из объекта ServletRequest.
public void setAttribute(java.lang.String name, java.lang.Object object)
Сохраняет пару имя/объект в объекте ServletRequest.
public void setCharacterEncoding(java.lang.String env)
4>throws java.io.UnsupportedEncodingException
Задает новую кодировку символов для тела запроса.
ServletResponse
Этот интерфейс представляет ответ сервлета web-клиенту.
Сигнатура
public interface ServletResponse
Методы
public void flushBuffer() throws java.io.lOException
Принудительно посылает содержимое буфера клиенту и очищает буфер.
public int getBufferSizeO
Возвращает размер буфера ответа.
Справочник по пакету javax.servlet
823
public void println(double d) throws java.io.lOException
Посылает клиенту double, за которым следуют возврат каретки и перевод строки.
public void println(float f) throws java.io.lOException
Посылает клиенту float, за которым следуют возврат каретки и перевод строки.
public void println(int i) throws java.io.lOException
Посылает клиенту int, за которым следуют возврат каретки и перевод строки.
public void println(long 1) throws java.io.lOException
Посылает клиенту long, за которым следуют возврат каретки и перевод строки.
public void println(String s) throws java.io.lOException
Посылает клиенту String, за которой следуют возврат каретки и перевод строки.
ServletRequestWrapper
Предоставляет оболочку для удобства работы с интерфейсом ServletRequest.
Сигнатура
public class ServletRequestWrapper implements ServletRequest
Методы
public java.lang.Object getAttribute(java.lang.String name)
Если не переопределен, то этот метод вызывает метод getAttribute объекта зап-
роса в оболочке.
public java.lang.Enumeration getAttributeNamesO
Если не переопределен, то этот метод вызывает метод getAttributeNames объекта
запроса в оболочке.
public java.lang.String getCharacterEncodingO
Если не переопределен, то этот метод вызывает метод getCharacterEncoding
объекта запроса в оболочке.
public int getContentLength()
Если не переопределен, то этот метод вызывает метод getContentLength объекта
запроса в оболочке.
public java.lang.String getContentType()
Если не переопределен, то этот метод вызывает метод getContentType объекта
запроса в оболочке.
820
Приложение В
Классы
Ниже приводятся классы пакетаjavax.servlet. Описываются каждый класс, его сиг-
натура и методы, если таковые имеются.
GenericServlet
Абстрактный класс, который реализует интерфейсы Servlet и ServletConfig. Этот
класс расширяют для создания независимого от протокола сервлета.
Сигнатура
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable
Методы
public void destroyO
Контейнер сервлетов вызывает этот метод для указания сервлета, который уда-
ляется из работы.
public java.lang.String get!nitParameter(java.lang.String name)
Возвращает значение начального параметра, имя которого определено в каче-
стве аргумента.
public java.util.Enumeration getlnitParameterNamesO
Возвращает все имена начальных параметров.
public ServletConfig getServletConfigO
Задает объект ServletConfig сервлета.
public ServletContext getServletContextO
Возвращает объект ServletContext.
public java.lang.String getServletlnfoO
Возвращает информацию о сервлете.
public java.lang.String getServletNameO
Возвращает имя сервлета.
public void initО throws ServletException
Этот метод предоставляется для удобства, чтобы программисту не нужно было
вызывать super.init(config).
public void init(ServletConfig config) throws ServletException
Контейнер сервлетов вызывает этот метод для указания сервлета, который будет
запущен в работу.
Справочник по пакету javax.servlet
825
public java.lang.String getRemoteHost()
Если не переопределен, то этот метод вызывает метод getRemoteHost объекта
за»|роса в оболочке.
public ServletRequest getRequestO
Возвращает объект запроса в оболочке.
public RequestDispatcher getRequestDispatcher(java.lang.String path)
Если не переопределен, то этот метод вызывает метод get RequestDispatcher объекта
запроса в оболочке.
public java.lang.String getSchemeO
Если не переопределен, то этот метод вызывает метод getScheme объекта запроса
в оболочке.
public java.lang.String getServerName()
Если не переопределен, то этот метод вызывает метод getServerName объекта
запроса в оболочке.
public int getServerPort()
Если не переопределен, то этот метод вызывает метод getServerPort объекта запроса
в оболочке.
public boolean isSecureO
Если не переопределен, то этот метод вызывает метод isSecure объекта запроса в
оболочке.
public void removeAttribute(java.lang.String name)
Если не переопределен, то этот метод вызывает метод removeAttribute объекта
запроса в оболочке.
public void setAttribute(java.lang.String name, java.lang.Object object)
Если не переопределен, то этот метод вызывает метод setAttribute объекта запроса
в оболочке.
public void setCharacterEncoding(java.lang.String env)
throws java.io.UnsupportedEncodingException
Если не переопределен, то этот метод вызывает метод setCharacterEncoding
объекта запроса в оболочке.
public void setRequest(ServletRequest request) throws
java.lang.IllegalArgumentException
Задает объект запроса в оболочке.
822
Приложение В
Методы
public int readLine(byte[] b, int off, int len) throws
java.io.lOException
Считывает входящий поток по одно строке за раз. Прочитанные байты помещают-
ся в байтовый массив. Метод возвращает -1, если он достигает конца входящего
потока до того, как прочитает максимальное число байтов.
ServletOutputStream
Представляет выходной поток для отправки двоичных байтов клиенту.
Сигнатура
public abstract class ServletOutputStream extends java.io.Outputstream
Методы
public void print(boolean b) throws java.io.lOException
Посылает клиенту булево значение.
public void print(char c) throws java.io.lOException
Посылает клиенту символ.
public void print(double d) throws java.io.lOException
Посылает клиенту double.
public void print(float f) throws java.io.lOException
Посылает клиенту float.
public void print(int i) throws java.io.lOException
Посылает клиенту int.
public void print(long 1) throws java.io.lOException
Посылает клиенту long.
public void print(String s) throws java.io.lOException
Посылает клиенту String.
public void println(boolean b) throws java.io.lOException
Посылает клиенту булево значение, за которым следуют возврат каретки и пере-
вод строки.
public void println(char с) throws java.ю.lOException
Посылает клиенту символ, за которым следуют возврат каретки и перевод
строки.
Справочник по пакету javax.servlet
827
public void resetBuffer()
Если не переопределен, то этот метод вызывает метод resetBuffer объекта ответа в
оболочке.
public void setBufferSize(int size)
Если не переопределен, то этот метод вызывает метод setBufierSize объекта
ответа в оболочке.
public void setContentl_ength(int length)
Если не переопределен, то этот метод вызывает метод setContentLength объекта
ответа в оболочке.
public void setContentType(java.lang.String type)
Если не переопределен, то этот метод вызывает метод setContentType объекта
ответа в оболочке.
public void setLocale(java.util.Locale locale)
Если не переопределен, то этот метод вызывает метод setLocale объекта ответа в
оболочке.
public void setResponse(ServletResponse response)
Задает ответ, который помещается в оболочку.
Исключения
Ниже приводятся исключения в пакетеjavax.servlet. Описываются каждое исклю-
чение, его сигнатура и методы, если они существуют.
ServletException
Базовое исключение, порождаемое сервлетом.
Сигнатура
public class ServletException extends java.lang.Exception
Метод
public java. lang. Th rowable getRootCauseO
Возвращает исключение, которое вызывало это ServletException.
UnavailableException
Исключение, порождаемое, если сервлет или фильтр недоступен.
824
Приложение В
public ServletlnputStream getInputStream()
Если не переопределен, то этот метод вызывает метод getlnputStream объекта
запроса в оболочке.
public java. util. Locale getLocaleO
Если не переопределен, то этот метод вызывает метод get Locale объекта запроса в
оболочке.
public java.util.Enumeration getLocales()
Если не переопределен, то этот метод вызывает метод getLocales объекта запроса
в оболочке.
public java.lang.String getParameter(java.lang.String name)..
Если не переопределен, то этот метод вызывает метод getParameter объекта
запроса в оболочке.
public java.util.Map getParameterMapO
Если не переопределен, то этот метод вызывает метод getParameterMap объекта
запроса в оболочке.
public java.util.Enumeration getParameterNamesO
Если не переопределен, то этот метод вызывает метод getParameterNames объекта
запроса в оболочке.
public java.lang.String[] getParameterValues(java.lang.String name)
Если не переопределен, то этот метод вызывает метод getParameterValues объекта
запроса в оболочке.
public java.lang.String getProtocolO
Если не переопределен, то этот метод вызывает метод getProtocol объекта запроса
в оболочке.
public java.io.BufferedReader getReader()
Если не переопределен, то этот метод вызывает метод getReader объекта запроса в
оболочке.
public java.lang.String getRealPath(java.lang.String path)
Если не переопределен, то этот метод вызывает метод getRealPath объекта зап-
роса в оболочке.
public java.lang.String getRemoteAddr()
Если не переопределен, то этот метод вызывает метод getRemoteAddr объекта
запроса в оболочке.
Справочник
по пакету javax.servlet.http
то приложение представляет пакетjavax.servlet.http, который определен вспе-
цификации Servlet 2.3. Это второй из двух пакетов, используемых при разработке
сервлетных приложений. Первый пакет, javax.servlet, описывается в приложении В.
Для краткости тип данных, принадлежащий пакетуjavax.servlet.http, записыва-
ется без указания полностью квалифицированного имени.
Пакет javax.servlet.http содержит восемь интерфейсов и семь классов.
На рис. С.1, показана диаграмма иМЬдля этого пакета.
826
Приложение В
ServletResponseWrapper
Предоставляет оболочку для удобства работы с интерфейсом ServletResponse.
Сигнатура
public class ServletRequestWrapper implements ServletResponse
Методы
public void flushBuffer() throws java.io.lOException
Если не переопределен, то этот метод вызывает метод flush Buffer объекта ответа в
оболочке.
public int getBufferSize()
Если не переопределен, то этот метод вызывает метод getBufferSize объекта
ответа в оболочке.
public java.lang.String getCharacterEncodingO
Если не переопределен, то этот метод вызывает метод getCharacterEncoding
объекта ответа в оболочке.
public java. util. Locale getLocaleO
Если не переопределен, то этот метод вызывает метод getLocale объекта ответа в
оболочке.
public ServletOutputStream getOutputStream() throws java.io.lOException
Если не переопределен, то этот метод вызывает метод getOutputStream объекта
ответа в оболочке.
public ServletResponse getResponseO throws java.io.lOException
Возвращает объект ServletResponse в оболочке.
public java.io.PrintWriter getWriter() throws java.io.lOException
Если не переопределен, то этот метод вызывает метод getWriter объекта ответа в
оболочке.
public boolean isCommitted()
Если не переопределен, то этот метод вызывает метод isCommitted объекта от-
вета в оболочке.
public void reset()
Если не переопределен, то этот метод вызывает метод reset объекта ответа в обо-
лочке.
Справочник по пакету javax.servlet.http
831
HttpSessionContext Этот интерфейс был опротестован в API
Servlet 2.1 по соображениям безопасности.
HttpSessionListener Класс, реализующий этот интерфейс, получает
уведомление об изменении списка активных
сеансов.
Таблица С.2. Классы пакета javax.servlet.http
Класс
Описание
Cookie Определяет cookie.
HttpServlet
HttpServletRequestWrapper
HttpServletResponse Wrapper
HttpSession BindingEvent
HttpSessionEvent
HttpUtils
Абстрактный класс, которому будут наследовать
сервлеты HTTP.
Класс оболочки для удобства работы
с интерфейсом HttpServletRequest.
Класс оболочки для удобства работы
с интерфейсом HttpServletResponse.
События этого типа посылаются объекту,
реализующему HttpSession Binding Listener, когда
он соединяется или разъединяется с сеансом,
или HttpSessionAttributeListener,
сконфигурированному в дескрипторе
развертывания, когда атрибут соединяется,
разъединяется или заменяется в сеансе.
Представляет уведомление о событии,
связанном с изменением объектов сеанса.
Этот класс был опротестован в API Servlet 2.3,
его методы перемещены в интерфейсы запроса.
Интерфейсы
Ниже представлены интерфейсы пакетаjavax.servlet.http. Описываются каждый ин-
терфейс, его сигнатура и методы, если они есть.
HttpServletRequest
Этот интерфейс представляет клиентский запрос HTTP.
Сигнатура
public interface HttpServletRequest extends javax.servlet.ServletRequest
828
Приложение В
Сигнатура
public class UnavailableException extends ServletException
Методы
public Servlet getServletO
Этот метод опротестован. Он возвращает сервлет, который сообщает о недо-
ступности.
public int getllnavailableSecondsO
Возвращает число секунд, в течение которых сервлет предполагает быть вре-
менно недоступным. Отрицательное число указывает, что сервлет недоступен
постоянно.
public boolean isPermanentO
Указывает, является ли недоступность постоянной.
Справочник по пакету javax.servlet.http
833
чение заголовка нельзя преобразовать в целое число, метод порождает исключение
java.lang. N umberFormat Exception.
public java.lang.String getMethod()
Возвращает метод, использованный для отправки запроса. Наиболее часто при-
меняются методы GET и POST.
public java.lang.String getPathlnfoO
Возвращает информацию, которая располагается между путем доступа к серв-
лету и строкой запроса.
public java.lang.String getPathTranslatedO
Возвращает информацию о пути доступа, которая идет после имени сервлета,
но перед строкой запроса, и транслирует ее в реальный путь доступа.
public java.lang.String getQueryString()
Возвращает строку запроса в URL запроса.
public java.lang.String getRemoteUser()
Возвращает регистрационное имя пользователя.
public java.lang.String getRequestSession!d()
Возвращает идентификатор сеанса, посланный клиентом. Идентификатор
сеанса, возвращаемый клиентом, может отличаться от идентификатора текущего
сеанса, если время ожидания предыдущего сеанса завершилось.
public java. lang. String getRequestURIO
Возвращает часть U RL запроса, находящуюся между именем протокола и стро-
кой запроса в первой строке запроса. Например, если первой строкой запроса
является POST/app/page.jsp?name=ab НТТР/1.1, методgerRequestURI вернет/арр/
page .jsp.
public java.lang.StringBuffer getRequestURL()
Возвращает модифицированную версию URL клиента, так что возвращаемое
зь тчение содержит протокол, имя сервера, номер порта и путь доступа к серверу,
но не включает в себя строку запроса.,
public java.lang.String getServletPath()
Возвращает URL запроса, который используется для вызова сервлета.
public HttpSession getSession()
Возвращает сеанс, связанный с этим запросом. Если запрос не имеет сеанса,
создает новый объект HttpSession для пользователя.
830
Приложение С
Рис. С.1. Диаграмма UML для пакета javax.servlet.http
В таблицах С.1 и С.2 дается описание интерфейсов и классов пакета.
Таблица С.1. Интерфейсы в пакете javax.servlet.http
Интерфейс HttpServletRequest HttpServletResponse HttpSession HttpSessionActivation Listener H ttpSession Attribute Listener HttpSession BindingListener Описание Представляет клиентский запрос HTTR Представляет ответ HTTP сервера. Этот интерфейс определяет сеанс пользователя, запрашивающего сервлет. Класс, реализующий этот интерфейс, используется для получения уведомления об активизации и деактивации сеанса. Класс, реализующий этот интерфейс, используется для получения уведомления об изменении атрибута объекта HttpSession. Класс, реализующий этот интерфейс, получает уведомление о соединении или разъединении с сеансом.
Справочник по пакету javax.servlet.http
835
public static final int SC.CONTINUE
Код статуса (100), указывающий, что клиент может продолжать.
public static final int SC_CREATED
Код статуса (201), указывающий, что запрос прошел успешно и что на сервере
создан новый ресурс.
public static final int SC_EXPECTATION_FAILED
Код статуса (417), указывающий, что сервер не может удовлетворить ожидани-
ям, представленным в заголовке запроса Expect.
public static final int SC_FORBIDDEN
Код статуса (403), указывающий, что сервер понял запрос, но отказывается его
выполнять.
public static final int SC_GATEWAY_TIMEOUT
Код статуса (504), указывающий, что сервер не получил вовремя ответ от выше-
стоящего сервера, когда он действовал в качестве шлюза или прокси.
public static final int SC_GONE
Код статуса (410), указывающий, что запрошенный ресурс больше недоступен
на сервере и не существует адреса для пересылки.
public static final int SC_HTTP_VERSION_NOT_SUPPORTED
Код статуса (505), указывающий, что сервер не поддерживает протокол HTTP,
использованный для отправки запроса.
public static final int SC_INTERNAL_SERVER_ERROR
Код статуса (500), указывающий, что сервер не может выполнить запрос в связи
с внутренней ошибкой.
public static final int SC_LENGTH_REQUIRED
Код статуса (411), указывающий, что не определен заголовок Content-Length, и
поэтому запрос не может быть обработан.
public static final int SC_METHOD_NOT_ALLOWED
Код статуса (405), указывающий, что метод, определенный в Request-Line,
недопустим для запрошенного ресурса.
public static final int SC_MOVED_PERMANENTLY
Код статуса (301), указывающий, что запрошенный ресурс был перемещен на
новое место.
public static final int SC_MOVED_TEMPORARILY
Код статуса (302), указывающий, что запрошенный ресурс был временно пере-
мещен на новое место.
832
Приложение С
Поля
public static final java.lang.String BASIC.AUTH
Идентификатор базовой (Basic) аутентификации. Значением этого поля будет
«BASIC».
public static final java.lang.String CLIENT_CERT_AUTH
Идентификатор базовой аутентификации. Значением поля «CLIENT_CERT».
public static final java.lang.String DIGEST_AUTH
Идентификатор базовой аутентификации. Значением этого поля «DIGEST».
public static final java.lang.String FORM_AUTH
Идентификатор базовой аутентификации. Значением этого поля будет «FORM».
Методы
public java.lang.String getAuthTypeO
Возвращает тип схемы аутентификации, используемой для защиты сервлета.
public java.lang.String getContextPath()
Возвращает контекст запроса из URI запроса.
public Cookie[] getCookiesO
Возвращает совокупность cookie из запроса. Если в запросе нет cookie, возвра-
щается null.
public long getDateHeader(java.lang.String name)
Возвращает значение заголовка, который содержит дату. Примером заголовка,
содержащим дату, является If-Modified-Since.
I
public java.lang.String getHeader(java.lang.String name)
Возвращает значение заголовка, имя которого передается в качестве аргумента.
public java.util.Enumeration getHeaderNamesO
Возвращает все имена заголовков в запросе.
public java.util.Enumeration getHeaders(java.lang.String name)
Возвращает все значения заголовка, имя которого передается в качестве аргу-
мента.
public int get!ntHeader(java.lang.String name) throws
java.lang.NumberFormatException
Возвращает значение заголовка, имя которого передается в качестве аргумента.
Метод возвращает -1, если заголовок с указанным именем не существует. Если зна-
Справочник по пакету javax.servlet.http
837
public static final int SC_REQUEST_ENTITY_TOO_LARGE
Код статуса (413), указывающий, что запрос невозможно обработать, потому
что он больше, чем может принять сервер.
public static final int SC_REQUEST_TIMEOUT
Код статуса (408), указывающий, что клиент не завершил запрос в течение ука-
занного периода времени.
public static final int 8C_REQUEST_URI_T00_L0NG
Код статуса (414), указывающий, что запрос нельзя обработать, потому что URI
длиннее, чем может принять сервер.
public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE
Код статуса (416), указывающий, что запрошенный диапазон байтов невозможно
обслужить.
public static final int SC_RESET_CONTENT
Код статуса (205), указывающий, что агент должен восстановить представление
документа, и тогда запрос будет отправлен.
public static final int SC_SEE_OTHER
Код статуса (303), указывающий, что ответ на запрос доступен по другому UR1.
public static final int SC_SERVICE_UNAVAILABLE
Код статуса (503), указывающий, что сервер перегружается и временно недо-
ступен.
public static final int 8C_SWITCHING_PR0T0C0LS
Код статуса (101), указывающий, что сервер переключает протоколы согласно
заголовку Upgrade.
public static final int SC_TEMPORARY_REDIRECT
Код статуса (307), указывающий, что запрошенный ресурс временно доступен
по другому URL
public static final int SCJJNAUTHORIZED
Код статуса (401), указывающий, что запрос пришел от неавторизованного
пользователя.
public static final int SC_UNSUPPORTED_MEDIA_TYPE
Код статуса (415), указывающий, что формат запроса не поддерживается запро-
шенным ресурсом для запрошенного метода.
public static final int SC_USE_PROXY
Код статуса (305), указывающий, что к запрошенному ресурсу следует обращать-
ся через прокси, определенный полем Location.
834
Приложение С
public HttpSession getSession(boolean create)
Возвращает сеанс, связанный с запросом. Если запрос не имеет сеанса и create
равно true, то создается новый объект HttpSession для пользователя.
public java, security.Principal getUserPrincipalO
Возвращает объект Principal, содержащий имя текущего аутентифицированного
пользователя, или null, если текущий пользователь не был аутентифицирован.
public boolean isRequestedSessionldFromCookieO
Указывает, что идентификатор сеанса в запросе был послан как cookie.
public boolean isRequestedSessionldFromUrlO
Указывает, что идентификатор сеанса в запросе был послан как часть URL.
public boolean isRequestedSessionldValidO
Указывает, что идентификатор сеанса в запросе связан с действительным объектом
HttpSession.
public boolean isUserInRole(java.lang.String role)
Указывает, что аутентифицированный пользователь входит в заданную роль.
HttpServletResponse
Этот интерфейс представляет ответ HTTP.
Сигнатура
public interface HttpServletResponse extends
j avax.se rvlet.Se rvletResponse
Поля
public static final int SC_ACCEPTED
Код статуса (202), указывающий, что запрос был принят для обработки, но не
был завершен.
public static final int SC_BAD_GATEWAY
Код статуса (502), указывающий, что сервер получил неверный ответ от другого
сервера, к которому он обращался, действуя как прокси или шлюз.
public static final int SC_BAD_REQUEST
Код статуса (400), указывающий, что клиент послал синтаксически неправиль-
ный запрос.
public static final int SC_CONFLICT
Код статуса (409), указывающий, что произошел конфликт с текущим состоя-
нием ресурса, поэтому запрос не может быть завершен.
Справочник по пакету javax.servlet.http
839
public void setHeader(java.lang.String name, java.lang.String value)
Добавляет в ответ заголовок, имеющий значение String. Если ответ уже содержит
заголовок с указанным именем, старое значение переопределяется.
public void set!ntHeader(java.lang.String name, int value)
Добавляет в ответ заголовок, имеющий целое значение. Если ответ уже содержит
заголовок с указанным именем, старое значение переопределяется.
public void setStatus(int sc)
Задает код статуса ответа.
public void setStatus(int sc, java.lang.String message)
Этот метод опротестован, используйте вместо него sendError или setStatus.
HttpSession
Этот интерфейс представляет сеанс пользователя.
Сигнатура
public interface HttpSession
Методы
public java.lang.Object getAttribute(java.lang.String name)
Возвращает атрибут, связанный с именем, заданным в качестве аргумента. Если
атрибут с указанным именем не найден, возвращается null.
public java.util.Enumeration getAttributeNamesO
Возвращает все имена атрибутов в объекте HttpSession.
public long getCreationTime()
Возвращает long, представляющее время создания объекта HttpSession.
public java.lang.String getld()
Возвращает идентификатор сеанса.
public long getLastAccessedTimeO
Возвращает время, когда клиент последний раз послал запрос, связанный с этим
объектом HttpSession.
public int getMaxInactivelntervalO
Возвращает число секунд, которое будет ожидать сервер после последнего об-
ращения клиента, прежде чем сделать этот сеанс недействительным.
public javax.servlet.ServletContext getServletContextO
Возвращает ServletContext, которому принадлежит объект HttpSession.
836
Приложение С
public static final int SC_MULTIPLE_CHOICES
Код статуса (300), указывающий, что запрошенный ресурс соответствует любому
из множества представлений, каждое из которых имеет свое собственное местопо-
ложение.
public static final int SC_NO_CONTENT
Код статуса (204), указывающий, что не существует информации для отправки
клиенту.
public static final int SC_NON_AUTHORITATIVE_INFORMATION
Код статуса (203), указывающий, что посланная клиентом метаинформация
пришла не с сервера.
public static final int SC_NOT_ACCEPTABLE
Код статуса (406), указывающий, что, исходя из заголовка accept клиента,
созданный ресурсом ответ в данном формате неприемлем.
public static final int SC_NOT_FOUND
Код статуса (404), указывающий, что запрошенный ресурс не найден.
public static final int SC_NOT_IMPLEMENTED
Код статуса (501), указывающий, что сервер не реализует запрошенную клиентом
функциональность.
public static final int SC_NOT_MODIFIED
Код статуса (304), указывающий, что условная операция GET обнаружила, что
ресурс был доступен и не модифицирован.
public static final int SC_OK
Код статуса (200), указывающий, что запрос выполнен нормально.
public static final int SC_PARTIAL_CONTENT
Код статуса (206), указывающий, что сервер смог выполнить только часть
запроса GET.
public static final int SC_PAYMENT_REQUIRED
Код статуса (402), зарезервирован для будущего использования.
public static final int SC_PRECONDITION_FAILED
Код статуса (412), указывающий, что предусловие по крайней мере в одном
заголовке оценивается как false при проверке на сервере.
public static final int SC_PROXY_AUTHENTICATION_REQUIRED
Код статуса (407), указывающий, что клиент должен сначала аутентифицировать
себя на прокси.
Справочник по пакету javax.servlet.http
841
Методы
public void sessionDidActivate(HttpSessionEvent he)
Этот метод вызывается контейнером сервлетов после активизации сеанса.
public void sessionWillPassivate(HttpSessionEvent he)
Этот метод вызывается контейнером сервлетов перед тем, как сеанс будет деак-
тивирован.
HttpSessionAttributeListener
Этот интерфейс реализуется любым классом, которому необходимо получать
уведомление об изменениях в атрибуте объекта HttpSession.
Сигнатура
public interface HttpSessionAttributeListener extends
java.util.EventListener
Методы
public void attributeAdded(HttpSessionBindingEvent he)
Этот метод вызывается контейнером сервлетов после добавления атрибута в сеанс.
public void attributeRemoved(HttpSessionBindingEvent he)
Этот метод вызывается контейнером сервлетов после удаления атрибута из сеанса.
public void attributeReplaced(HttpSessionBindingEvent he)
Этот метод вызывается контейнером сервлетов после замены атрибута в сеансе.
HttpSessionBindingListener
Этот интерфейс реализуется любым классом, которому необходимо получать
уведомления о соединении и разъединении с сеансом.
Сигнатура
public interface HttpSessionBindingListener extends
java.util.EventListener
Методы
public void valueBound(HttpSessionBindingEvent he)
Этот метод вызывается контейнером сервлетов, когда объект соединяется с сеансом.
public void valuellnbound(HttpSessionBindingEvent he)
Этот метод вызывается контейнером сервлетов, когда объект разъединяется с
сеансом.
838
Приложение С
Методы
public void addCookie(Cookie cookie)
Добавляет cookie в ответ.
public void addDateHeader(java.lang.String name, long date)
Добавляет в ответ заголовок с указанным именем и значением даты.
public void addHeader(java.lang.String name, java.lang.String value)
Добавляет в ответ заголовок с указанным именем и строковым значением.
public void add!ntHeader(java.lang.String name, int value)
Добавляет в ответ заголовок с указанным именем и целым значением.
public boolean containsHeader(java.lang.String name)
Указывает, содержит ли ответ заданный заголовок.
public java.lang.String encodeRedirectdrl( java. lang. String url)
Этот метод опротестован, используйте вместо него encodeRedirectURL.
public java.lang.String encodeRedirectURL(java.lang.String url)
Кодирует URL для надежной передачи в методе sendRedirect.
public java.lang.String encodedrl(java.lang.String url)
Этот метод опротестован, используйте вместо него encodeURL.
public java.lang.String encodeURL(java.lang.String url)
Кодирует указанный URL, добавляя в него идентификатор сеанса.
public void sendError(int sc) throws java.io.lOException
Посылает сообщение об ошибке HTTP, указывая код статуса. Исключение
lOException порождается, если ответ был завершен.
public void sendError(int sc, java.lang.String message)
throws java.io.lOException
Посылает сообщение об ошибке HTTP, указывая код статуса и описывающее сооб-
щение. Исключение lOException порождается, если ответ был завершен.
public void sendRedirect(java.lang.String location) throws
java.io.lOException
Заставляет клиента запросить новое местоположение, указанное в аргументе.
public void setDateHeader(java.lang.String name, long date)
Добавляет в ответ заголовок, имеющий значение даты. Если ответ уже содержит
заголовок с указанным именем, старое значение переопределяется.
Справочник по пакету javax.servlet.http
843
public java.lang.String getNameO
Возвращает имя cookie.
public java.lang.String getPath()
Возвращает путь доступа на сервере, куда браузер возвращает этот cookie.
public boolean getSecureO
Указывает, был ли cookie послан с помощью защищенного протокола.
public java.lang.String getValueO
Возвращает значение cookie.
public java.lang.String getVersion()
Возвращает номер версии протокола, которой соответствует cookie. Версия О
соответствует исходной спецификации Netscape, а версия 1 — RFC 2109.
public void setComment(java.lang.String comment)
Добавляет описание в cookie.
public void setDomain(java.lang.String domain)
Определяет домен, в котором должен быть представлен cookie.
public void setMaxAge(int age)
Определяет возраст cookie в секундах.
public void setPath(java.lang.String path)
Определяет путь доступа cookie.
public void setSecure(boolean secure)
Указывает браузеру, должен ли посылаться cookie только по защищенному про-
токолу.
public void setValue(java.lang.String value)
Заменяет старое значение значением, определенным в аргументе.
public void setVersion(int version)
Определяет версию протокола, которой соответствует cookie.
HttpServlet
Абстрактный класс, которому будет наследовать сервлет HTTP.
840
Приложение С
public HttpSessionContext getSessionContextO
Этот метод опротестован.
public java.lang.Object getValue(java.lang.String name)
throws java.lang.IllegalStateException
Этот метод опротестован, используйте вместо него метод getAttribute.
public java.lang.Stringf] getValueNamesO
throws java.lang.IllegalStateException
Этот метод опротестован, используйте вместо него метод getAttributeNames.
public void invalidate() throws java. lang.IllegalStateException
Удаляет объект HttpSession.
public boolean isNew() throws java. lang. IllegalStateException
Указывает, что этот сеанс создается в текущем запросе.
public void putValue(java.lang.String name, java.lang.Object value)
throws java.lang.IllegalStateException
Этот метод опротестован, используйте вместо него метод setAttribute.
public void removeAttribute(java.lang.String name) throws
java.lang.IllegalStateException
Удаляет атрибут, связанный с указанным именем.
public void removeValue(java.lang.String name) throws
java.lang.IllegalStateException
Этот метод опротестован, используйте вместо него метод removeAttribute.
public void setAttribute(java.lang.String name, java.lang.Object value)
throws java.lang.IllegalStateException
Добавляет атрибут, связанный с заданным именем, в объект HttpSession.
public void setMaxInactiveInterval(int interval)
Задает число секунд, которое будет ожидать сервер после последнего обраще-
ния клиента, прежде чем сделать объект HttpSession недействительным.
HttpSessionActivationListener
Этот интерфейс реализуется любым классом, которому необходимо получать
уведомление об активизации и деактивации сеанса.
Сигнатура
public interface HttpSessionActivationListener extends
javax.util.EventListener
Справочник по пакету javax.servlet.http
845
protected long getLastModified(HttpServletRequest request)
Возвращает время последней модификации HttpServletRequest.
protected void service(HttpServletRequest request,
HttpServletResponse response) throws
javax.servlet.ServlerException, java.io.lOException
Этот метод вызывается другим служебным методом для получения запроса HTTP.
Затем этот метод направляет запрос соответствующему методу doXXX.
public void service(javax.servlet.ServletRequest request,
javax.servlet.Response response)
throws javax.servlet.ServletException, java.io.lOException
Направляет клиентский запрос защищенному служебному методу.
HttpServletRequestWrapper
Класс оболочки для удобства работы с интерфейсом HttpServletRequest.
Сигнатура
public class HttpServletRequestWrapper extends
javax.servlet.ServletRequestWrapper implements
javax.servlet.http.HttpServletRequest
Методы
public java.lang.String getAuthTypeO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getContextPathO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public Cookie[] getCookiesO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public long getDateHeader(java.lang.String name)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getHeader(java.lang.String name)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
842
Приложение С
HttpSessionContext
Этот интерфейс опротестован по соображениям безопасности.
HttpSession Listener
Этот интерфейс реализуется любым классом, которому необходимо уведомление
об изменении в списке активных сеансов.
Сигнатура
public interface HttpSessionListener extends java.util.EventListener
Методы
public void sessionCreated(HttpSessionEvent he)
Этот метод вызывается контейнером сервлетов после создания сеанса.
public void sessionDestroyed(HttpSessionEvent he)
Этот метод вызывается контейнером сервлетов после уничтожения сеанса.
1
Классы
Ниже приводятся классы из пакета javax.servlet.http. Описываются каждый класс,
его сигнатура, поля и методы, если они есть.
Cookie
Этот класс представляет cookie.
Сигнатура
public class Cookie implements java.lang.Cloneable
Методы
public java.lang.Object clone()
Возвращает копию cookie.
public java.lang.String getCommentO
Возвращает описание cookie или null, если ничего нет.
public java.lang.String getDomain()
Возвращает домен данного cookie.
public int getMaxAgeO
Возвращает число секунд, втечение которых будет сохраняться cookie. Значение-1
указывает, что cookie будет уничтожен после того, как пользователь закроет браузер.
Справочник по пакету javax.servlet.http
847
public java.lang.String getServletPath()
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public HttpSession getSession()
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public HttpSession getSession(boolean create)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.security.Principal getUserPrincipalO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public boolean isRequestedSessionldFromCookieO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public boolean isRequestedSessionldFromUrlO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public boolean isRequestedSessionldValidO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public boolean isUser!nRole(java.lang.String role)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
HttpServletResponseWrapper
Класс оболочки для удобства работы с интерфейсом HttpServletResponse.
Сигнатура
public class HttpServletResponseWrapper extends
javax.servlet.ServletResponseWrapper implements
javax.servlet.http.HttpServletResponse
Методы
public void addCookie(Cookie cookie)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
844
Приложение С
Сигнатура
public abstract class HttpServlet extends javax.servlet.GenericServlet
implements java.io.Serializable
Методы
protected void doDelete(HttpServletRequest request, HttpServletResponse
response) throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с при-
менением метода DELETE.
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с по-
мощью метода GET.
protected void doHead(HttpServletRequest request, HttpServletResponse
response) throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с при-
менением метода HEAD.
protected void doOptions(HttpServletRequest request,
HttpServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с по-
мощью метода OPTIONS.
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с по-
мощью метода POST.
protected void doPut(HttpServletRequest request,
HttpServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с при-
менением метода PUT.
protected void doTrace(HttpServletRequest request,
HttpServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Этот метод вызывается контейнером сервлетов, когда запрос посылается с при-
менением метода TRACE.
Справочник по пакету javax.servlet.http
849
public void set!ntHeader(java.lang.String name, int value)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void setStatus(int sc)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void setStatus(int sc, java.lang.String message)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
HttpSessionBindingEvent
События этого типа посылаются объекту, реализующему HttpSessionBindingListener,
когда он соединяется или разъединяется с сеансом, или HttpSessionAttributeListener,
сконфигурированному в дескрипторе развертывания, когда атрибут соединяется, разъе-
диняется или заменяется в сеансе.
Сигнатура
public class HttpSessionBindingEvent extends
javax.servlet.http.HttpSessionEvent
Методы
public java.lang.String getNameO
Возвращает имя, с которым связан атрибут сеанса.
public HttpSession getSession()
Возвращает измененный сеанс.
public java. lang. Object getValueO
Возвращает атрибут сеанса, который был добавлен, удален или модифицирован.
HttpSessionEvent
Этот класс представляет уведомление о событии изменения объектов сеанса.
Сигнатура
public class HttpSessionEvent extends java.util.Eventobject
Методы
public HttpSession getSession()
Возвращает измененный сеанс.
HttpUtils
Этот класс опротестован.
846
Приложение С
public java.util.Enumeration getHeaderNamesO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.util.Enumeration getHeaders(java.lang.String name)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public int get!ntHeader(java.lang.String name) throws
java.lang.NumberFormatException
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getMethod()
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getPathlnfoO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getPathTranslatedO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getQueryStringO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getRemoteUserO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getRequestSessionldO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.String getRequestURIO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
public java.lang.StringBuffer getRequestURLO
Если не переопределен, то этот метод возвращает аналогичный метод объекта
запроса в оболочке.
Справочник по пакету javax.servletJsp
851
Таблица D.2. Классы пакета javax.servlet.jsp
Класс
Описание
JspEnginelnfo
JspException
JspFactory
JspTagException
JspWriter
PageContext
Предоставляет информацию о контейнере JSP.
Базовый класс для всех исключений JSP.
Абстрактный класс, предоставляющий ряд методов, из
которых можно получить различные объекты
времени выполнения для страницы JSP.
Экземпляр подкласса этого класса создается
контейнером JSP.
Исключение, порождаемое обработчиком тегов.
Объект JspWriter соответствует неявной переменной
вне страницы JSP, в которую записываются выходные
данные для клиента.
Абстрактный класс, которому необходимо
наследовать, чтобы контейнер JSP мог предоставить
различные реализации.
Интерфейсы
Ниже представлены интерфейсы пакетаjavax.servlet.jsp. Описываются каждый ин-
терфейс, его сигнатура и методы, если они есть.
HttpJspPage
Этот интерфейс определяет контракт между классом реализации страницы JSP
и контейнером JSP. Объекты этого типа получают из класса JspFactory.
Сигнатура
public interface HttpJspPage extends JspPage
Методы
public void _jspService(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.lOException
Этот метод определяется автоматически контейнером JSP и не должен опреде-
ляться автором страницы JSP.
JspPage
Этот интерфейс является суперинтерфейсом интерфейса HttpJspPage и опреде-
ляет контракт между классом реализации страницы JSP и контейнером JSP. Объекты
JspPage получают из класса JspFactory.
848
Приложение С
public void addDateHeader(java.lang.String name, long date)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void addHeader(java.lang.String name, java.lang.String value)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void add!ntHeader(java.lang.String name, int value)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public boolean containsHeader(java.lang.String name)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public java.lang.String encodeRedirectUrl(java.lang.String url)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public java.lang.String encodeRedirectURL(java,lang.String url)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public java.lang.String encodeUrl(java.lang.String url)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public java. lang. String encodellRL( java. lang. String url)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void sendError(int sc) throws java.io.lOException
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void sendError(int sc, java.lang.String message)
throws java.10.lOException
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void sendRedirect(java.lang.String location) throws
java.io.lOException
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void setDateHeader(java.lang.String name, long date)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
public void setHeader(java.lang.String name, java.lang.String value)
Если не переопределен, то этот метод возвращает аналогичный метод объекта
ответа в оболочке.
Справочник по пакету javax.servlet.jsp
853
JspFactory
Абстрактный класс, предоставляющий ряд методов, из которых можно получить
различные объекты времени выполнения для страницы JSP. Экземпляр подкласса
этого класса создается контейнером JSP.
Сигнатура
public abstract class JspFactory
Методы
public static synchronized JspFactory getDefaultFactoryO
Возвращает используемый по умолчанию объект JspFactory для этой реализации.
public abstract PageContext getPageContext(
javax.servlet.Servlet servlet,
javax.servlet.ServletRequest request,
javax.servlet.ServletResponse response,
java.lang.String errorPageUrl,
boolean needsSession, int buffer, buffer autoFlush)
Возвращает объект PageContext для вызывающего сервлета и текущих запроса и
ответа.
public abstract void releasePageContext(PageContext pageContext)
Этот метод вызывается для освобождения ранее выделенного PageContext.
public static synchronized void setDefaultFactory(JspFactory
defaultFactory)
Этот метод вызывается контейнером JSP для задания используемого по умол-
чанию объекта JspFactory для этой реализации.
JspTagException
Это исключение порождается обработчиком тегов. Не определяет никаких
методов.
Сигнатура
public class JspTagException extends JspException
JspWriter
Объект JspWriter соответствует неявной переменной вне страницы JSP, куда
записываются выходные данные клиента.
Справочник
по пакету javax.servlet.jsp
^^^пецификация JSP 1.2 определяет два пакета расширения Java для разработ-
чиков, применяющих технологию сервлетов: javax.servlet.jsp и javax.servlet.jsp.tagext.
Это приложение содержит полный справочник по пакету javax.servlet.jsp. Пакет
javax.servlet.jsp.tagext представлен в приложении Е.
Тип данных, принадлежащий пакету javax.servlet.jsp, записывается без указания
полностью квалифицированного имени.
Зтаблицах D.1 и D.2 приводится описание интерфейсов и классов пакета соот-
ветственно.
Таблица D.I. Интерфейсы пакета javax.servlet.jsp
Интерфейс
Описание
HttpJspPage
Этот интерфейс определяет контракт между
классом реализации страницы JSP и
контейнером JSP. Объекты этого типа получают
из класса JspFactory.
JspPage Этот интерфейс является суперинтерфейсом
интерфейса HttpJspPage; он определяет контракт
между классом реализации страницы JSP и
контейнером JSP. Объекты JspPage получают из
класса JspFactory.
Справочник по пакету javax.servlet.jsp
855
public abstract void newLineO throws java. io. lOException
Посылает символ разделителя строки.
public abstract void print(boolean value) throws java.io.lOException
Печатает булево значение.
public abstract void print(char value) throws java.io.lOException
Печатает символ.
public abstract void print(char[] value) throws java.io.lOException
Печатает массив символов.
public abstract void print(double value) throws java.io.lOException
Печатает double.
public abstract void print(float value) throws java.io.lOException
Печатает float.
public abstract void print(int value) throws java.io.lOException
Печатает целое значение.
public abstract void print(long value) throws java.io.lOException
Печатает long.
public abstract void print(java.lang.Object object) throws
java.io.lOException
Печатает объект.
public abstract void print(java.lang.String string) throws
java.io.lOException
Печатает String.
public abstract void println() throws java.io.lOException
Печатает символ разделителя строки.
public abstract void println(boolean value) throws java.io.lOException
Печатает логическое значение, за которым следует разделитель строки.
public abstract void println(char value) throws java.io.lOException
Печатает char, за которым следует разделитель строки.
public abstract void println(char[] value) throws java.io.lOException
Печатает массив char, за которым следует разделитель строки.
public abstract void println(double value) throws java.io.lOException
Печатает double, за которым следует разделитель строки.
852
Приложение D
Этот интерфейс имеет три метода, двумя из которых являются jsplnit и jspDestroy.
Третий метод, JspService, зависит от используемого протокола. Сигнатуру метода
JspService нельзя представить в базовом виде.
Сигнатура
public interface JspPage extends javax.servlet.Servlet
Методы
public void jspDestroyO
Этот метод вызывается перед уничтожением страницы JSP.
public void jsplnit()
Этот метод вызывается при инициализации страницы JSP.
Классы
Ниже приводятся классы пакета javax.servlet.jsp. Описываются каждый класс, его
сигнатура, поля и методы, если они есть.
JspEnginelnfo
Этот класс предоставляет информацию о контейнере JSP.
Сигнатура
public abstract class JspEnginelnfo
Метод
public java.lang.String gefSpecificationVersion()
Возвращает номер версии спецификации JSP, поддерживаемой контейнером JSP.
JspException
Базовый класс для всех исключений JSP.
Сигнатура
public class JspException extends java.lang.Exception
Метод
public java. lang. Th rowable getRootCauseO
Возвращает корневое исключение, которое вызвало это исключение.
Справочник по пакету javax.servlet.jsp
857
public static final java.lang.String PAGE
Этот идентификатор используется как имя для хранения объекта
javax.servlet.Servlet в таблице имен текущего PageContext.
public static final int PAGE_SCOPE
Целое число, представляющее область действия страницы.
public static final java.lang.String PAGE_CONTEXT
И мя для хранения текущего объекта PageContext в его собственной таблице имен.
public static final java.lang.String REQUEST
Имя для хранения объекта javax.servlet.ServletRequest в таблице имен текущего
PageContext.
public static final int REQUEST_SCOPE
Целое значение, представляющее область действия запроса.
public static final java.lang.String RESPONSE
Имя для хранения объектаjavax.servlet.ServletResponse втаблице имен текущего
PageContext.
public static final java.lang.String SESSION
Имя для хранения объектаjavax.servlet.http.HttpSession втаблице имен текуще-
го PageContext.
public static final int SESSION-SCOPE
Целое значение, представляющее область действия сеанса.
Методы
public abstract java.lang.Object findAttribute(java.lang.String name)
Ищет атрибут, имя которого определено в качестве аргумента, в областях дей-
ствия запроса, страницы, сеанса (если допустимо) и приложения.
public abstract void forward(java.lang.String localUrl)
throws javax.servlet.ServletException, java.io.lOException,
java.lang.IllegalArgumentException,
java.lang.IllegalStateException,
j ava.lang.Secu rityException
Пересылает запрос другому локальному ресурсу.
public abstract java.lang.Object getAttribute(java.lang.String name)
Возвращает атрибут, имя которого определено в качестве аргумента, из области
действия страницы.
854
Приложение D
Сигнатура
public abstract class JspWriter extends java.io.Writer
Поля
protected boolean autoFlush
Указывает, будетли содержи мое объекта JspWriter автоматически выталкиваться.
protected int bufferSize
Размер буфера.
public static final int DEFAULT-BUFFER
Константа, определяющая использование для буфера размера, заданного по
умолчанию в данной реализации.
public static final int NO_BUFFER
Константа, определяющая, что буфер не используется.
public static final int UNBOUNDED-BUFFER
Константа, определяющая, что JspWriter имеет буфер и неограничен.
Методы
public abstract void clear() throws java.io.lOException
Очищает буфер. Если содержимое буфера было зафиксировано, порождается
исключение java.io.IOException.
public abstract void clearBuffer() throws java.io.IOException
Очищаетбуфер. Если содержимое буфера было зафиксировано, никакие исклю-
чения не порождаются.
public abstract void close() throws java.io.IOException
Выталкивает буфер и закрывает поток.
public abstract void flush() throws java.io.IOException
Выталкивает буфер.
public int getBufferSize()
Возвращает размер буфера.
public abstract int getRemainingO
Возвращает оставшееся неиспользованное число байтов в буфере.
public boolean isAutoFlush()
Указывает, включено ли свойство autoflush.
Справочник по пакету javax.servlet.jsp
859
public abstract void include(java.lang.String relativeUrl)
throws javax.servlet.ServletException, java.io.lOException,
java.lang.IllegalArgumentException, java.lang.SecurityException
Обрабатывает внешний ресурс и возвращает команду в вызывающий поток
выполнения.
public abstract void initialize(javax.servlet.Servlet servlet,
javax.servlet.ServletRequest request, javax.servlet.ServletResponse
response, java.lang.String errorPageUrl,. boolean needsSession,
int bufferSize, boolean autoFlush)
throws java.io.lOException, java.lang.IllegalStateException,
java.lang.IllegalArgumentException
Этот метод вызывается контейнером JSP для подготовки текущей страницы к
обслуживанию входящих запросов.
public JspWriter popBodyO
Возвращает JspWriter, сохраненный предыдущим обращением к методу push Body.
public javax.servlet.jsp.tagext.BodyContent pushBodyO
Возвращает новый объект BodyContent, сохраняет текущий объект JspWriter и
обновляет значение атрибута out.
public void release()
'Этот метод вызывается контейнером JSP для сброса внутреннего состояния те-
кущего объекта PageContext.
public abstract void removeAttribute(java.lang.String name)
Удаляет первый найденный атрибут при поиске в областях действия.
public abstract void removeAttribute(java.lang.String name, int scope)
Удаляет атрибут в указанной области действия.
public abstract void setAttribute(java.lang.String name,
java.lang.Object attribute)
throws java.lang.NullPointerException
Добавляет атрибут к странице.
public abstract void setAttribute(java.lang.String name,
java.lang.Object attribute, int scope)
throws java.lang.NullPointerException,
java.lang.IllegalArgumentException
Добавляет атрибут в указанную область действия.
856
Приложение D
public abstract void println(float value) throws java.io.IOException
Печатает float, за которым следует разделитель строки.
public abstract void println(int value) throws java.io.IOException
Печатает целое значение, за которым следует разделитель строки.
public abstract void println(long value) throws java.io.IOException
Печатает long, за которым следует разделитель строки.
public abstract void println(java.lang.Object object)
throws java.io.IOException
Печатает объект, за которым следует разделитель строки.
public abstract void println(java.lang.String string)
throws java.io.IOException
Печатает String, за которой следует разделитель строки.
PageContext
Класс PageContext является абстрактным классом, которому необходимо насле-
довать, чтобы контейнер JSP мог предоставить конкретные реализации.
Сигнатура
public abstract class PageContext
Поля
public static final java.lang.String APPLICATION
Имя для хранения объекта javax.servlet.ServletContext в таблице имен текущего
PageContext.
public static final int APPLICATION_SCOPE
Целое значение, представляющее область действия приложения.
public static final java.lang.String CONFIG
Идентификатор, используемый в качестве имени для хранения объекта
javax.servlet.ServletConfig в таблице имен текущего PageContext.
public static final java.lang.String EXCEPTION
Идентификатор, используемый в качестве имени для хранения непредвиден-
ного исключения в списке атрибутов ServletRequest и в таблице имен PageContext.
public static final java.lang.String OUT
Идентификатор, используемый в качестве имени для хранения объекта JspWriter
в таблице имен текущего PageContext.
Справочник по пакету javax.servlet.jsp. tagext
861
Таблица E.2. Классы пакета Javax.servlet.jsp.tagext
Класс
BodyContent
BodyTagSupport
PageData
TagAttributelnfo
Tag Data
TagExtralnfo
Taginfo
Tag Library Info
Tag Library Validator
TagSupport
Tag Variable Info
Variableinfo
Описание
Этот класс представляет содержимое тела тега.
Это базовый класс; ему наследует обработчик тегов,
которому необходимо манипулировать телом.
Предоставляет информацию о странице JSP,
которая доступна во время трансляции.
Предоставляет информацию об атрибутах тега.
Предоставляет информацию об экземпляре тега,
который доступен во время трансляции.
Необязательный класс, который предоставляет
дополнительную информацию времени
трансляции, недоступную в файле TLD.
Предоставляет информацию для тега
в библиотеке тегов.
Предоставляет информацию, связанную
с директивой taglib и с ее файлом TLD, который
доступен во время трансляции.
Класс проверки страницы JSP, которая доступна
во время трансляции.
Предоставляет удобные методы и является базовым
классом, который будет расширяться новыми
обработчиками тегов.
Предоставляет информацию для тега
в библиотеке тегов.
Предоставляет информацию о переменных сценариев.
Интерфейсы
Ниже приводятся интерфейсы пакетаjavax.servlet.jsp.tagext Описываются каждый
интерфейс, его сигнатура, методы и поля, если они есть.
BodyTag
Этот интерфейс реализуется обработчиком тегов, которому необходимо мани-
пулировать телом.
Сигнатура
public interface BodyTag extends IterationTag
Поля
public static final int EVAL_BODY_BUFFERED
858
Приложение D
public abstract java.lang.Object getAttribute(java.lang.String name,
int scope)
Возвращает атрибут, имя которого определено в качестве аргумента, из указан-
ной области действия.
public abstract java.util.Enumeration getAttributeNameslnScope(int scope)
Возвращает имена атрибутов в указанной области действия.
public abstract int getAttributesScope(java.lang.String name)
Возвращает область действия атрибута, имя которого определено в качестве
аргумента.
public abstract java.lang.Exception getException()
Возвращает объект текущего исключения.
public abstract JspWriter getOutO
Возвращает текущий объект JspWriter.
public abstract java.lang.Object getPage()
Возвращает текущую страницу.
public abstract javax.servlet.ServletRequest getRequest()
Возвращает текущий объект запроса.
public abstract javax.servlet.ServletResponse getResponseO
Возвращает текущий объект ответа.
public abstract javax.servlet.ServletConfig getServletConfigO
Возвращает текущий объект ServletConfig.
public abstract javax.servlet.ServletContext getServletContextO
Возвращает текущий объект ServletContext.
public abstract javax.servlet.http.HttpSession getSession()
Возвращает объект сеанса, связанный с запросом.
public abstract void handlePageException(java. lang. Exception e)
throws javax.servlet.ServletException, java.io.lOException,
java.lang.NullPointerException, java.lang.SecurityException
Обрабатывает необработанное исключение уровня страницы.
public abstract void handlePageException(java.lang.Throwable e)
throws javax.servlet.ServletException, java.io.lOException,•
java.lang.NullPointerException, java.lang.SecurityException
Обрабатывает необработанное исключение уровня страницы.
Справочник по пакету javax.servlet.jsp.tagext
863
public static final int SKIP_BODY
Возвращаемое значение для методов doStartTag и doAfterBody, указывающее, что
контейнер должен пропустить тело.
public static final int SKIP_PAGE
Возвращаемое значение для метода do EndTag, указывающее, что контейнер дол-
жен пропустить оставшуюся страницу.
Методы
public int doEndTagO throws javax.servlet.jsp.JspException
Этот метод вызывается контейнером JSP для обработки конечного тега для это-
го экземпляра тега.
public void doStartTagO throws javax.servlet.jsp.JspException
Этот метод вызывается контейнером JSP для обработки начального тега для этого
экземпляра тега.
public Tag getParentO
Возвращает прямого предка тега.
public void releaseO
Этот метод вызывается для освобождения состояния обработчика тегов.
public void setPageContext(javax.servlet, jsp.PageContext pageContext)
Задает контекст текущей страницы.
public void setParent(Tag parent)
Этот метод вызывается контейнером JSP для задания предка текущего обработ-
чика тегов.
TryCatchFinally
Вспомогательный интерфейс других интерфейсов для поддержки дополнитель-
ных способов управления ресурсами.
Сигнатура
public interface TryCatchFinally
Методы
public void doCatch(java.lang.Throwable t) throws java.lang.Throwable
Этот метод вызывается, если порождается Throwable в то время, когда контей-
нер JSP анализирует BODY внутри тега или одного из следующих методов:
Tag.doStartTag, Tag.doEndTag, IterationTag.doAfterBody, BodyTag.dolnitBody.
public void doFinallyO
Справочник
по пакету
javax.servlet.jsp.tagext
то приложение представляет собой полный справочник по пакету
javax.servlet.jsp.tagext — второму пакету, определенному в спецификации JSP 1.2.
Первый пакет, javax.servlet.jsp, описывается в приложении D.
В приложении тип данных, принадлежащий naKeTyjavax.servlet.jsp.tagext, запи-
сывается без указания полностью квалифицированного имени.
В таблицах Е.1 и Е.2 приводятся интерфейсы и классы пакета соответственно.
Таблица Е.1. Интерфейсы пакета javax.servlet.jsp.tagext
Интерфейс
BodyTag
IterationTag
Tag
TryCatch Finally
Описание
Этот интерфейс реализуется обработчиком
тегов, которому необходимо манипулировать их
телом.
Этот интерфейс добавляет метод, который
имеет дело с пересмотром тела.
Этот интерфейс реализуется обработчиком
тегов, которому не нужно манипулировать
телом.
Вспомогательный интерфейс для поддержки
дополнительных возможностей управления
ресурсами.
Справочник по пакетуjavax.servlet.jsp.tagext
865
Методы
public int doAfterBodyO throws http.servlet. jsp.JspException
Вызывается после анализа тела.
public int doEndTagO throws http.servlet.jsp.JspException
Вызывается для обработки конечного тега.
public int doInitBodyO throws http.servlet.jsp.JspException
Вызывается перед первым анализом тела.
public int doStartTagO throws http.servlet.jsp.JspException
Вызывается для обработки начального тега.
public BodyContent getBodyContentO
Возвращает содержимое тела.
public javax.servlet. jsp. JspWriter getPreviousOutO
Возвращает охватывающий JspWriter.
public void release()
Освобождает состояние.
public void setBodyContent(BodyContent bodyContent)
Задает содержимое тела.
PageData
Этот класс предоставляет информацию о странице JSP, которая доступна во
время трансляции.
Сигнатура
public abstract class PageData
Методы
public abstract java.io.Inputstream getlnputStreamO
Возвращает XML-документ страницы JSP.
TagAtrributelnfo
Этот класс предоставляет информацию об атрибутах тега.
Сигнатура
public class TagAttributelnfo
862
Приложение Е
Возвращаемое значение метода doStartTag в классе, который реализует интер-
фейс BodyTag.
public static final int EVAL_BODY_TAG
Это поле опротестовано.
Методы
public void doInitBodyO throws javax.servlet.jsp.JspException
Этот метод вызывается контейнером JSP перед первым анализом тела.
public void setBodyContent(BodyContent bodyContent)
Этот метод задает BodyContent.
IterationTag
Этот интерфейс добавляет метод, который имеет дело с пересмотром тела.
Сигнатура
public interface IterationTag extends Tag
Поле
public static final int EVAL_BODY_AGAIN
Возвращаемое значение для метода doAfterBody, указывающее, что контейнер
должен проанализировать тело снова.
Метод
public int doAfterBodyO throws javax.servlet.jsp.JspException
Этот метод вызывается контейнером JSP для выполнения анализа тела.
Tag
Этот интерфейс реализуется обработчиком тегов, которому не нужно манипу-
лировать телом.
Сигнатура
public interface Tag
Поля
public static final int EVAL_BODY_INCLUDE
Возвращаемое значение для метода doStartBody, указывающее, что контейнер
должен включать тело в существующий поток out.
public static final int EVAL_PAGE
Возвращаемое значение для метода do EndTag, указывающее, что контейнер дол-
жен продолжить анализ страницы.
Справочник по пакету javax.servlet.jsp.tagext
867
public java.lang.String getAttibuteString(java.lang.String name)
Возвращает атрибут, имя которого определено как аргумент.
public java.lang.String getld()
Возвращает идентификатор атрибута, если доступен.
public void setAttribute(java.lang.String name,
java.lang.Object attribute)
Задает атрибут.
TagExtralnfo
Дополнительный класс, который предоставляет информацию времени транс-
ляции, недоступную в файле TLD.
Сигнатура
public abstract class TagExtralnfo
Методы
public final Taginfo getTaglnfo
Возвращает объект Taginfo для этого класса.
public Variablelnfo[] getVariable!nfo(TagData data)
Возвращает информацию о переменных сценария.
public boolean isValid(TagData data)
Указывает, является ли допустимым экземпляр тега.
public final void setTag!nfo(Tag!nfo taginfo)
Задает Taginfo для этого класса.
Taginfo
Этот класс предоставляет информацию для тега в библиотеке тегов.
Сигнатура
public class Taginfo
Поля
public static final java.lang.String BODY_CONTENT_EMPTY
Значение для метода getBodyContent, если тело пустое.
public static final java.lang.String BODY_CONTENT_JSP
Значение для метода getBodyContent, если это JSP.
864
ПриложениеЕ
Этот метод вызывается после doEndTag в любом классе, реализующем интер-
фейс Tag, IterationTag или BodyTag.
Классы
Ниже приводятся классы пакетаjavax.servlet.jsp.tagext. Описываются каждый класс,
его сигнатура, поля и методы, если они есть.
BodyContent
Этот класс представляет содержимое тела тега.
Сигнатура
public abstract class BodyContent extends javax.servlet.jsp.JspWriter
Методы
public void clearBody()
Очищает тело.
public void flush() throws java. io.lOException
Переопределяет flush.
public javax.servlet.jsp.JspWriter getEnclosingWriter()
Возвращает охватывающий объект JspWriter.
public abstract java.io.Reader getReader()
Возвращает текущий BodyContent как объект Reader.
public abstract java. lang. String getStringO
Возвращает строковое представление текущего BodyContent.
public abstract void writeOut(java.io.Writer out)
throws java.io.lOException
Записывает текущий BodyContent в объект Writer.
BodyTagSupport
Этому базовому классу наследует обработчик тегов, которому необходимо
манипулировать телом.
Сигнатура
public class BodyTagSupport extends TagSupport implements BodyTag
Справочник по пакету javax.servlet.jsp.tagext
869
public void setTagLibrary(TagLibrary!nfo tli)
Задает свойство TagLibrarylnfo.
public java.lang.String toStringO
Переопределяет метод toString в классе java.lang.Object
TagLibrarylnfo
Этот класс предоставляет информацию, связанную с директивой taglib и с ее
файлом TLD, который доступен во время трансляции.
Сигнатура
public abstract class TagLibrarylnfo
Методы
public java.lang.String getlnfoStringO
Возвращает информацию для этого TLD.
public java.lang.String getPrefixStringO
Возвращает префикс для данного taglib.
public java.lang.String getReliablellRNO
Возвращает надежный URN.
public java.lang.String getRequiredVersion()
Возвращает номер требуемой версии контейнера JSP.
public java.lang.String getShortNameO
Возвращает краткое имя, описанное в TLD.
public Taginfo getTag(java.lang.String shortName)
Возвращает экземпляр Taginfo для указанного краткого имени.
public Taglnfo[] getTagsO
Возвращает все экземпляры Taginfo для данного тега.
public java.lang.String getURIO
Возвращает URI из директивы taglib.
TagLibraryValidator
Этот класс является классом проверки для страницы JSP, который доступен во
время трансляции.
866
ПриложениеЕ
Поле
public static final java.lang.String ID
Идентификатор.
Методы
public boolean canBeRequestTimeO
Указывает, можно ли сохранить значение времени запроса в этом атрибуте.
public static TagAttributelnfo getIdAttribute(TagAttributelnfo[] tai)
Производит поиск в массиве объектов TagAttributelnfo и возвращает объект
TagAttributelnfo, содержащий id.
public java.lang.String getNameO
Возвращает имя атрибута.
public java.lang.String getTypeNameO
Возвращает тип атрибута.
public boolean isRequiredO
Указывает, требуется ли этот атрибут.
public java.lang.String toStringO
Переопределяет метод toString класса java.lang.Object.
Tag Data
Этот класс предоставляет информацию об экземпляре тега, доступном во время
трансляции.
Сигнатура
public class TagData implements java.lang.Cloneable
Поле
public static final java.lang.Object REQUEST_TIME_VALUE
Уникальное значение для атрибута, указывающее, что его значение доступно
только во время запроса.
Методы
public java.lang.Object getAttribute(java.lang.String name).
Возвращает атрибут, имя которого определено в качестве аргумента.
public java.util.Enumeration getAttributes()
Возвращает все атрибуты.
Справочник по пакету javax.servlet.jsp.tagext
871
public java.lang.Object getValue(java.lang.String key)
Возвращает значение заданного ключа.
public java.util.Enumeration getValuesO
Возвращает все значения в этом теге.
public void release-0
Освобождает состояние.
public void removeValue(java.lang.String key)
Удаляет значение, ассоциированное с заданным ключом.
public void setld(java.lang.String id)
Задает атрибут id тега.
public void setPageContext(javax.servlet, jsp.PageContext pageContext)
Задает контекст страницы.
public void setParent(Tag parent)
Задает охватывающий тег для данного тега.
public void setValue(java.lang.String key, java.lang.Object value)
Связывает ключ co значением.
TagVariablelnfo
Этот класс предоставляет информацию для тега в библиотеке тегов.
Сигнатура
public class TagVariablelnfo
Методы
public java.lang.String getClassName()
Возвращает имя класса переменной.
public boolean getDeclareO
Указывает, должна ли быть объявлена переменная.
public java.lang.String getNameFromAttributeO
Возвращает атрибут, определяющий имя переменной.
public java.lang.String getNameGiven()
Возвращает имя переменной.
868
ПриложениеЕ
public static final java.lang.String BODY_CONTENT_TAG_DEPENDENT
Значение для метода getBodyContent, если существует зависимость от тега.
Методы
public TagAttributelnfo[] getAttributes()
Возвращает информацию атрибутов для этого тега.
public java.lang.String getBodyContent()
Возвращает информацию содержимого тела для этого тега.
public java.lang.String getDisplayNameO
Возвращает имя, выводимое на экран.
public java. lang. String getlnfoStringO
Возвращает информационную строку для этого тега.
public java.lang.String getLargelconO
Возвращает путь доступа к файлу большого значка.
public java.lang.String getSmallIcon()
Возвращает путь доступа к файлу маленького значка.
public java.lang.String getTagClassNameO
Возвращает имя класса, который предоставляет обработчика для этого тега.
public TagExtralnfo getTagExtralnfoO
Возвращает дополнительную информацию для этого тега.
public TagLibrarylnfo getTagLibraryO
Возвращает библиотечный экземпляр текущего тега.
public java.lang.String getTagNameO
Возвращает короткое имя тега.
public TagVariablelnfо[] getTagVariablelnfosO
Возвращает объекты TagVariablelnfo, ассоциированные с текущим Taginfo.
public Variablelnfо[] getVariable!nfo(TagData tagData)
Возвращает информацию времени выполнения об объекте сценария.
public boolean isValid(TagData tagData)
Указывает, является ли заданный TagData допустимым.
public void setTagExtra!nfo(TagExtraInfo tei)
Задает дополнительную информацию тега.
Установка и
конфигурирование
JBoss
ер вер JBoss (www.jboss.org) — это сервер приложений, который соответ-
ствует J2EE и полностью реализован на Java. Распространяемый согласно публичной
лицензии GNU, JBoss является полностью бесплатным. Не существует никакой
лицензионной схемы и даже платы за коммерческое использование.
JBoss обладает таки ми свойствами, как «горячее развертывание», «динамичес-
кие прокси» и полностью модульный дизайн на основе J MX, который позволяет
заменять практически любой компонент сервера. Такие возможности трудно най-
ти даже в очень дорогих коммерческих продуктах. Важным является также тот
факт, что JBoss является относительно небольшим, он использует меньше памя-
ти и дискового пространства и, следовательно, запускается быстрее. В настоящее
время JBoss версии 2.4.4 поставляется со встроенным сервером базы данных SQL
для обработки устойчивых Beans. Это добавление делает установку проще, пото-
му что не требуется загружать и устанавливать отдельный продукт. Сервер базы
данных запускается автоматически при запуске сервера, в отличие от некоторых
конкурирующих продуктов, где сервер базы данных должен запускаться отдель-
но. Что касается производительности, то существует отчет, согласно которому
JBoss превосходит некоторые из ведущих продуктов в этой области.
Конечно, J Boss имеет свои слабости. Подобно большинству продуктов с откры-
тым исходным кодом, JBoss не сопровождается документацией. Доступно несколько
статей типа «как сделать», однако этого недостаточно. Не существует технической
поддержки, хотя можно получить консультации у JBoss Group (не бесплатно, ко-
нечно). При возникновении проблемы приходится полагаться на список почтовой
870
ПриложениеЕ
Сигнатура
public abstract class TagLibraryValidator
Методы
public java.util.Map getlnitParametersO
Возвращает начальные параметры.
public void releaseO
Освобождает все удерживаемые данные.
public void setInitParameters(java.util.Map initParameters)
Задает начальные параметры объекта проверки (validator).
public java.lang.String validate(java.lang.String prefix,
java.lang.String uri, PageData pageData)
Проверяет страницу JSP.
TagSupport
Этот класс предоставляет удобные методы и является базовым классом, кото-
рый будет расширяться новыми обработчиками тегов.
Сигнатура
public class TagSupport implements IterationTag, java.io.Serializable
Методы
public int doAfterBody throws javax.servlet.jsp.JspException
Обрабатывает тело.
public int doEndTag throws javax.servlet.jsp.JspException
Обрабатывает конечный тег.
public int doStartTag throws javax.servlet.jsp.JspException
Обрабатывает начальный тег.
public static final Tag findAncestorWithClass(Tag from,
java.lang.Class class)
Возвращает экземпляр типа, ближайшего к указанному типу класса, начиная с
тега, заданного в качестве аргумента from.
public java.langString getld()
Возвращает атрибут id.
public Tag getParentO
Возвращает ближайший охватывающий тег.
Установка и конфигурирование JBoss
875
4. Распакуйте двоичный файл. Если вы загрузили двоичный файл как zip-файл
в каталог /tmp, то командой для распаковки будет unzip /tmp/jboss-2.x.zip.
Файлы будут извлекаться в каталог jboss каталога установки. Это каталог
JBOSS_HOME. Если каталогом установки является /usr/local, то каталогом
J BOSS_HOM Е будет /usr/local/jboss.
Если установка проходит гладко, появится структура каталогов, показанная на
рис. Е1.
deploy
Й-P | docs
‘—••Г I images
Й--Р 1 examples
Рис. F. 1. Структура каталогов J Boss
Структура каталогов
Все описываемые ниже каталоги указаны относительно JBOSS_HOME, т.е. верх-
него каталога установки J Boss.
bin
Все двоичные файлы, входящие в установку J Boss, располагаются в этом ката-
логе. Наиболее важными являются файлы run.bat и run.sh, которые используются
для запуска JBoss в Windows и Linux соответственно.
872
ПриложениеЕ
public int getScopeO
Возвращает область действия переменной.
Variableinfo
Этот класс предоставляет информацию о переменных сценария.
Сигнатура
public class Variableinfo
Поля
public static final int AT_BEGIN
Указатель области действия; означает, что переменная видима после начально-
го тега.
public static final int AT.END
Указатель области действия; означает, что переменная видима после конечного
тега.
public static final int NESTED
Указатель области действия; означает, что переменная видима только между
начальным и конечным тегами.
Методы
public java.lang.String getClassNameO
Возвращает имя класса этой переменной.
public boolean getDeclareO
Указывает, должна ли объявляться переменная.
public int getScopeO
Возвращает область действия переменной.
public java.lang.String getVarNameO
Возвращает имя переменной.
Установка и конфигурирование JBoss
877
Запуск JBoss
Для запуска JBoss в Linux выполните следующие действия:
• Проверьте, что вы имеете полномочия на запись в каталог JBoss (необходи-
мые для файлов журналов и развертывания).
• Перейдите в каталог bin из каталога JBoss и затем введите ,/run.sh.
Для запуска JBoss в Windows необходимо перейти в каталог bin и затем выпол-
нить файл run.bat.
Сервер должен запуститься без выдачи каких-либо сообщений об ошибках или
исключений. При запуске он создаст несколько выходных страниц.
Развертывание
Чтобы развернуть Bean, скопируйте его файл .jar в каталог deploy домашнего ката-
лога JBoss. Если Bean был загружен ранее, JBoss автоматически выгрузит его и затем
загрузит новую версию.
JBoss и Tomcat
Если клиент EJВ является сервлетом или страницей JSP, то для выполнения серв-
летов/JSP понадобится Tomcat, контейнер сервлетов/JSP. С целью улучшения про-
изводительности можно запустить Tomcat в той же самой виртуальной машине, что
и JBoss. Организация JBoss закончила интеграцию сервера JBoss с Tomcat версии
4.0.1. Можно загрузить этот пакет и установить его на своем компьютере.
Однако в типичном web-приложении обычно имеются статические страницы
HTML и динамические страницы. Неэффективным является подход, при котором
Tomcat обслуживает также страницы HTML. Обычно устанавливается масштаби-
руемый web-сервер, такой как Apache. Только запросы страниц сервлетов/JSP^ne-
ресылаются Tomcat.
Если Tomcat интегрируется в J Boss, выполнение Tomcat в виде модуля Apache
делает установку системы более сложной. Пока еще не существует одного един-
ственного загружаемого файла установки, который не требует конфигурирования
и может интегрировать JBoss с Tomcat и web-сервером. К счастью, скоро он будет
доступен, и тогда JBoss обеспечит лучшую системную производительность,
простоту установки и конфигурирования.
Заключение
JBoss —бесплатный сервер, показавший отличную производительность. К его свой-
ствам ОТНОСЯТСЯ ППЛСТЛТЯ игтяилиl/u и rnnauPA naTDAnTkiDQuuA
874
ПриложениеF
рассылки в поисках ответа. Перед тем как приступить к кодированию, полезно
просмотреть архивы, по крайней мере будет понятно, чего можно ожидать.
Системные требования
Обе версии JBoss, Linux и Windows, требуют, чтобы выполнялся JDK 1.3.
Необходим также компьютер, имеющий как минимум 64 МБайт оперативной
памяти. Не существует никакой спецификации, говорящей о минимальныхтребо-
ваниях J Boss к процессору, но Pentium 550 М Гц обеспечивает вполне удовлетвори-
тельную производительность. Конечно, как и для любого другого серверного про-
граммного обеспечения, чем больше оперативной памяти и мощности ЦП, тем
лучше. В терминах дискового пространства, JBoss требует нескольких мегабайтов
жесткого диска.
Установка JBoss
Одной из сильных сторон JBoss является простота установки. Однако прежде чем
приступить к установке, проверьте, что J DK работает.
Установка в Windows
Чтобы установить JBoss на машине Windows, выполните следующие действия:
1. Загрузите двоичный пакет (в формате .zip) из http://www.jboss.org/binary.jsp
в раздел Download-Binary. Сохраните файл zip во временном каталоге.
2. Распакуйте загруженный файл с помощью WinZip или аналогичного инстру-
мента и извлеките сжатые файлы в каталог, например в C:\jboss. Этот ката-
лог называется JBOSS_HOME.
Вот и все.
Установка в Linux
Выполните следующие действия, чтобы установить JBoss в Linux:
1. Загрузите двоичный файл из http://www.jboss.org/binary.jsp во временный ка-
талог:
lynx -source http://www.jboss.Org/bin/jboss-2.1.zip > jboss-2-1.zip
2. Выберите каталог для установки. В этом примере предполагается, что в каче-
стве каталога установки используется /usr/local.
3. Перейдите в выбранный каталог.
Дополнительные ресурсы
879
JSP
Дополнительную информацию о JSP можно найти на сайтах:
• http://java.sun.coni/products/jsp/index.htnil Сайт Sun Microsystems по JSP.
• http://www.jcp.org/aboutJava/communityprocess/final/jsr053/ Спецификация JSP 1.2.
• http://jakarta.apache.org/struts/index.html Среда разработки с открытым кодом для
создания web-приложений с помощью сервлетов и JSP.
Библиотека тегов
Ресурсы библиотеки тегов можно найти на следующем сайте:
•http://jakarta.apache.org/taglibs/index.html Библиотека тегов с открытым кодом
Jakarta Taglibs.
Контейнеры сервлетов/JSP
Эти сайты предоставляют дополнительные ресурсы для сервлетов/JSP:
• http://jakarta.apache.org/tomcat/index.html Контейнер сервлетов/JSP с открытым
кодом Jakarta Tomcat.
• http://www.newatlanta.com/ Контейнер сервлетов/JSP ServletExec компании New
Atlanta Communications.
• http://www.caucho.com/ Контейнер сервлетов/JSP Resin компании Caucho
Technology.
JDBC
Посетите следующие сайты, чтобы получить дополнительную информацию о JDBC:
• http://java.sun.com/products/jdbc/index.html Основной источникпотехнологии JDBC.
• http://java.sun.com/products/jdbc/index.html Страница для загрузки JDBC.
• http://java.sun.com/products/jdbc/index.html Список драйверов JDBC.
JNDI
Следующие сайты содержат ресурсы JNDI:
•http://java.sun.com/products/jndi/index.html Исходный koaJNDL
• http://java.sun.eom/products/jndi/index.html#download Источник для загрузки JND1.
• http://java.sun.com/products/jndi/tutorial/index.html Учебник по JND1.
JMS
Следующие сайты предоставляют дополнительные ресурсы JMS:
• http://java.sun.com/products/jms/index.html Web-сайт компании Sun Microsystems
по API JMS.
876
ПриложениеF
lib и lib/ext
Эти два каталога содержат библиотеки Java в форматах .jar и .zip, используемые
JBoss. Существует разделение между библиотеками, которые должны находиться в
системном пути доступа (т.е. jar-файлы в каталоге lib), и файлами в каталоге lib/ext,
которые делаются доступными серверу J Boss с помощью загрузчика классов М Let.
Если необходимо добавить некоторые библиотеки Java в JBoss — например, jar-
файлы драйвераjdbc, — следует поместить библиотеки в каталог lib/ext. Они будут
выбираться JBoss автоматически.
db
Этот каталог содержит файлы, имеющие отношение к базам данных hypersonic
и instantdb (конфигурационные файлы, индексные таблицы и т. д.), а также
J BossMQ — файлы очередей сообщений провайдера системы сообщений Java (JMS).
deploy
Каталог развертывания. Поместите свои файлы jar в этот каталог, и они будут
автоматически развернуты.
log
Каталог для файлов log. Регистрация файлов включена по умолчанию.
conf
Здесь располагаются конфигурационные настройки JBoss. По умолчанию
существует только один конфигурационный набор: default. Однако при желании
можно добавить один или несколько конфигурационных наборов.
client
Библиотеки, необходимые клиентам, находятся в каталоге client. Типичному
клиенту требуются jboss-client.jar, jbosssx-client.jar, jaas.jar, jnp-client.jar, ejb.jar и jta-
specl_0_l .jar. Если клиент не использует J DK1.3, то потребуется также jndi.jar. Если
будет применяться провайдер J MS J BossMQ, понадобится jbossmq-client.jar.
Конфигурирование
Для запуска JBoss в работу делать практически ничего не требуется. Может понадо-
биться только внести незначительные конфигурационные изменения для поддержки
определенных приложений. При изменении конфигурации следует изучить раздел
Advanced Configuration оперативной документации.
Дополнительные
ресурсы
В этом приложении приводятся ссылки на различные связанные с книгой темы,
которые помогут вам в дальнейшем изучении. Эти ресурсы содержат большой объем
информации: от учебных пособий по J2EE до спецификаций и серверов приложений.
J2EE
Дополнительные ресурсы по J2EE:
• http://java.sun.com/j2ee/ Сайт Sun Microsystems для платформы Java 2 Enterprise
Edition.
• http://java.sun.com/blueprints/enterprise/index.htm Ресурсы проектов Java
Enterprise.
• http://java.sun.com/j2ee/ sdk_1.3/index.htm Ссылка для загрузки Java 2 SDK,
Enterprise Edition.
• http://java.sun.com/blueprints/pattems/j2ee_pattems/index.html Образцы проекти-
рования J2EE.
• http://java.sun.com/j2ee/tutorial/l__3-fcs/index.html Учебники по J2fiE.
• http://java.sun.eom/blueprints/code/index.html#javajpet_store__demo Пример при-
ложения, который показывает, как использовать возможности платформы J2EE
1.3 для разработки гибких, масштабируемых, межплатформенных приложе-
ний уровня предприятия.
Сервлеты
Эти сайты предоставляют полезную информацию о сервлетах:
• http://java.sun.com/products/servlet/index.html Основной сайт по технологии сер-
влетов Java.
•http://www.jcp.org/abouUava/communityprocess/final/jsr053/Спецификация Servlet2.3.
• http://java.sun.com/products/servlet/Fiters.html Документы по фильтрам сервлетов.
880
Приложение G
•http://java.sun.com/products/jms/tutorial/l_3-fcs/doc/jms_tutorialTOC.html Учеб-
ники по JMS.
EJB
Следующие сайты предлагают дополнительную информацию о EJB:
• http.//java.sun.com/products/ejb/index.html Основной сайт компании Sun
Microsystems по Enterprise JavaBeans.
•http://java.sun.eom/products/ejb/2.0.html Спецификация EJB 2.0.
Сервер J2EE
Ресурсы J2EE:
• http://www.jboss.org Сервер приложений JBoss с открытым исходным кодом.
• http://www.weblogic.com Сервер приложений WebLogic компании BEA Systems.
• http://www-4.ibm.com/software/webservers/appserv/ Сервер приложений WebSphere
компании IBM.
• http://www.bluestone.com/products/hp-as/default.htm Сервер приложений HP-AS
компании HP.
• http://orion.evermind.net/ Web-сайт сервера приложений Orion.
• http://www.iplanet.com/ Web-сайт сервера приложений iPlanet.
• http://www.oracle.com/ip/deploy/ias/ Сервер приложений Oracle.
• http://www.silverstream.com/Website/app/en_US/AppServer Сервер приложений
eXtend компании Silverstream.
• http://www.gemstone.com/products/ Сервер приложений Gemstone.
•http://www.borland.com/bes/appserver/ Сервер приложений Borland.
• http://www.persistence.com/products/powertier/index.php Сервер приложений
PowerTier компании Persistence.
• http://www.trifork.com/ Сервер приложений Trifork.
• http://www.macromedia.com/software/jrun/ Сервер приложений JRun компании
Macromedia.
• http://www.interstage.com/CepBep приложений Interstage компании Fujitsu.
• http://www.hitachi.co.jp/Prod/comp/softl/open-e/Cosminexus/index/index.html
Сервер приложений Cosminexus компании Hitachi.
• http://www.enterprisebeans.de/CepBep приложений In-Q-My.
• http://www.interactivebusiness.com/EASlnfo/framesetl .html Сервер приложений 1BS
Enterprise.
• http://www.iona.com/products/appserv.htm Сервер приложений E2A0rbix компа-
нии Iona.
•http://www.lutris.com/ Сервер приложений Lutris.
• http://www.pramati.com/ Сервер приложений Pramati.
• http://www.secant.com/products/ES/index.html Сервер ModelMethods Enterprise
компании Secant.
• http://www.sybase.com/products/easerver Сервер ЕА компании Sybase.