Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

введение в JSP. В ней рассматривается около 50 пользовательских дескрипторов JSP

Работа добавлена на сайт samzan.net:

Поможем написать учебную работу

Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 3.6.2024

JAVASERVER  PAGES

Использование сервлетов и JSP при проектировании и реализации гибких, расширяемых приложений, удобных в сопровождении

Мощные средства

аутентификации

и интернационализации

Совместное использование стандартов XML и XSLT и технологии JSP

Применение шаблонов JSP для разработки приложений на базе компонентов

Sun

microsystems

ДЭВИД М. ГЕРИ

Netz.ru

Данная книга начинается с рассмотрения пользовательских дескрипторов, т.е. с тех вопросов, которыми обычно заканчиваются книги, представляющие собой введение в JSP. В ней рассматривается около 50 пользовательских дескрипторов JSP. Они выполняют различные задачи: от поддержки форматов, специфических для разных стран, до разбора XML-кода с использованием Document Object Mode!. Поддержка пользовательских дескрипторов — одно из главных преимуществ JSP, поскольку данная возможность позволяет организовывать одновременную работу нескольких специалистов, при этом они практически не зависят друг от друга. Далее В книге рассматриваются HTML-формы, JSP-шаблоны, архитектуры Model I и Model 2, поддержка событий, вопросы безопасности, работа с базами данных и XML. В последней главе продемонстрировано использование данных технологий при создании реального Web-приложения. Главной целью было рассказать читателю о том, как с помощью компонентов bean, сервлетов и JSP создаются гибкие расширяемые приложения, удобные в сопровождении.

Содержание

Введение 10

Глава 1. Основы построения пользовательских дескрипторов 17

Применение пользовательских дескрипторов — JSP-файл 19

Определение пользовательских дескрипторов — файл описания 20
Реализация пользовательских дескрипторов — класс поддержки

дескриптора 21

Ссылка на описание библиотеки в WEB-INF/web.xml 24

Элементы <taglib> и <tag> 24

Жизненный цикл пользовательского дескриптора 26

Организация взаимодействия потоков 27

Пользовательские дескрипторы с атрибутами 28

Доступ к информации о документе 32

Обработка ошибок 35

Классы для реализации пользовательских дескрипторов 35

Тело дескриптора 39

Глава 2. Дополнительные вопросы создания пользовательских

дескрипторов 43

Обработчики тела дескриптора 44

Итерации 46

Переменные сценария 49

Тело дескриптора 54

Вложенные дескрипторы 63

Глава 3. HTML-формы 67

Формы и компоненты bean 67

Проверка корректности данных 74

Базовый набор классов для работы с формами 82

Применение пользовательских дескрипторов 95

Глава 4. Шаблоны 99

Инкапсуляция алгоритмов компоновки 100

Необязательное содержимое 104

Содержимое, зависящее от роли 107

Раздельное определение областей 108

Вложенные области 110

Расширение областей 112

Объединение различных подходов к созданию областей 113

Реализация дескрипторов поддержки областей 115

Содержание

Глава 5. Разработка WEB-приложений 131

Model 1 132

Model 2: архитектура MVC 133

Пример использования архитектуры Model 2 134

Глава 6. Базовый набор классов для создания приложений Model 2 149

Базовый набор классов Model 2 149

Модернизация программ 157

Учет новых сценариев развития 162

Применение пользовательских дескрипторов 167

JSP-c цен арии 169

Глава 7. Поддержка событий 173

Поддержка событий в базовом наборе классов Model 2 174

Повторная активизация форм 178

Глава 8.I18N 197

Unicode 197

Кодировки 199

Поддержка регионов 201

Наборы ресурсов 202

Форматирование данных, чувствительных к региону 212

Языки, поддерживаемые броузерами 219

Пользовательские дескрипторы 223

Глава 9, Защита 235

Аутентификация 235

Базовая аутентификация 239

Дайджест-аутентификация 242

Аутентификация на основе форм 242

Использование SSL и сертификата клиента 246

Настройка процедуры аутентификации 246

Элементы защиты WеЬ-приложений 251

Программная аутентификация 254

Глава 10. Работа с базами данных 267

Создание базы данных 268

Источники данных 270

Пользовательские дескрипторы для работы с базами данных 271

Пул соединений 283

Предварительно подготовленные выражения 294

Транзакции 300

Прокрутка набора результатов 303

Глава 11. XML 309

Генерация XML-документов 311

Обработка сгенерированных XML-данных 316

Разбор XML-кода 317

8         Содержание

Преобразование XML-документов 347

Использование XPath 356

Глава 12. Приложение на базе JSP 363

Интерактивный магазин 364

Базовый набор классов Model 2 385

Интернационализация 403

Аутентификация 409

HTML-формы 420

Повторная активизация чувствительных форм 427

SSL 428

XML и DOM 429

Приложение Л. Фильтры сервлетов 435

Пример фильтра 436

Предметный указатель 439

ВВЕДЕНИЕ

, ■ ;■,:

Вскоре после того, как в марте 1999 г. был опубликован том Graphic Java, посвященный Swing, я заметил, что Java-программы, предназначенные для работы на стороне сервера, приобретают все большую популярность. Пришлось задуматься, не следует ли посвятить мою следующую книгу именно этим вопросам. Несмотря на то что все мое время было заполнено увлекательными экспериментами с XML, XSLT и Java, я отдавал себе отчет в том, что эти средства играют, скорее, вспомогательную роль при создании Web-приложений. Основной технологией, как мне тогда казалось, являются сервлеты.

Надо признаться, сервлеты не приводили меня в восторг. Я недоумевал, как могут разработчики мириться с тем, что им приходится создавать интерфейсные элементы, формируя HTML-код посредством операций печати. С 1984 г. я участвовал в программных проектах, где к моим услугам были объектно-ориентированные языки и инструментальные средства создания пользовательских интерфейсов. Я имел опыт разработки приложений на Smalltalk, Eiffel и NeXTSTEP и мне казалось, что использование HTML, а в особенности написание программных кодов для генерации HTML-элементов, можно сравнить с попытками ездить на спортивном автомобиле по песчаным насыпям.

В 1999 г. технология JSP делала свои первые шаги, но уже тогда можно было понять, насколько она перспективна. Благодаря JSP появилась возможность объединять Java и HTML и открылись новые перспективы создания Web-приложений. Кроме того, в спецификации JSP 1.0 одна фраза привлекла особое внимание. Речь шла о том, что в спецификации JSP 1.1 будет предусмотрена поддержка расширяемых дескрипторов; эти дескрипторы можно будет применять в любом JSP-документе. Разработчик получал возможность создавать собственные элементы, инкапсулировать в них Java-код и включать в документ как обычные дескрипторы. Я понял, что темой моей следующей книги будет JSP.

Я начал работать над введением в JSP и даже написал первую главу, но тут мне пришлось пересмотреть свое решение. На то были две причины. Во-первых, в изданиях, рассчитанных на начинающих, нет недостатка, а я не хотел, чтобы моя книга стала лишь одной из многих. Во-вторых, первая глава получилась скучной, а я терпеть не могу скучных книг. Поэтому я решил оставить начатую работу и написать книгу, которую вы держите в руках.

Введение       11

О чем эта книга

Как видно из названия, эта книга о javaServer Pages, в частности о расширенных средствах, предоставляемых в распоряжение разработчика JSP-документов. Главной целью было рассказать читателю о том, как с помощью компонентов bean, сервлетов и JSP создаются гибкие расширяемые приложения, удобные в сопровождении.

Данная книга начинается с рассмотрения пользовательских дескрипторов, т.е. с тех вопросов, которыми обычно заканчиваются книги, представляющие собой введение в JSP. Поддержка пользовательских дескрипторов— одно из главных преимуществ JSP, поскольку данная возможность позволяет организовывать одновременную работу нескольких специалистов, при этом они практически не зависят друг от друга. Далее в книге рассматриваются HTML-формы, JSP-шаблоны, архитектуры Model 1 и Model 2, поддержка событий, вопросы безопасности, работа с базами данных и XML. В последней главе продемонстрировано использование данных технологий при создании реального Web-приложения.

API сервлетов и JSP

Коды программ, приведенные в данной книге, соответствуют спецификациям Servlet 2.2 и JSP 1.1. Несмотря на то что проекты спецификаций Servlet 2.3 и JSP 1.2 появились в ноябре 2000 года, к моменту выхода этой книги в печать они постоянно дорабатывались. Важным дополнением, которое появилось в спецификации Servlet 2.3, стали фильтры сервлетов; они описаны в приложении А. Однако имейте в виду, что к тому времени, как эта книга попадет к вам в руки, спецификация может измениться.

Как тестировались коды

Все коды, приведенные в данной книге, были протестированы с помощью сервера Tomcat 3.2.I. Некоторые коды, в частности примеры, связанные с аутентификацией, некорректно работают с Tomcat S.2.1, подобные случаи специально оговорены в тексте книги.

Поскольку Tomcat является основным сервером для сервлетов hJSP, коды, приведенные в этой книге, должны работать с любым контейнером сервлетов, который соответствует Servlet 2.2 и JSP 1.1 (либо более поздним версиям этих спецификаций). Если пример из книги некорректно работает с вашим контейнером сервлетов, причиной тому, вероятнее всего, является ошибка в реализации контейнера.

Примеры из данной книги были также протестированы с помощью контейнера сервлетов Resin 1.2 (он доступен по адресу http://www.caucho.com). Чтобы убедиться, что код, написанный вами, работает корректно и является переносимым, желательно протестировать его с помощью нескольких доступных вам контейнеров.

12   Введение

На кого рассчитана эта книга

Данная книга написана для разработчиков, имеющих опыт работы на языке Java и знакомых с сервлетами и JSP. Для большинства читателей эта книга станет второй книгой о сервлетах hJSP, прочитанной ими. Если же вы еще никогда не встречались с сервлетами и JSP, я рекомендую вам следующие книги для начинающих.

Core Servlets andJSP, Marty Hall, Sun Microsystems Press.

Java Servlet Programming, Jason Hunter, O'Reilly.

Web Development with JavaServer Pages, Fields и Kolb, Manning.

Читателю не помешает также познакомиться с шаблонами проектов и UML (Unified Modeling Language— унифицированный язык моделирования). В этой книге используются диаграммы, которые показывают взаимосвязь между классами. Список ресурсов, имеющих отношение к шаблонам проектов и UML, приведен в конце главы 6.

Данная книга не предназначена для авторов Web-страниц. Если вы создаете HTML-документы, но не имеете опыта программирования на Java, знакомство с JSP вам лучше начать с одной из книг, перечисленных выше.

Как создавалась эта книга

Создание объектно-ориентированных программ — это, как правило, итеративный процесс. Вы начинаете с нескольких классов, добавляете новые, дорабатываете созданные ранее, постоянно вносите изменения до тех пор, пока не будет завершена работа над системой. Эту процедуру принято называть доводкой («factoring).

Проработав 15 лет программистом, я привык писать книги по тому же принципу, что и программы. Каждая глава начиналась с нескольких "штрихов" и в процессе доводки выглядела так, как вы видите ее сейчас.

Вы можете представить себе процесс доводки, ознакомившись с моей статьей, посвященной JSP-шаблонам, которая была опубликована в JavaWoHd (http: //developer. Java.sun.com/developer/technicalArticles/javaserverpages/jsp_templates) . Эта статья стала "исходным материалом" для главы 4 данной книги. Сравнив статью с главой 4, вы увидите, с чего начиналась работа над главой и чем закончилась. Как текст, так и коды были существенно переработаны.

Как пользоваться этой книгой

Эта книга— не роман, поэтому вряд ли вы сядете и прочтете ее "от корки до корки". Поскольку многие предпочитают читать главы в произвольном порядке, каждая глава написана так, что она практически не зависит от других. Исключение составляет глава 6, посвященная Model 2. Архитектура Model 2 рассмотрена в главе 5, поэтому эту главу желательно прочитать перед главой 6.

В последней главе этой книги показано, как технологии, рассмотренные ранее, применяются для создания Web-приложения. Если вы хотите составить представление об этих технологиях, просмотрите последнюю главу перед тем, как приступать к чтению.

Введение       13

Библиотеки пользовательских дескрипторов

5 данной книге рассматривается около 50 пользовательских дескрипторов JSP. Они выполняют различные задачи: от поддержки форматов, специфических для разных стран, до разбора XML-кода с использованием Document Object Model. Эти дескрипторы вы можете свободно использовать в своих разработках. Адрес, по которому расположены коды дескрипторов, будет указан ниже,

Рассмотрение пользовательских дескрипторов в данной книге преследует две цели. Во-первых, приведенный код служит примером пользовательских дескрипторов. Во-вторых, они подтверждают основные идеи, которые обсуждаются в книге. Например, в главе, посвященной поддержке кодировок и форматов различных стран, обсуждаются вопросы локализации текста, числовых значений, дат и денежных единиц. В конце этой главы показано, как реализуются пользовательские дескрипторы, предназначенные для выполнения этих задач.

Коды, приведенные в книге

Коды, приведенные в данной книге, в том числе библиотеки пользовательских дескрипторов, вы можете скопировать, обратившись по адресу http://www.phptr. com/advj sр.

Соглашения, принятые в книге

В табл. 0.1 приведены основные соглашения, используемые для представления программного кода.

Соглашение

Пример

Имена классов начинаются с прописной буквы

public class ClassName

Имена методов начинаются со строчной буквы;    getLength остальные слова, входящие в имя метода, начинаются с прописной буквы

private int length

private int bufferLength

Имена переменных начинаются со строчкой буквы; остальные слова, входящие в имя переменной, начинаются с прописной буквы

В большинстве случаев имена методов приводятся в тексте без параметров. Параметры указываются лишь тогда, когда это необходимо по ходу обсуждения. В табл. 0.2 приведены соглашения о представлении текста.

14         Введение

Таблица 0.2. Соглашения о представлении текста

Шрифт Использование

courier Команды, имена файлов, имена классов, методы, параметры,

ключевые слова Java, HTML-дескрипторы, текст в составе файла, фрагменты кода и URL

courier Текст, введенный в командной строке, а также важные фраг-

полужирный менты листингов

курсив Определения, фрагменты текста, требующие особого внима-

ния, названия книг, а также переменные, которые должны быть заменены реальными значениями

Введение        15

основы

ПОСТРОЕНИЯ

ПОЛЬЗОВАТЕЛЬСКИХ

ДЕСКРИПТОРОВ

В этой главе...

Применение пользовательских дескрипторов — JSP-файл.

Определение пользовательских дескрипторов — файл описания.

Реализаций пользовательских дескрипторов — класс

поддержки дескриптора.

Ссылка на описание библиотеки в web-INF/web. xml.

Жизненный цикл пользовательского дескриптора.

Организация взаимодействия потоков.

Пользовательские дескрипторы с атрибутами.

Доступ к информации о документе.

Обработка ошибок.

Классы для реализации пользовательских дескрипторов.

Интерфейс Tag.

Класс TagSupport: предки, значения и идентификаторы.
Тело дескриптора.

XML считается перспективной технологией; одна из причин такого отношения специалистов заключается в том, что XML является простым метаязыком, используемым для создания дескрипторов. XML-дескрипторы представляют данные, специфические для конкретной предметной области. Например, следующий фрагмент XML-кода описывает набор компакт-дисков:

<cdcollection> <cd>

<artist>Radiohead</artist>

<title>OK Computer</title>

<price>$14.99</price> </cd>

</cdcollection>

Подобно XML, JSP также можно использовать для определения дескрипторов, но если XML-дескрипторы представляют данные, то пользовательские дескрипторы JSP представляют функции, специфические для конкретной области1. Например, в фрагменте JSP-кода, представленном в листинге 1.1, пользовательские дескрипторы используются для отображения таблицы базы данных.

Листинг 1.1. Применение пользовательских дескрипторов для доступа к базе данных

<html><title>Database  Example</title> <head>

<%@  taglib uri='/WEB-INF/tlds/database.tld'   prefix='database'%> </head> <body>

1 Термин пользовательский дескриптор введен для того, чтобы отличать дескрипторы, созданные разработчиками, от встроенных дескрипторов. В XML встроенные дескрипторы отсутствуют, поэтому необходимость в специальном термине отпадает. - Прим. авт.

18        Глава 1. Основы построения пользовательских дескрипторов

<database:connect database= 'F:/databases/sunpress'> <database:query>

SELECT  *   FROM Customers,   Orders </database:query>

<table border='2'   cellpadding='5'>

<database: columnNames  columnName='column_name'>

<th><%=  column_name  %></th> </database:columnNames>

<database: rows><tr>

<tr><database:columns  columnValue='colurnn_value'>

<td><%= column_value   %></td> </database:columns></tr> </database:rows> </table> </database:connect>

</body> </html>

Код, приведенный в листинге 1.1 (он содержит как HTML-дескрипторы, так и пользовательские дескрипторы JSP), создает HTML-таблицу. Дескриптор connect устанавливает соединение с базой данных, а дескриптор query выполняет запрос к базе. Дескрипторы rows, columns и columnNames предназначены для перебора результатов.

Как видно из листинга 1.1, пользовательские дескрипторы JSP могут выполнять достаточно сложные действия. Код, содержащийся между открывающим и закрывающим дескрипторами query, интерпретируется как SQL-запрос, кроме того, connect и query взаимодействуют с другими дескрипторами, присутствующими в данном документе. Дескрипторы columns и columnNames осуществляют перебор результатов запроса и создают переменные сценария, имена которых задает разработчик JSP. В документе, представленном в листинге 1,1, определены имена col-umn_name и со1шпп_value.

Пользовательские дескрипторы обладают различными характеристиками, которые перечислены ниже.

Между открывающим и закрывающим дескрипторами могут содержаться дан
ные, либо дескриптор может быть пустым.

Дескриптор может находиться в составе другого пользовательского дескрип
тора; в общем случае глубина вложенности дескрипторов не ограничена,

Дескрипторы могут управлять потоком управления, в частности реализовывать
условные операторы или циклы.

Дескриптор, содержащий данные, может преобразовывать их, в частности
осуществлять фильтрацию либо редактирование.

Пользовательские дескрипторы могут взаимодействовать с другими дескрип
торами в составе документа.

Применение пользовательских дескрипторов — JSP-файл        19

Пользовательские дескрипторы имеют доступ к информации о документе {по
средством предопределенных переменных request, response, session и т.д.).

Дескрипторы могут создавать переменные сценария.

Пользовательские дескрипторы JSP соответствуют спецификации XML; по сути, они являются XML-дескрипторами. Это очень важно, поскольку для обработки JSP-дескрипторов могут применяться инструментальные средства XML и HTML.

Все пользовательские дескрипторы, приведенные в листинге 1.1, содержат открывающий (начальный) и закрывающий (конечный) дескрипторы, между которыми находятся данные. Пользовательский дескриптор может быть также пустым, при этом открывающий и закрывающий дескрипторы объединяются следующим образом:

<prefix:someTag/>

Все пользовательские дескрипторы JSP объединяются в библиотеки. С библиотекой связывается префикс, который позволяет различать дескрипторы с одинаковыми именами, принадлежащие разным библиотекам.

В то время когда использовалась JSP 1.1, было предложено определить в следующей версии спецификации стандартную библиотеку дескрипторов, которая содержала бы средства доступа к базам данных и другие вспомогательные дескрипторы".

Совет

Пользовательские дескрипторы — мощное средство JSP

Наличие пользовательских дескрипторов позволяет организовать работу так, чтобы авторы Web-страниц и разработчики программного обеспечения могли решать свои задачи практически независимо друг от друга. При этом авторы Web-страниц ориентируются на использование HTML-, XML- и JSP-дескрипторов, а разработчики программ сосредоточивают свое внимание на реализации низкоуровневых средств, предназначенных, например, для поддержки форматов или организации доступа к базам данных. Результаты работы программистов становятся доступными авторам Web-страниц в виде пользовательских дескрипторов.

Применение пользовательских дескрипторов — JSP-файл

На рис. 1.1 показан JSP-документ, в котором применяется простейший пользовательский дескриптор. В нем отсутствуют атрибуты и тело дескриптора. Данный дескриптор поддерживает счетчик обращений kJSP.

2 Подробнее см. Java Specification Request (JSR) на сервере http://java.sun.com. - Прим. авт.

20        Глава 1. Основы построения пользовательских дескрипторов

Листинг 1.2,а. /test, jsp

<body>

<%@  taglib uri='/WEB-INF/tlds/counter.tld'   prefix-'util'   %> This page has been accessed <b><util:counter/></b>  times. </body></html>

В листинге 1.2,а присутствует директива taglib, в которой указывается URI описания библиотеки дескрипторов {TLD— tag library descriptor). Файл, содержащий описание библиотеки дескрипторов, обычно имеет расширение .tld. В директиве taglib также указывается атрибут prefix, который задает префикс, используемый при обращении к дескрипторам данной библиотеки. В листинге 1.2,а атрибут prefix имеет значение util, поэтому для обращения к дескриптору counter используется выражение <util:counter/>.

Описание библиотеки дескрипторов для примера, приведенного в листинге 12,а, содержится в каталоге WEB-INF/tIds. Такое расположение описания рекомендуется, но не является обязательным.

Определение пользовательских дескрипторов — файл описания

Описание библиотеки дескрипторов представляет собой XML-документ, в котором определены библиотека и принадлежащие ей дескрипторы. Пример TLD-файла приведен в листинге 1.2,6. Это описание используется в JSP-документе. показанном в листинге 1.2,а.

Реализация пользовательских дескрипторов...        21

Листинг 1.2,6. /WEB-INF/tlds/counter. Tld

<?xml version="1.0" encoding="ISO-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>

<jspversion>1.1</jspversion>

<shortname>Sun Microsystems Press examples</shortname>

<info>Example 3.2 from the book</info>

<tag>   

 <name>counter</name>

 <tagclass>tags.CounterTag</tagclass>

 <bodycontent>empty</bodycontent>

</tag>

</taglib>

Первая строка указывает на то, что содержимое файла является XML-документом. Вторая строка уточняет тип документа как taglib и задает URL DTD (document type definition — определение типа документа), в котором описывается структура документа taglib, DTD используется контейнерами сервлетов для проверки корректности документа.

Для определения библиотеки дескрипторов служит элемент taglib. В листинге 1.2,6 задана версия библиотеки 1.0 и указано, что она может использоваться средствами поддержки JSP, которые соответствуют JSP 1.1 или более поздней версии спецификации.

Кроме приведенных выше сведений, для библиотеки задается имя и краткое описание. Эти данные используются средствами поддержки общей информацш I о документе.

Дескрипторы определяются с помощью элемента tag, в состав которого входят два обязательных элемента: name и tagclass. Элемент tagclass задает Java-класс, реализующий функции пользовательского дескриптора. Этот класс называется классом поддержки дескриптора.

В листинге 1.2,6 также указано, что тело дескриптора должно отсутствовать, поэтому, если включить между открывающим и закрывающим дескрипторами некоторые данные, будет считаться, что этот дескриптор задан некорректно.

В составе как taglib, так и tag могут содержаться дополнительные данные. Более подробная информация об этих элементах будет приведена далее в этой главе.

Реализация пользовательских дескрипторов — класс поддержки дескриптора

Класс поддержки дескриптора реализует интерфейс Tag, принадлежащий пакету javax.servlet. jsp. tagext. Дополнительная информация об интерфейсе Tag будет приведена далее в данной главе. В этом интерфейсе объявлены шесть методов; три из них, которые используются наиболее часто, представлены ниже.

22        Глава 1. Основы построения пользовательских дескрипторов

int doStartTaq()   throws  JspException int doEndTaq()   throws  JspException void release ()

Контейнер сервлетов вызывает методы интерфейса Tag в том порядке, в котором они перечислены здесь. Методы doStartTag и doEndTag вызываются соответственно при встрече открывающего и закрывающего дескрипторов. Каждый из методов возвращает целочисленное значение; как правило, для этого используются константы, определенные в интерфейсе Tag. Возвращаемое значение задает поведение контейнера сервлетов после завершения метода.

После вызова метода doEndTag контейнер вызывает метод release. Этот метод должен освободить ресурсы, которые испол ьзовались классом поддержки дескриптора.

В листинге 1.2,в показан пример реализации методов, объявленных в интерфейсе Tag.

Листинг 1.2,в. /WEB-INF/classes/tags/CounterTag. java

package tags;

import java.io.File; import Java.io.FileReader; import Java.io.FileWriter; import Java.io.IOException;

import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; import javax.servlet.http.HttpServletRequest;

public class CounterTag extends TagSupport [ private int count = 0; private File file = null;

public int doStartTag() throws JspException { try (

checkFilef); readCount ();

pageContext.getOut().print(++count); ) catch(java.io.lOException ex) f

throw new JspException(ex.getMessage()); }

return SKIP_BODY; )

public int doEndTag() throws JspException { saveCount(); return EVA_PAGE; }

private void checkFile() throws JspException, lOException { if(file = = null) {

file  = new File(getCounterFilename()); count = 0;

}

if(!file.exists()) {

file.createNewFile();

saveCount ( );

Реализация пользовательских дескрипторов...        23

private   String  getCounterFilename()    {

HttpServletRequest   req =   (HttpServletRequest)pageContext.

getRequest();

String servletPath =  req.getServletPath ();
String realPath = pageContext.getServletContext
().

getRealPath(servletPath);

return  realPath  +   ".counter"; )

private  void  saveCount0   throws   JspException   ( try   {

FileWriter writer =  new  FileWriter (file);

writer.write (count);

writer.close(); 1 catch(Exception  ex)    {

throw new  JspException(ex.getMessage());

private void  readCount()   throws   JspException   { try   {

FileReader  reader  = new  FileReader(file);

count  =  reader.read();

reader.close(); } catch(Exception  ex)    {

throw new JspException[ex.getMessage());

   }

 }

}

Показанный в листинге класс поддержки дескриптора подсчитывает число обращений к дескриптору, а следовательно, и к содержащему его JSP-документу. Информация о числе обращений хранится в файле. Имя этого файла совпадает с именем соответствующего JSP-документа, кроме того, к нему добавляется суффикс . counter. Например, если в файле /index, jsp содержится пользовательский дескриптор, реализующий счетчик обращений, информация о числе обращений хранится в файле /index, jsp. counter.

Подобно многим классам поддержки дескрипторов, класс tags . CounterTag реализован как подкласс TagSupport. Класс TagSupport реализует интерфейс Tag, кроме того, он содержит ряд вспомогательных методов. Подробно класс TagSupport будет обсуждаться далее в этой главе.

Метод CounterTag. doStartTag выводит число обращений, используя для доступа к объекту out переменную pageContext, определенную в классе TagSupport как protected. По окончании выполнения метод возвращает значение SKIP_BODY, указывающее на то, что тело дескриптора, если оно существует, должно игнорироваться. После этого контейнер вызывает метод CounterTag.doEndTag, который возвращает значение EVAL_PAGE. Данная константа означает, что контейнер сервлетов должен обрабатывать остаток документа, который следует за закрывающим дескриптором.

Переменные экземпляра count и file инициализируются при каждом вызове метода doStartTag, поэтому метод release в данном классе не переопределяется.

24        Глава 1. Основы построения пользовательских дескрипторов

Ссылка на описание библиотеки B WEB-INF/web.xml

В JSP-файлс, представленном в листинге 1.2,а, директива taglib использовалась для непосредствнного обращения к описанию библиотеки дескрипторов. Эта директива может определять описание библиотеки косвенным образом, ссылаясь на другую директиву taglib, содержащуюся в дескрипторе доставки Web-приложения. Например, директива taglib в листинге 1.2,а могла бы выглядеть следующим образом:

//  В JSP-файле   ...

<%@   taglib uri='counters'   prefix='util'   %>

Элемент taglib дескриптора доставки Web-приложения определяет URI counters и задает расположение TLD-файла.

// в файле web.xml  ...

<taglib>

<taglib-uri>counters</taglib-uri>

<taglib-location>/WEB-INF/tldg/counter,tld</taglib-location> </taglib>

Косвенное указание описания библиотеки дескрипторов обеспечивает большую гибкость, поскольку TLD-файл можно заменить, не внося изменений eJSP-файл. Таким образом, при доставке приложений следует отдавать предпочтение косвенному указанию. Прямое указание TLD реализуется проще и, как правило, используется в процессе разработки.

Совет

Создание простых пользовательских дескрипторов

Пользовательские дескрипторы реализуются достаточно просто, особенно если в них не предусмотрены атрибуты и.тело дескриптора отсутствует. Для создания пользовательского дескриптора надо выполнить следующие действия.

Включить в jSP-файл, в котором используется дескриптор, директиву taglib.

Создать описание библиотеки дескрипторов (TDL-файл).

Реализовать класс поддержки дескриптора как подкласс класса TagSupport и
переопределить в нем методы doStartTag и doEndTag.

Элементы <taglib> и <tag>

В данном разделе подробно описываются особенности применения элементов taglib и tag в описании библиотеки дескрипторов (TLD-файле). Пример использования этих дескрипторов см. в листинге 1.2,6. В табл. 1.1 перечислены элементы, входящие в состав taglib.

Элементы <taglib> и <tag>        25

Таблица 1.1. Элементы в составе taglib {перечислены в том порядке, в котором они включаются в данный элемент)

Элемент Тип     Описание

tlibversion      1         Версия библиотеки дескрипторов

jspversion        ?         Версия спецификации JSP, используемой при создании библиотеки. По умолчанию принимается спецификация JSP 1,1

shortname 1 Используется средствами авторизации для идентификации

библиотеки

uri ? URI, однозначно идентифицирующий библиотеку

info ? Описание, поясняющее порядок использования библиотеки

tag + Дескрипторы, содержащиеся в библиотеке

В столбце "Тип" использованы следующие обозначения:

1 — один элемент;

? — элемент может отсутствовать;

♦ — один или более элементов.

Для определения пользовательских дескрипторов служит элемент tag. Пример определения дескриптора counter см, в листинге 1.2,6. В нем заданы имя дескриптора, класс поддержки, сведения о том, что тело дескриптора отсутствует, а также информация о дескрипторе. Элементы, содержащиеся в составе tag, перечислены в табл. 1.2.

Таблица 1„2. Элементы в составе tag (перечислены в том порядке, в котором они включаются в данный элемент)

Элемент Тип     Описание

name 1 Имя, следующее за. префиксом,  например <prefix: name ...>

tagclass 1 Класс поддержки, реализующий интерфейс Tag

teiclass ? Класс, определяющий переменные сценария для дескриптора

bodycontent ? Описание тела дескриптора:

Дескриптор   самостоятельно   обрабатывает   содержа
щиеся в нем данные

Контейнер сервлетов обрабатывает тело дескриптора

• Тело дескриптора должно отсутствовать
info                        ?          Информация о дескрипторе

attribute * Атрибут дескриптора

В столбце "Тип" использованы следующие обозначения: 1 — один элемент;

26        Глава 1. Основы построения пользовательских дескрипторов

? — элемент может отсутствовать; * — нуль или более элементов.

Атрибуты определяются с помощью элемента attribute, который будет рассмотрен далее в этой главе.

Элемент bodycontent указывает, какие действия должен выполнять контейнер сервлетов с телом дескриптора. По умолчанию предполагается значение JSP, в соответствии с которым тело дескриптора должно обрабатываться контейнером. Если элемент bodycontent содержит значение tagdependent, контейнер не обрабатывает тело дескриптора; такую обработку выполняет класс поддержки. Значение empty указывает на то, что тело пользовательского дескриптора должно отсутствовать.

Жизненный цикл пользовательского дескриптора

Класс поддержки пользовательского дескриптора представляет собой программный компонент, включаемый в контейнер сервлетов. Контейнер создает экземпляр класса поддержки, инициализирует его и вызывает методы doStartTag, doEndTag и release. Для простых дескрипторов инициализация сводится к вызову методов setPageContext и setParent. Поскольку контейнер вызывает метод release класса поддержки, этот класс может быть подготовлен к последующему использованию.

На рис. 1.2 показана диаграмма взаимодействия класса поддержки дескриптора, рассматриваемого в данной главе в качестве примера, с контейнером сервлетов.

Организация взаимодействия потоков        27

Взаимодействие, представленное на рис. 1.2, типично для простых дескрипторов без атрибутов, тело которых отсутствует. Контейнер сер влетов вызывает метод doStartTag, который выполняет требуемые действия и возвращает значение SKIP_BODY. Значение SKIP_BODY указывает на то, что тело дескриптора, если даже оно присутствует, ие должно обрабатываться.

Почти для всех пользовательских дескрипторов метод doEndTag возвращает значение EVAL_PAGE, в результате чего контейнер продолжает обработку остальной части JSP-документа. После завершения метода doEndTag контейнер сервлетов вызывает метод release, при выполнении которого освобождаются ресурсы (например, закрываются соединения с базами данных) и устанавливаются необходимые значения переменных.

Для более сложных дескрипторов диаграммы взаимодействия также имеют более сложный вид. Например, если тело дескриптора должно обрабатываться, класс поддержки содержит дополнительные методы, вызываемые контейнером. Диаграммы взаимодействия для дескриптора с атрибутами и дескриптора, в котором предусмотрена многократная обработка содержащихся в нем данных, показаны соответственно на рис. 1.5 и 2.2.

Организация взаимодействия потоков

Относительно времени жизни экземпляров класса Tag в спецификации JSF 1.1 сказано следующее.

На этапе выполнения реализация JSP-документа применяет доступные экземпляры класса Tag... если они уже не используются .... По окончании работы экземпляр класса освобождается с тем, чтобы он был доступен для последующего применения.

Таким образом, в каждый момент времени с дескриптором может работать лишь один поток. Если JSP допускает только однопотоковое выполнение, то при реализации класса поддержки не обязательно принимать специальные меры для организации многопотокового доступа к переменным класса. Конечно, при этом надо следить за использованием других данных, чувствительных к одновременному обращению, например атрибутам.

Поскольку контейнер сервлетов может повторно обращаться к классу поддержки дескриптора, необходимо уделять внимание методу release и инициализации переменных в методе doStartTag. Например, класс поддержки, приведенный ниже, работает корректно.

public  class  TagHandler extends TagSupport   ( private  Hashtable hashtable;

public  int doStartTag ()   throws  JspException   { hashtable = new Hashtable ();

     ................             }

public void released { hashtable = null;   }

28        Глава 1. Основы построения пользовательских дескрипторов

Однако при повторном обращении к следующему классу обработки генерируется исключение, связанное с тем, что ссылка имеет значение null.

public  class TagHandler extends TagSupport   { private  Hashtable  hashtable;

public TagHandler()   {

hashtable =  new Hashtable();

public void release()    { hashtable = null;

Пользовательские дескрипторы с атрибутами

Пользовательский дескриптор может содержать любое число атрибутов, как обязательных, так и необязательных. Атрибуты задаются с помощью выражения имл_атри6ута=значение_в_кавычках. Например, дескриптор с одним атрибутом выглядит следующим образом:

<util:iterate times='4'>

Значения атрибутов могут вычисляться в момент запроса. Пример такого атрибута приведен ниже.

<util:iterate  collection='<% aCollection %>'>

Здесь атрибуту collection присваивается значение переменной. Для того чтобы пользовательский дескриптор поддерживал некоторый атрибут, надо выполнить три дополнительных действия.

  1.  Добавитьтребуемый атр ибут к дес кр и пто ру в JSP-файле.

Добавить элемент, соответствующий этому атрибуту, к TLD.

Реализовать метод setAttra классе поддержки дескриптора. Здесь Лиг— имя ат
рибута, соответствующее соглашениям JavaBeans.

Обычно в классе поддержки реализуют также метод qetAttr, таким образом, вложенные дескрипторы получают доступ ксвойствам.

Чтобы продемонстрировать выполнение перечисленных выше действий, создадим простой пользовательский дескриптор с одним атрибутом.

Если на Web-странице содержится форма с полями редактирования, то при очередном отображении формы желательно сохранять в полях редактирования текст, заданный пользователем ранее. На рис. 1.3 показана Web-страница с регистрационной формой, которая отображается повторно, если пользователь некорректно ввел данные. В окис, расположенном слева, показана форма, в которой пользователь не заполнил одно из полей. В правом окне показана та же форма с сообщением об ошибке. В полях редактирования сохраняются данные, введенные ранее, поэтому пользователю не надо повторно заполнять поля.

Пользовательские дескрипторы с атрибутами        29

Первоначально появляется соблазн решить эту задачу следующим образом:

<input type='text'   size=15 name='firstName1

value='<%= request,getParameter("firstName")%>'>

Атрибут value HTML-дескриптора inpflit устанавливается равным значению параметра в составе запроса, который соответствует данному полю редактирования. Теперь, если документ, показанный на рис. 1.3, будет отображен повторно, содержимое полей редактирования сохранится.

Однако описанное решение имеет существенный недостаток. Если параметр запроса, соответствующий полю редактирования, отсутствует (такая ситуация возникает при первом обращении к форме), метод ServietRequest.getParameter вернет значение null, которое и отобразится в поле.

Рж. 1,4. Недостаток непосредственного вызова request.getParameter

Разрешить эту проблему можно, реализовав пользовательский дескриптор, который возвращал бы параметр запроса, а при его отсутствии— пустую строку. Фрагмент JSP-документа, в котором применяется такой дескриптор, показан в листинге 1.3,а. Пользовательский дескриптор requestParameter содержит один обязательный атрибут, определяющий имя параметра запроса.

30        Глава 1. Основы построения пользовательских дескрипторов

Листинг 1.3,a. /register.jsр

 taglib uri='WEB-INF/tlds/htmL.tld'   prefix='html'%

<table> <tr>

<td>  First Name:   </td>

<tdxinput  type=text'   size=15  narae=' firstMame'

value='<html:requestParameter property=" </td> </tr> <tr>

<td> Last Name:   </td>

<td><input   type='text'   size=15   name='lastName'

value='<htmlrrequestParameter property="lastName"/>' </td> </tr> <tr>

<td> E-mail  Address:   </td>

<tdxinput  type='text'   si2e=25 name=' emailAddress' value='<html:requestParameter

property="emailAddress"/>'> </td> </tr> </table>

Атрибуты объявляются в описании библиотеки дескрипторов. Фрагмент TLD-файла для библиотеки, содержащей дескриптор requestParameter, показан в листинге 1.3,6.

Листинг 1.3,6. /weB-iNf/ tlds /html. tld

<taglib>

<tag>

<name>requestParameter</name>

<tagclass>tags.GetRequestParameterTag</tagclass>

<bodycontent>empty</bodycontent>

<attribute>

<name>property</name> <required>t rue</requi red> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>

Атрибут property является обязательным, и его значение может быть определено в момент запроса. Элементы, которые могут содержаться в составе элемента attribute, перечислены в табл. 1.3 (данные элементы соответствуют спецификации JSP 1.1).

Пользовательские дескрипторы с атрибутами        31

Таблица 1.3. Элементы в составе attribute

Элемент Тип     Описание

name ] Имя атрибута

required ? Если значение данного элемента равно true, атрибут дол-

жен присутствовать

rtexprvalue      ? Если значение данного элемента равно true, атрибут мо-

жет определяться с помощью JSP-выраженил

В столбце "Тип" использованы следующие обозначения: 1 — один элемент;

2 — элемент может отсутствовать.

Класс поддержки для дескриптора requestParameter приведен в листинге 1.3, в.

Листинг 1.3,В. /WEB-INF/clasees/tags/GetRequestParameterTag.Java

package tags;

import javax.servlet.ServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class GetRequestParameterTag extends TagSupport {

  private String property;

  public void setProperty(String property) {

     this.property = property;

  }

  public int doStartTag() throws JspException {

     ServletRequest req = pageContext.getRequest();

     String value = req.getParameter(property);

     try {

        pageContext.getOut().print(value == null ? "" : value);

     }

     catch(java.io.IOException ex) {

        throw new JspException(ex.getMessage());

     }

     return SKIP_BODY;

  }

}

Для каждого из атрибутов в классе поддержки должен быть определен set-метод в соответствии с соглашениями JavaBeans. При вызове set-методу передается значение атрибута, которое обычно присваивается переменной, объявленной в классе поддержки. Вызовы set-методов осуществляются перед первым обращением к методу doStartTag.

32        Глава 1. Основы построения пользовательских дескрипторов

В классе GetRe quest Parameter! ag определен set-метод для атрибута property. Значение атрибута сохраняется в переменной, объявленной как private, и используется при выполнении метода doStartTag.

Диаграмма, представленная на рис. 1.5, описывает действия, которые выполняются при обработке дескриптора requestParameter.

Совет

Атрибуты дескриптора и свойства класса поддержки

Для каждого атрибута пользовательского дескриптора в классе поддержки должно существовать соответствующее JavaBeans-свойство. Кроме того, в классе поддержки необходимо определить для каждого свойства set-метод; эти методы вызываются перед обращением к doStartTag. Такая последовательность вызовов гарантирует, что в теле метода cloStar tTag будут доступны значения всех атрибутов.

Доступ к информации о документе

Пользовательским дескрипторам часто бывает нужна информация о содержащем их документе. Это необходимо, например, для обработки параметров запроса или для получения объекта, принадлежащего некоторой области видимости. Информация о документе доступна пользовательским дескрипторам посредством экземпляра класса PageContext, ссылка на который хранится в переменной pageContext, объявленной в классе TagSupport как protected.

Доступ к информации о документе        33

Класс PageContext предоставляет набор вспомогательных методов, которые условно подразделяются на следующие категории,

Доступ к атрибутам в области видимости документа.

Доступ к атрибутам в указанной области видимости.

Перенаправление и включение.

Методы контейнера сервлетов.

Все категории методов, кроме последней, часто используются при построении пользовательских дескрипторов. Например, обратиться к объекту в области видимости документа можно следующим образом:

public class  SomeTag  extends TagSupport   {

public  int doStartTagt)   throws   JspException   ;

User user =   (User) pageContext.getAttribute('user") ;

}

Пользовательский дескриптор может также обращаться к объекту из заданной области видимости.

User user  =   (UserJpageContext.getAttribute("user",

PageContext.SESSION_SCOPE);

При вызове метода PageContext.getAttribute задается имя и область видимости, а по завершении работы метода возвращается ссылка на объект Ob j ect. Область видимости задается константами PAGE_SCOPE, REQUEST_SCOPE, SESSION_SCOPE и APPLICATION_SCOPE, определенными в классе PageContext.

Переменная pageContext также используется для получения информации о запросе, например:

public class  SomeTag  extends  TagSupport   (

public  int doStartTag()   throws  JspException   {

ServletRequest   request   » pageConteact.getEequest(); Locale   locale =  request.getLocale();

В табл. 1.4 перечислены методы класса PageContext, которые применяются при

создании пользовательских дескрипторов.

Таблица 1.4. Методы класса PageContext

Метод Описание

Object   findAttribute Выполняет поиск атрибута в области видимости доку-

(String) мента, запроса, сеанса и приложения

Object  getAttribute Возвращает объект из области видимости документа;

(String) если объект отсутствует, возвращается значение null

34        Глава 1. Основы построения пользовательских дескрипторов

Окончание табл. 1.4

Метод

void setAttribute (String,  Object)

void  removeAttribute (String)

JspWriter  getOut()

ServletRequest getRequest()

ServletResponse getResponse()

ServletContext getServletContext()

HttpSession getSession()

void  forward (String path)

void include (String path)

Описание

Сохраняет объект в области видимости документа Удаляет объект из области видимости документа

Возвращает объект JspWriter, который используется дескриптором для вывода данных

Возвращает объект ServletRequest, инкапсулирующий данные о запросе

Возвращает объект ServletResponse, инкапсулирующий данные об ответе

Возвращает объект приложения

Возвращает объект сеанса

Перенаправляет запрос, используя относительный путь

Включает HTML- или JSP-файл; для указания файла используется относительный путь

Методы setAttribute getAttribute и removeAttribute переопределены так, что при вызове им передается дополнительное целочисленное значение, которое определяет область видимости. Благодаря этому появляется возможность хранить атрибуты в произволыгай области. Если при вызове метода область не указана, предполагается область видимости документа.

Класс PageContext также предоставляет доступ к предопределенным переменным документа, содержащего дескриптор, например к переменным out, session и т.д. Метод PageContext .getOut используется очень часто, особенно если дескриптор предназначен для фильтрации или редактирования содержащихся в нем данных.

Из пользовательского дескриптора можно также выполнять перенаправление или включение Web-компонентов, например сервлетов или JSP. При вызове методам forward и include класса PageContext передается строка, представляющая относительный путь к ресурсу.

Согласно спецификации JSP 1,1, нельзя включать ресурсы из обработчика тела дескриптора. Это ограничение связано с буферизацией, используемой при работе серв-лета, в который преобразуется JSP. Вспецификации^Р 1.2 этот недостаток устранен.

Обработка ошибок        35

Обработка ошибок

Классы поддержки дескрипторов должны реагировать на исключительные ситуации, например генерировать исключение, если задано недопустимое значение атрибута.

В процессе работы класс поддержки может генерировать исключение JspException. При обработке этого исключения отображается специальная страница. Например, в следующем фрагменте JSP-кода заданы ссылка ма описание библиотеки дескрипторов и страница ошибки.

<%@ taglib uri='util.tld'   prefix='util'%> <%@ page errorPage='error.jsp'   %>

Класс поддержки дескриптора для JSP, содержащего приведенный выше код, может выглядеть приблизительно так:

import  javax.servlet.jsp.JspException; import  javax.servlet.jsp.tagext.TagSupport;

public class  SomeTagHandler extends  TagSupport   ( private boolean  someCondition;

public  int doStartTag()   throws  JspException   {

if(someCondition  =  false)

throw new JspEicception("informative message");

}

)

Документ error. j sp, указанный посредством директивы page, вызывается в том случае, если при выполнении метода SomeTagHandler.doStartTag генерируется исключение.

Классы для реализации пользовательских дескрипторов

В пакете javax. servlet. jsp. tagext содержится набор классов для поддержки информации о библиотеках дескрипторов, а также классы и интерфейсы для реализации пользовательских дескрипторов. На диаграмме, представленной на рис. 1.6, показаны классы и интерфейсы, часто применяемые разработчиками.

Класс поддержки дескриптора должен рсализовывать интерфейс Tag. Разработчики почти никогда не определяют вручную методы, объявленные в интерфейсе Tag, а создают класс поддержки как подкласс класса TagSupport либо класса BodyTagSupport.

На первый взгляд может показаться, что класс BodyTagSupport предназначен для обработки тела дескриптора, а класс TagSupport ориентирован на дескрипторы, тело которых отсутствует. Однако на самом деле это не так. Оба типа дескрипторов могут содержать данные, но при использовании подклассов класса TagSupport тело дескриптора либо игнорируется, либо передается без изменений. Подклассы BodyTagSupport используются для обработки тела дескриптора.

36        Глава 1. Основы построения пользовательских дескрипторов

Рис. 1.6. Диаграмма классов, принадлежащих пакету tagext

Доступ к информации о документе, содержащем дескриптор, осуществляется посредством контекста документа, который связывается с каждым дескриптором. Как видно из рис. 1.6, класс PageContext содержит методы, предоставляющие различные сведения о документе.

Интерфейс Tag

Интерфейс Tag описывает основные возможности дескрипторов; ниже перечислены методы, объявленные в этом интерфейсе.

void  setPaqeContext(PageContext) void  setParent(Tag}

int  doStartTag()   throws  JspException int  doEndTag()   throws  JspException void  release ()

Tag qetParent ()

Классы для реализации пользовательских дескрипторов        37

Методы из первой группы приведены в той последовательности, в которой они вызываются при обработке дескриптора. При появлении открывающего дескриптора сначала производится обращение к методу setPageContext, а затем к методу setParent. После этого вызывается метод doStartTag. При встрече закрывающего дескриптора вызывается метод doEndTag. В случае пустого дескриптора doEndTag вызывается сразу же после doStartTag. После завершения метода doEndTag производится обращение к методу release, который освобождает ресурсы, использованные классом поддержки.

Как doStartTag, так и doEndTag возвращают целочисленные значения, определяющие поведение контейнера сервлетов. Возвращаемые значения и действия, которые они вызывают, описаны в табл. 1.5.

Таблица 1.5. Значения, Возвращаемые методами doStartTag И doEndTag

Метод Возвращаемые значения

doStartTag ()        SKI P_BODY; тело дескриптора не обрабатывается

EVAL_BODY_INCLUDE; тело дескриптора передается без изменений

doEndTag () SKIP_PAGE: часть документа, следующая за закрывающим деск-

риптором, не обрабатывается

EVAL_PAGE: часть документа, следующая за закрывающим дескриптором, обрабатывается

Для каждого дескриптора определен родительский дескриптор (null для дескриптора верхнего уровня) и старший из вложенных дескрипторов. Например, в следующем JSP-фрагменте дескриптор inbetween является родительским для дескриптора innermost, а дескриптор outermost выступает в качестве родительского по отношению к дескриптору inbetween.

<example:outermost>

<example:inbetween>

<example:innermost>

</example:innermost> <example:inbetween> </example:outermost>

Все дескрипторы, в состав которых входят другие дескрипторы, считаются предками включенных дескрипторов. В предыдущем примере дескрипторы inbetween и outermost являются предками дескриптора innermost.

Интерфейс Tag предоставляет доступ к родительскому дескриптору, но set-методы, определяющие родительский дескриптор и контекст документа, предназначены для контейнера сервлетов, и при создании пользовательских дескрипторов применять их не следует.

На практике разработчики редко реализуют интерфейс Tag непосредственно. Вместо этого они создают класс поддержки дескриптора как подкласс класса TagSupport либо класса BodyTagSupport.

38        Глава 1. Основы построения пользовательских дескрипторов

Класс TagSupport: предки, значения и идентификаторы

Если тело дескриптора не обрабатывается и если дескриптор не влияет на поток выполнения, его класс поддержки обычно создается как подкласс класса TagSupport. Класс TagSupport реализует интерфейс Tag и предоставляет следующие возможности:

определение предков дескриптора;

доступ к идентификатору дескриптора;

сохранение и извлечение именованных значений.

По умолчанию подклассы TagSupport игнорируют тело дескриптора и обрабатывают JSP-код, следующий за закрывающим дескриптором. Для того чтобы контейнер сервлетов демонстрировал именно такое поведение, надо, чтобы метод doStartTag возвращал значение SKIP_BODY, а метод doEndTag — значение EVAL_PAGE.

Методы класса TagSupport перечислены ниже.

//   Класс TagSupport реализует интерфейс Tag //   и предоставляет следующие методы: protected String  id; protected  PageContext pageContext;

static  Tag   findAncestorWithClass(Tag,   Class)

Object   qetValue[String key) void  setValue(String  key,   Object value) void  removeValue(String   key) Enumeration qetValues()

String  qetIdQ void seIldQ

Подклассам класса TagSupport доступны две переменные: одна из них содержит идентификатор дескриптора, а вторая — контекст документа. Эти переменные объявлены как protected.

Метод findAncestorWithClass определяет расположение предка дескриптора. Этот метод, объявленный как static, получает дескриптор для начала поиска и класс предка. Пример использования метода findAncestorWithClass приведен в главе 2.

Третья группа методов класса TagSupport, перечисленных выше, обеспечивает доступ к именованным значениям, которые хранятся в виде пар ключ-значение. В качестве ключа используется строка символов, а значением может быть любой объект.

Совет

Большинство пользовательских дескрипторов реализуется с помощью классов TagSupport и BodyTagSupport

Классы TagSupport и BodyTagSupport предоставляют основные средства для создания пользовательских дескрипторов. Вместо непосредственной реализации интерфейса Tag разработчики создают классы поддержки как подклассы TagSupport и BodyTagSupport.

Тело дескриптора        39

Поскольку Java не допускает множественного наследования, модифицировать существующий класс так, чтобы он наследовал возможности TagSupport или BodyTagSupport, невозможно. Единственным выходом в таком случае является непосредственная реализация интерфейсов Tag и BodyTag, однако подобные ситуации возникают достаточно редко.

Тело дескриптора

Между открывающим и закрывающим дескрипторами могут присутствовать раалич-ные данные, однако только дескрипторы, реализующие интерфейс BodyTag, способны обрабатывать их. Если в составе элемента bodycontent описания библиотеки дескрипторов указано значение empty, дескриптор не может содержать данные. Дескриптор, реализующий интерфейс BodyTag, считается обработчиком тела дескриптора.

Если класс поддержки не реализует интерфейс BodyTag, тело дескриптора должно либо игнорироваться, либо передаваться в неизменном виде. JSP-документ, показанный и правом окне на рис. 1.7. содержит простой пользовательский дескриптор, который отображает содержащиеся в нем данные, если процедура регистрации окончилась успешно и если пользовательская роль определена как 'user'. В противном случае тело дескриптора игнорируется.В левом окне на рис. 1.7 представлена Web-страница, предназначенная для регистрации. Если процедура регистрации оканчивается успешно, происходит перенаправление к другому документу (контейнер сервлетов вызывает welcome, jsp; подробнее о регистрации и средствах защиты будет сказано в главе 9). Документ, код которого показан в листинге 1.4,а, использует дескриптор authenticate. Если пользовательская роль имеет значение 'user', дескриптор передает содержащиеся в нем данные без изменений.

40        Глава 1, Основы построения пользовательских дескрипторов

Листинг 1.4,8. /welcome, jsp

<html><head><title>Welcome</title></head> <body>

<%@  taglib uri='/WEB-INF/tlds/authenticate.tld' prefix='security'%>

<h3>Welcome <%=  request.getUserPrincipal()   %></h3>

<security:authenticate  role='user'>

You are a  user </security:authenticate>

</body></html>

Класс поддержки дескриптора authenticate показан в листинге 1.4,6.

ЛИСТИНГ 1.4,6. /WEB -INF/ classes/ tags/ Authenticate Tag . Java

package tags;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class AuthenticateTag extends TagSupport {

  private String role = null;

  public void setRole(String role) {

     this.role = role;

  }

  public int doStartTag() throws JspException {

     HttpServletRequest request = (HttpServletRequest)

                                   pageContext.getRequest();

     if(request.isUserInRole(role)) {

        return EVAL_BODY_INCLUDE;

     }

     return SKIP_BODY;

  }

  public int doEndTag() throws JspException {

     return EVAL_PAGE;

  }

}

Класс AuthenticateTag является подклассом класса TagSupport и поддерживает единственное свойство, соответствующее атрибуту role дескриптора authenticate.

Если пользовательская роль имеет значение 'user', метод doStartTag возвращает EVAL_BODY_INCLUDE и тело дескриптора включается в отображаемый документ. В противном случае метод возвращает SKIP_BODY, и тело дескриптора игнорируется.

Тело дескриптора        41

Совет

Включение тела дескриптора

Если пользовательский дескриптор не реализует интерфейс BodyTag, он может лишь передать содержащиеся в нем данные без изменений. Для этого метод doS tart Tag должен возвращать значение EVAL_BODY_INCLUDE. Если тело дескриптора предназначено для обработки, необходимо, чтобы класс поддержки реализо-вывал интерфейс BodyTag. Подробнее этот вопрос будет рассмотрен в главе 2.

Резюме

По мере развития JSP становится очевидно, что наличие пользовательских дескрипторов является одним из главных преимуществ данной технологии. Пользовательские дескрипторы упрощают задачу разработчиков и способствуют широкому распространению JSP. Во время написания данной книги велась работа над рядом проектов по созданию библиотек пользовательских дескрипторов.

В состав следующей версии спецификации JSP войдет стандартная библиотека де* скрипторов. Реализация итераций либо функций для взаимодействия с базами данных будет в значительной мере способствовать росту популярностиJSP,

В большинстве случаев разработчики создают простые пользовательские дескрипторы, в которых поддерживается ограниченное число атрибутов, а тело дескриптора не обрабатывается. Рассмотрению таких дескрипторов была посвящена эта глава. В следующей главе мы обсудим более сложные вопросы, связанные с созданием пользовательских дескрипторов, в частности, уделим внимание обработке содержащихся в них данных.

ДОПОЛНИТЕЛЬНЫЕ

ВОПРОСЫ

СОЗДАНИЯ

ПОЛЬЗОВАТЕЛЬСКИХ

ДЕСКРИПТОРОВ

В этой главе...

Обработчики тела дескриптора.

Итерации.

Переменные сценария.

Хранение компонентов bean в области видимости документа.

Информация о переменных сценария.

Связывание класса поддержки с переменными сценария.

Идентификаторы пользовательских дескрипторов.

• Тело дескриптора.

Особенности обработки тела дескриптора.

Генерация JavaScript-кода.

• Вложенные дескрипторы.

Обнаружение предков дескрипторов.

Разделение данных.

о мере усложнения пользовательских дескрипторов разработчики реализуют обработку содержащихся в них данных. Например, приведенный ниже фрагмент кода помещает тело дескриптора в состав HTML-элемента SELECT.

<html:links name='api'>

<option value='Servlets' >servlets</option>

</html:links>

HTML-код, сгенерированный в результате выполнения дескриптора, имеет следующий вид:

<select  name='api'   onChange^' this.form.submit()'> <option value=' Servlets'>servlets</option>

</select>

Кроме обработки тела дескриптора, многие пользовательские дескрипторы обеспечивают также доступ JSP-документов к компонентам bean и неременным сценария. Для создания переменных сценария приходится затрачивать дополнительные усилия, однако они окупаются тем, что JSP-документы становятся значительно проще. Ниже представлен дескриптор iterate, который включает текущий пункт в набор, доступный как bean.

<smp:iterate  collection>'<%= vector   %>'>

<jsp:useBean  id='item'   scope='page'   class='Java,lang.String'/>

Item:   <%=  item  %><br> </smp:iterate>

Сравните его с переменной сценария:

<smp:iterate   collection><%=  vector   %>'>

Item:   <%=  item %><br> </smp:iterate>

Обе реализации дескриптора iterate будут обсуждаться далее.

44        Глава 2. Дополнительные вопросы...

Эта глава начинается с рассмотрения пользовательских дескрипторов, предназначенных для реализации итераций и обработки содержимого. Далее мы обсудим дескрипторы, которые обеспечивают доступ к компонентам bean и переменным сценария. В завершение данной главы мы рассмотрим вложенные дескрипторы, в том числе вопросы поиска предков и разделения данных.

Обработчики тела дескриптора

Обработчиками тела дескриптора называются такие дескрипторы, классы поддержки которых реализуют интерфейс BodyTag. По сравнению с простыми дескрипторами, рассмотренными ранее, они имеют две дополнительные возможности: они способны поддерживать итерации и обрабатывать содержащиеся в них данные.

Интерфейс BodyTag

Интерфейс BodyTag является расширением интерфейса Tag. По сравнению с интерфейсом Tag, в BodyTag объявлены следующие дополнительные методы:

void doInitBody [) int doAfterBody [) void setBodyContent(BodyContentl

Эти методы позволяют реализовывать итерации и обрабатывать тело дескриптора. Контейнер сервлетов вызывает эти методы следующим образом:

/7  Порядок вызова методов  BodyTag контейнером сервлетов if (tag.doStartTagO   ==  EVAL__BODY_TAG)    ( tag.setBodyContent(bodyContent);

tag.doInitBodyO ; do   {

//  Обработка  тела дескриптора }

while(tag.doAfterBody()   == ЈVAL_BODY_TAG); }

Обращение к методу doInitBody производится лишь один раз, а метод doAfterBody может вызываться многократно. Таким образом, пользовательские дескрипторы позволяют организовывать итерации. Ниже показан дескриптор, который реализует пять повторов.

<util:loop from='l' to='5'> <util:loop>

Для инициализации цикла из метода doInitBody производится обращение к атрибутам from и to дескриптора loop. До завершения цикла метод doAfterBody возвращает значение EVAL_BODY_TAG, а после окончания цикла — значение SKI P_BODY.

Методы doInitBody и doAfterBody имеют доступ к телу дескриптора, но метод doInitBody вызывается до первой обработки содержимого.

Обработчики тела дескриптора        45

В табл. 2.1 описаны значения, возвращаемые методами интерфейса BodyTag, и действия, которые эти значения оказывают.

Таблица 2.1. Значения, возвращаемые методами интерфейса BodyTag

Метод Возвращаемые значения

doStartTag ()      EVAL_BODY_TAG: обработка тела дескриптора и сохранение результата в объекте BodyContent

SKI P_BODY: обработка тела дескриптора не производится

doAf terTag ()      EVAL_BODY_TAG: повторная обработка тела дескриптора

SKIF_BODY: повторная обработка тела дескриптора не производится

doEndTag (} EVAL_PAGE: обработка части документа, следующей за закры-

вающим дескриптором

SKIF_PAGE: часть документа после закрывающего дескриптора не обрабатывается

Класс BodyTagSupport

Практически все обработчики тела дескриптора создаются как подклассы класса BodyTagSupport, реализующего интерфейс BodyTag. Класс BodyTagSupport предоставляет как методы класса TagSupport, так и методы, объявленные в интерфейсе BodyTag,

Кроме того, в классе BodyTagSupport определены дополнительные методы, перечисленные ниже.

// Класс BodyTagSupport является подклассом

// TagSupport  и реализует интерфейс BodyTag.

//  В нем также  определены следующие  дополнительные методы:

BodyContent  getBodyContent()

JspWriter qetPreviousOut()

В дополнение к методам, унаследованным от BodyTag и TagSupport, в классе BodyTagSupport добавлены два приведенных выше метода. Метод getBodyContent возвращает тело дескриптора, а метод getPreviousOut — выходной поток, связанный с родительским дескриптором. В случае дескриптора верхнего уровня метод getPreviousOut возвращает значение предопределенной переменной out. Под-робко эти два метода будут рассмотрены далее в этой главе.

По умолчанию подклассы класса BodyTagSupport единожды обрабатывают тело дескриптора. Значения, возвращаемые по умолчанию методами данного класса, приведены в табл. 2.2.

46        Глава 2. Дополнительные вопросы...

Таблица 2.2. Значения, возвращаемые по умолчанию методами КЛаССЗ BodyTagSupport

Метод

Значение, возвращаемое поумолчанию

doStartTag ()       EVAL_BODY_TAG: обработка тела дескриптора

doAfterTag ()       SKIP_BODY: повторная обработка тела дескриптора не производится

doEndTag () EVAL_PAGE: обработка части документа, следующей за закры-

вающим дескриптором

Итерации

Обработчики тела дескриптора содержат встроенный цикл do-while, что позволяет им поддерживать итерации. Так, например, на рис. 2.1 показан JSP-документ, включающий пользовательский дескриптор, который перебирает в цикле элементы вектора.

 Листинг 2.1,a. /test.jsp

<html><head><title>An Iterator</title></head>

<%@ taglib uri='/WEB-INF/tlds/iterator.tld' prefix='it' %>

<body>

<% java.util.Vector vector = new java.util.Vector();

vector.addElement("one");   vector.addElement("two");

vector.addElement("three"); vector.addElement("four");

%>

Iterating over <%= vector %> ...<p>

<it:iterate collection='<%= vector %>'>

  <jsp:useBean id='item' scope='page' class='java.lang.String'/>

  Item: <%= item %><br>

</it:iterate>

</p>

</body>

</html>

Итерации        47

В представленном выше листинге вектор объектов String задается как значение атрибута collection пользовательского дескриптора iterate. Дескриптор iterate перебирает в цикле строковые значения и сохраняет текущую строку в области видимости документа. В теле дескриптора для получения строки используется действие < j sp: useBean>, затем строка отображается с помощью JSP-вы раже и ия.

В листинге 2,1,6 представлен класс поддержки дескриптора iterate.

Листинг 2.1,6. /WEB-INF/classes/ tags/IteratorTag . Java

package tags;

import java.util.Collection;

import java.util.Iterator;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class IteratorTag extends BodyTagSupport {

private Collection collection;

private Iterator iterator;

public void setCollection(Collection collection) {

 this.collection = collection;

}

public int doStartTag() throws JspException {

 return collection.size() > 0 ? EVAL_BODY_TAG : SKIP_BODY;

}

public void doInitBody() throws JspException {

 iterator = collection.iterator();

 pageContext.setAttribute("item", iterator.next());

}

public int doAfterBody() throws JspException {

 if(iterator.hasNext()) {

  pageContext.setAttribute("item", iterator.next());

  return EVAL_BODY_TAG;

 }

 else {

    try {

   getBodyContent().writeOut(getPreviousOut());

  }

  catch(java.io.IOException e) {

   throw new JspException(e.getMessage());

  }

  return SKIP_BODY;

 }

}

public void release() {

 collection = null;

   iterator = null;

}

}

В листинге 2.1,6 методы класса IteratorTag определены в том порядке, в котором они вызываются контейнером сервлетов. Сначала контейнер вызывает метод setCollection и передает ему значение атрибута collection дескриптора.

Затем контейнер вызывает метод doStartTag, который возвращает значение EVAL_BODY_TAG, Это значение возяращается, только если в составе вектора присут-

48       Глава 2. Дополнительные вопросы...

ствуют элементы, в противном случае возвращается значение SKIP_BODY и контейнер сервлетов не вызывает методы doInitBody и doAf terBody.

Если вектор содержит элементы, контейнер сервлетов вызывает метод doInitBody, в котором определяется итератор набора (вектора), извлекается первый элемент вектора и сохраняется в области видимости документа под именем "item".

После инициализации тела дескриптора и обработки содержимого вызывается метод doAf terBody. Если не все элементы вектора исчерпаны, метод doAf terBody извлекает очередной элемент и сохраняет его в области видимости документа. Затем метод doAfterBody возвращает значение EVAL_BODY_TAG, вызывая тем самым повторную обработку содержимого дескриптора. Если элементов в составе вектора больше не осталось, возвращается значение SKIP_BQDY и цикл завершается.

Дескриптор iterate не реализует метод release, поскольку в составе iterate нет данных, которые должны инициализироваться перед повторным обращением к дескриптору.

На первый взгляд может показаться странным, что дескриптор iterate записывает данные в поток, который принято называть выходным потоком предыдущего дескриптора (previous out). Кроме того, может возникнуть вопрос, почему содержимое записывается в поток в теле метода doAf terBody, а не в методе doEndTag. Ответы на эти вопросы вы найдете далее в данной главе.

На рис. 2.2 показана диаграмма взаимодействия класса IteratorTag, код которого представлен в листинге 2.1 ,б. Класс поддержки сохраняет каждый элемент вектора в области видимости документа.

pageContext.setAttribut©("it«m",   iterator.next(});

Сохраненные элементы затем извлекаются посредством действия < j sp: useBean>. <jsp:useBean id='it»m'   scope»'page'   class»'Java.lang.String'>

Заметьте, что ключевое значение, задаваемое при вызове метода setAttribute ("item"), должно соответствовать значению атрибута id действия <jsp:useBean>. Области видимости также должны совпадать.

Избежать этой зависимости можно, обеспечив доступ к элементам посредством переменных сценария. В этом случае исчезает необходимость в действии <jsp:useBean>. Реализация класса IteratorTag с использованием переменных сценария обсуждается в следующем разделе.

Совет

Обработчики содержимого не обеспечивают автоматическое включение тела дескриптора

Если класс поддержки дескриптора не реализует интерфейс  BodyTag, метод I doStartTag этого класса может возвращать значение EVAL_BODY_INCLUDE; в ре- | эультате тело дескриптора передается для обработки без изменений. Однако если класс поддержки реализует интерфейс BodyTag, это означает, что не контейнер, а именно класс поддержки отвечает за обработку содержимого. В этоьЈ случае doStartTag не может возвращать EVAL_BODY_INCLUDE. Тело дескриптора должно непосредственно обрабатываться, как это показано в листинге 2,1,6.

Переменные сценария        49

Переменные сценария

В процессе выполнения пользовательских дескрипторов нередко создаются объекты, доступные другим элементам JSP-документа. Например, дескриптор iterate, который рассматривался ранее, обеспечивает доступ к элементам вектора.

В некоторых случаях бывает удобнее, если объекты, создаваемые дескрипторами, доступны не как компоненты bean, а как переменные сценария. Подобный подход реализован в JSP-до куме нте, представленном в листинге 2.2,а.

Листинг  2.2,a/ test.jsp

<html><head><title>Scripting Variable Example</title></head>

<%@ taglib uri='iterator' prefix='it' %>

<% java.util.Vector vector = new java.util.Vector();

  vector.addElement("one");   vector.addElement("two");

  vector.addElement("three"); vector.addElement("four");

%>

Iterating over <%=vector %> ...<p>

<it:iterate id='item' collection='<%= vector %>'>

  item <%= item %><br>

</it:iterate>

</p></body>

</html>

50        Глава 2. Дополнительные вопросы...

Web-страница, отображаемая при обращении к данному документу, не отличается от представленной на рис. 2.1.

Сравним коды в листингах 2.2.а и 2.1,а, можно заметить, что использование переменных сценария исключает необходимость в действии <jsp:useBean>. При этом код упрощается, и область видимости компонента не указывается.

Реализация пользовательского дескриптора, который создает переменные сценария, включает следующие действия.

  1.  Создание класса поддержки, который хранит один или несколько объектов в
    области видимости документа.

Создание подкласса класса TagExtralnfo, в котором определяются свойства,
связанные с переменными сценария.

Модификация ТЫ), в частности объявление в нем подкласса TagExtralnfo,
созданного на предыдущем шаге.

Перечисленные действия подробно рассматриваются ниже на примере дескриптора iterate, приведенного в листинге 2.2,а.

Хранение компонентов bean в области видимости документа

Класс поддержки для новою варианта дескриптора iterate ничем не отличается от класса IteratorTag, рассмотренного ранее (/WEB-INF/classes/tags/Iterator-Tag. Java). Дело в том, что первый этап создания дескриптора, использующего переменные сценария, совпадает с соответствующим этапом, необходимым для создания компонента bean; при этом компонент сохраняется в области видимости документа. Поэтому в данном примере класс IteratorTag применяется без изменений.

Информация о переменных сценария

JSP-контейнер должен иметь данные об имени переменной сценария, ее типе, области видимости и времени жизни, а также знать, существует ли переменная или она должна быть создана. Такая информация инкапсулируется в объекте, называемом объектом дополнительной информации о дескрипторе (tag extra info). Этот объект является экземпляром подкласса класса TagExtralnfo. Класс TagExtralnfo принадлежит пакету javax.servlet. jsp,tagext.

Переменные сценария        51

В листинге 2.2,6 представлен класс IteratorTaglnfo для пользовательского дескриптора, содержащегося в документе, код которого приведен В листинге 2.2,а.

ЛИСТИНГ 2.2,6. /WEB-INF/claaяеа/tags/IteratorTagInfo.Java

package tags;

import javax.servlet.jsp.*;

import javax.servlet.jsp.tagext.*;

public class IteratorTagInfo extends TagExtraInfo {

  public VariableInfo[] getVariableInfo(TagData data) {

     return new VariableInfo[] {

        new VariableInfo(data.getId(), // scripting var's name

              "java.lang.Object", // variable's type

              true, // whether variable is created

              VariableInfo.NESTED) // scope

     };

  }

}

Метод getvariablelnfo возвращает массив объектов Variablelnfo, каждый из которых представляет переменную сценария. В листинге 2.2,6 метод getvariablelnfo возвращает массив, содержащий единственный объект Variablelnfo, представляющий переменную сценария item, используемую в листинге 2.2,а.

Область видимости переменной сценария задается следующими тремя константами:

VariableInfo.AT_BEGIN

Variablelnfo.NESTED

VariableInfo.AT_END

Константы AT_BEGIN и AT_END задают область соответственно от открывающего и закрывающего дескрипторов до конца документа. Константа NESTED задает область между открывающим и закрывающим дескрипторами.

Связывание класса поддержки с переменными сценария

Последним этапом создания переменных сценария является связывание класса поддержки с объектом дополнительной информации о дескрипторе. Это связывание выполняется в описании библиотеки дескрипторов. TLD-файл для дескриптора iterate показан в листинге 2.2,в.

Листинг2.2,в. /WEB-INF/tlds/iterator.tld

<?xml version="1.0" encoding="ISO-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>

<jspversion>1.1</jspversion>

<shortname>smp</shortname>

<info>Sun Microsystems Press Tag Library</info>

<tag>

 <name>iterate</name>

 <tagclass>tags.IteratorTag</tagclass>

 <teiclass>tags.IteratorTagInfo</teiclass>

 <bodycontent>JSP</bodycontent>

 <attribute>

  <name>id</name>

  <required>true</required>

  <rtexprvalue>true</rtexprvalue>

 </attribute>

 <attribute>

  <name>collection</name>

  <required>false</required>

  <rtexprvalue>true</rtexprvalue>

 </attribute>

 <info>Iterates over a collection</info>

</tag>

</taglib>

52        Глава 2. Дополнительные вопросы...

Описание библиотеки дескрипторов, приведенное в листинге 2.2,в, определяет с помощью элемента teiclass класс дополнительной информации о дескрипторе. Элемент teiclass содержится в составе элемента taglib.

Совет

Переменные сценария предпочтительнее компонентов bean

Пользовательские дескрипторы обеспечивают доступ к компонентам bean, сохраняя компоненты в контексте документа. Приложив дополнительные усилия, можно создавать пользовательские дескрипторы так, что они будут создавать переменные сценария, доступные из^Р-документа.

Поскольку из JSP можно непосредственно обращаться к переменным сценария, использовать эти переменные удобнее, чем включать в состав документа действия <jsp:useSean>.

Идентификаторы пользовательских дескрипторов

Переменные сценария уменьшают зависимость пользовательских дескрипторов от других элементов jSP-документа. Однако, как видно из примеров, JSP-документ должен знать имя переменной. Рассмотрим следующий фрагмент кода:

<it:iterate collection='<%= vector %>'>

item <%= item %><br> </it:iterate>

В данном случае переменная сценария имеет имя item, которое указывается в объекте дополнительной информации о дескрипторе. Соответствующий код приведен ниже.

public  Variablelnfo[]'getVariablelnfo(TagData data)   { return new Variablelnfo[]    {

new Variablelnfo("item",   "Java.lang.Object",

true,     Variablelnfo.NESTED) };

Переменные сценария        53

Поскольку обращение к переменной сценария производится по имени, подобная зависимость существует всегда. Однако вы имеете возможность устранить ее, позволив автору JSP задавать имена переменных сценария посредством атрибута id. Например, можно потребовать, чтобы рассматриваемый дескриптор iterate включал обязательный атрибут id, применяемый для именования переменной сценария. В этом случае дескриптор будет выглядеть следующим образом:

<it:iterate id='anltem'   collection^<%= vector  %>'>

item <%= anltem  %><br> </it:iterate>

В предыдущем фрагменте кода используется имя переменной сценария anltem, однако вы можете выбрать любое другое имя. Данная возможность обеспечивается минимальными усилиями. Во-первых, в описании библиотеки дескрипторов надо предусмотреть атрибут id.

<taglib> <tag>

<name>iterate</name>

<tagclass>tags.IteratorTag</tagclass>

<teiclass>tags.IteratorTaglnfo</teiclass>

<bodycontent>JSP</bodycontent>

<attribute>

<name>id</name> <required>tme</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute>

<name>collection</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute>

<info>Iterates  over a  collection</info> </tag> </taglib>

Во-вторых, надо изменить класс дополнительной информации о дескрипторе так, чтобы в нем могло использоваться значение атрибута id. Класс дополнительной информации для дескриптора iterate выглядит следующим образом:

public  class  IteratorTaglnfo extends TagExtralnfo   [

public Variablelnfo[]   getVariablelnfo(TagData data)   { return new Variablelnfo[]    {

new Variablelnfo(data.getld(),   //  имя переменной  сценария "Java,lang.Object",   //  тип  переменной true,   // должна ли переменная быть  создана Variablelnfo.NESTED)   //  область  видимости

54

Глава 2. Дополнительные вопросы...

Выполнив эти несложные действия, вы обеспечиваете возможность именовать

переменные сценария посредством атрибута id, Классы поддержки, выполненные как подклассы TagSupport или BodyTagSupport, не нужно модифицировать, поскольку класс TagSupport поддерживает атрибут id и содержит соответствующий метод set Id.

Тело дескриптора

В процессе работы часто возникает необходимость в специальной обработке тела дескриптора, например, его содержимое может интерпретироваться как SQL-запрос. Класс поддержки, реализующий интерфейс BodyTag, имеет доступ к содержимому дескриптора. Пример документа, включающего такой дескриптор, приведен на рис, 2.3,

JSP-документ, показанный на рис. 2.3, применяет нолыю вате лье кий дескриптор capitalize для преобразования текста, содержащегося между открывающим и закрывающим дескрипторами, в верхний регистр. Код документа приведен влистипге 2.3,а.

Листинг 2.3,а. /test.

<html><head><title>Capitalize Tag Example</title></head>

<%@ taglib uri='example' prefix='example' %>

<example:capitalize>

<%= "capitalize this string" %>

</example:capitalize>

</body>

</html>

Класс поддержки дескриптора capitalize представлен в листинге 2.3,6.

ЛИСТИНГ2.3,6. /WEB-INF/classes/tags/CapitalizeTag. Java

package tags;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class CapitalizeTag extends BodyTagSupport {

  public int doAfterBody() throws JspException {

     try {

        String content = bodyContent.getString();

        String   upper = content.toUpperCase();

        bodyContent.clearBody();

        bodyContent.print(upper);

        bodyContent.writeOut(getPreviousOut());

     }

     catch(java.io.IOException e) {

        throw new JspException(e.getMessage());

     }

     return SKIP_BODY;

  }

}

Тело дескриптора        55

В данном классе поддержки дескриптора переопределяется метод doAfterBody, а переменная bodyContent, определенная в классе BodyTagSupport, используется для получения содержимого дескриптора в виде строки символов. Эта строка преобразуется в верхний регистр, и содержимое дескриптора очищается. Затем метод doAfterBody оформляет преобразованную строку как содержимое дескриптора и записывает в поток ответа.

Подобно дескриптору iterate, показанному в листинге 2.1,6, дескриптор capitalize записывает содержимое дескриптора в выходной поток.

Перед тем как содержимое дескриптора становится доступным классу поддержки, оно обрабатывается контейнером сервлстов. Например, дескриптор capitalize, представленный ниже, приведет к появлению изображения, показанного на рис. 2.3, поскольку выражение в теле дескриптора обрабатывается и результат обработки (строка "capitalize  this  string") становится доступной классу поддержки.

<example:capitalize>

<%=  "capitalize this  string"  %> </example:capitalize>

Подобное поведение пользовательского дескриптора обусловлено тем, что в описании библиотеки дескрипторов по умолчанию используется значение JSP элемента bodycontent.

<tag>

<name>capitalize</name>

<tagclass>tags.CapitalizeTag</tagclass>

<bodycontent>JSP</bodycontent> </tag>

Иногда бывает необходимо, чтобы содержимое дескриптора не обрабатывалось, например, если оно должно интерпретироваться как SQL-запрос. В таких случаях надо указать значение" дескриптора bodycontent, равное tagdependent.

<tag>

<name>capitalize</name> <tagclass>tags.Capital!zeTag</tagclass>

<bodycontent>tagdependent</bodycontent> </tag>

56

Глава 2. Дополнительные вопросы...

Особенности обработки тела дескриптора

Если вы хотите реализовывать пользовательские дескрипторы, обрабатывающие свое содержимое (такие как рассмотренные выше дескрипторы capitalize и iterate), вам надо составить ясное представление о том, что такое тело дескриптора и как оно обрабатывается контейнером сервлетов. Обсуждению этих вопросов посвящен данный раздел.

Тело дескриптора представляется классом BodyContent, который создан на базе буферизованного выходного потока. Благодаря наличию буфера вы можете выполнять различные действия с данными, содержащимися в составе дескриптора.

Диаграмма

классов для BodyContent показана на рис. 2.4.

Класс BodyContent является подклассом JspWriter, ссылка на который содержится в предопределенной переменной out. Если в JSP-документе вы используете для вывода информации переменную out, данные непосредственно попадают в выходной поток, связанный с ответом на запрос. В пользовательском дескрипторе выходные данные передаются экземпляру класса BodyContent.

Контейнер сервлетов поддерживает стек объектов BodyContent так, что вложенные дескрипторы не заменяют содержимое родительского дескриптора. Каждый объект BodyContent содержит ссылку на буферизованный выходной поток, соответствующий объекту, расположенному под ним в стеке. Этот поток принято называть выходным потоком предыдущею дескриптора (previous out), или включающим выходным потоком (enclosing writer). Для обращения к нему используются методы BodyContent. getEnclosingWriter или BodyTagSupport. getPreviousOut.

Тело дескриптора        57

Рассмотрим, как контейнер сервлетов обрабатывает объекты BodyContent. He зная этого, трудно понять, какой из потоков следует использовать для вывода модифицированного содержимого дескриптора и какие методы класса BodyTagSupport следует переопределить. На примере простого JSP-документа, содержащего пользовательские дескрипторы (листинг 2.4,а), продемонстрируем действия, которые выполняет контейнер сервлетов над содержимым пользовательского дескриптора.

Листинг 2.4,a. /test.jsp

<html><head><title>Body Content</title></head>

<%@ taglib uri='body' prefix='body' %>

<body:printBody>

  BODY 1<br>

  <body:printBody>

     BODY 2<br>

  </body:printBody>

</body:printBody>

</body>

</html>

Вложенные дескрипторы printBody выводят данные, показанные на рис. 2.5.

Как видно из рисунка, дескриптор printBody выводит содержащийся в нем текст. Класс поддержки дескриптора printBody представлен в листинге 2.4,6.

ЛИСТИНГ 2.4,6. /WEB-INF/classes/tags/PrintBodyTag. Java

package tags;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class PrintBodyTag extends BodyTagSupport {

  public int doEndTag() throws JspException {

   try {

    getBodyContent().writeOut(pageContext.getOut());

   }

   catch(java.io.IOException e) {

    throw new JspException(e.getMessage());

   }

   return SKIP_BODY;

  }

}

58        Глава 2. Дополнительные вопросы...

Как видите, код класса занимает всего несколько строк, но разобраться в нем непросто, поскольку непонятно, почему данные выводятся в выходной поток предыдущего дескриптора. Рассмотрим JSP-документ, представленный в листинге 2.4,а, с точки зрения контейнера сервлетов.

<html><head><title>Body Content</title></head>

<%@ taglib uri='body' prefix='body' %>

<body:printBody>

  BODY 1<br>

  <body:printBody>

     BODY 2<br>

  </body:printBody>

</body:printBody>

</body>

</html>

*   Вызывается  контейнером сервлетов после  обращения  к doStartTag() **  Вызывается контейнером сервлетов после обращения  к методу doAfterTag() ,   но перед вызовом doEndTag()

До появления включающего, или внешнего, дескриптора printBody, предопределенная переменная ссылается на объект JspWriter, посредством которого передается ответ на запрос. Присвоим этому состоянию номер 1. На рис. 2.6 два дескриптора printBody условно обозначаются как внешний и внутренний.

После того как контейнер сервлетов вызывает метод doStartTag внешнего дескриптора printBody, он записывает в предопределенную переменную out ссылку на экземпляр класса BodyContent. В этом объекте содержится ссылка на JspWriter, который в данном случае играет роль выходного потока предыдущего дескриптора. В этом состоянии (ему присвоен номер 2) стек выходных потоков насчитывает два элемента (рис. 2,6). Стек формируется посредством контекста документа, в частности с помощью метода PageContext. pushBody. Метод pushBody вызывается контейнером сервлетов сразу после вызова метода doStartTag внешнего дескриптора printBody.

Встретив внутренний дескриптор printBody, контейнер сервлетов снова вызывает метод PageContext.pushBody. Теперь стек содержит объект BodyContent внутреннего дескриптора, объект BodyContent внешнего дескриптора, а также объект JspWriter. Этому состоянию присваиваем номер 3. Заметьте, что выходным потоком предыдущего дескриптора для внутреннего printBody является объект BodyContent внешнего printBody. Аналогично, выходным потоком предыдущего дескриптора для внешнего printBody является объект JspWriter, посредством которого выводится ответ на запрос.

Тело дескриптора        59

Извлечение из стека

Обработка теш дескриптора и запись в стек

Содержимое внешнего дескриптора

Обработка тала дескриптора и запись в стек

Содержимое вн/треннего дескриптора

Содержимое внешнего дескриптора

Рис. 2.6. Обработка тела дескриптора

После выполнения метода doAfterBody внутреннего дескриптора printBody, но перед выполнением метода doEndTag этого дескриптора контейнер сервлетов вызывает метод PageContext. popBody. Метод popBody извлекает из стека текущий объект BodyContent, в результате чего восстанавливается состояние 2 (см. рис. 2.6).

Поскольку контейнер сервлетов вызывает PageContext .popBody между вызовами методов doAfterBody и doEndTag дескриптора, метод doAf terBody внутреннего дескриптора printBody выполняется в состоянии 3, а метод doEndTag этого дескриптора — в состоянии 2. Не зная этой особенности, трудно организовать обработку тела дескриптора.

Наконец, после выполнения метода doAfterBody внешнего дескриптора контейнер сервлетов выбывает метод PageContext. popBody.

Теперь становится ясно, почему в методе PrintBodyTag. doAfterBody данные выводятся в выходной поток предыдущего дескриптора. Так происходит потому, что при вызове doAfterBody объект BodyContent еще находится в стеке. Внутренний дескриптор printBody записывает данные в объект BodyContent внешнего дескриптора. Внешний printBody записывает свое содержимое и данные, переданные внутренним printBody, в объект JspWriter, Если бы метод PrintBodyTag. doAfterBody записывал данные с помощью предопределенной переменной out, они бы попали в текущий объект и несколько позже были бы вместе с ним удалены из стека.

На первый взгляд может показаться, что в классе PrintBodyTag удобнее было бы переопределить метод doEndTag и выводить данные, пользуясь предопределенной переменной out. Действительно, к моменту вызова doEndTag объект BodyContent, соответствующий текущему дескриптору, уже удален из стека, и переменная out ссылается

60

Глава 2. Дополнительные вопросы...

на объект BodyContent внешнего дескриптора. Как правило, таким способом можно обеспечить нормальную работу дескриптора, однако, в спецификации JSP 1.1 сказано, что после того, как объект BodyContent извлекается из стека, он становится доступным для повторного использования. Поэтому подобный подход лучше не применять.

Совет

Не следует обращаться к объекту BodyContent из метода doEndTag

В тот момент, когда контейнер сер влетов вызывает метод doEndTag, объект Body-Content, согласно спецификации JSP 1.1, доступен для повторного использования. Поэтому при обращении к содержимому дескриптора из метода doEndTag есть опасность обратиться к телу другого дескриптора или к объекту null. Обработку тела дескриптора лучше выполнять в методе doAfterBody, который вызывается до того, как объект BodyContent станет доступным для повторного использования.

Генерация JavaScript-кода

В данном разделе рассматриваются пользовательские дескрипторы, которые оформляют содержащиеся в них данные 8 виде HTML-элементов и добавляют к ним фрагменты JavaScript-кода. Возможность пользовательских дескрипторов генерировать JavaScript-фрагменты позволяет объединить преимущества программ, выполняющихся на стороне клиента и сервера.

На рис. 2.7 показан JSP-документ, в котором тело пользовательского дескриптора представляется в виде HTML-элемента SELECT. Дескриптор генерирует элемент SELECT и после выбора пункта списка передает данные на сервер. Подобным образом удобно оформлять набор ссылок.

В окне, представленном слева на рис. 2.7, показан процесс выбора пункта списка, а в правом окне — результат выбора.

Тело дескриптора   61

Код JSP-документа представлен в листинге 2.5,а.

Листинг2.5,a. /teat.jsp

<html><title>Java Api Documentation</title>

<body>

<%@ taglib uri='html.tld' prefix='html' %>

<form action='showApi.jsp'>

<font size='4'> View Documentation for</font>

 <html:links name='api'>

  <option value='Servlets'>servlets</option>

  <option value='JSP'>jsp</option>

  <option value='Swing'>swing</option>

  <option value='JDBC'>JDBC</option>

  <option value='JNDI'>JNDI</option>

  <option value='JavaBeans'>JavaBeans</option>

 </html:links>

</form>

</body></html>

Дескриптор links используется также, как и HTML-элемент select; в его состав входят элементы option. Код документа, отображаемого при выборе пункта списка, показан в листинге 2.5,6.

Листинг 2.5,6. /showApi.jsp

<html><title>The <%= request.getParameter("api") %> Api</title>

<body>

Documentation for <%= request.getParameter("api") %>

</body>

</html>

Документ showApi . j sp лишь показывает, что имеется возможность определить, какой из пунктов списка был выбран. При решении реальной задачи, по-видимому, следует отображать различные данные, в зависимости от того, какой API выбран пользователем. Класс поддержки дескриптора links приведен в листинге 2.5,в.

Листинг 2.5,в. /WEB-INF/classes/tags/LinksTag. Java

package tags;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyContent;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class LinksTag extends BodyTagSupport {

  private String name;

  private StringBuffer buffer;

  public void setName(String name) {

     this.name = name;

  }

  public int doAfterBody() throws JspException {

     try {

        String body = bodyContent.getString();

        bodyContent.clearBody();

        buffer = new StringBuffer(

                       "<select name='" + name + "' " +

                         "onChange='this.form.submit()'>" +

                          body + "</select>");

        bodyContent.print(buffer.toString());

        bodyContent.writeOut(getPreviousOut());

     }

     catch(java.io.IOException ex) {

        throw new JspException(ex.getMessage());   

     }

     return SKIP_BODY;

  }

}

62        Глава 2. Дополнительные вопросы...

Подобно HTML-элементу select, дескриптор links может содержать атрибут name (атрибуты size и multiple в дескрипторе links не поддерживаются). Для поддержки этого атрибута в классе LinksTag содержится свойство, соответствующее соглашениям JavaBeans.

Вызывая BodyContent. getString, метод doAf terBody получает содержимое дескриптора, представленное в виде строки. Затем объект BodyContent очищается и формируется буфер, содержащий старое тело дескриптора, помещенное между открывающим и закрывающим HTML-дескрипторами select. После этого содержимое буфера помещается в BodyContent, а затем записывается во включающий выходной поток. В результате генерируется следующий фрагмент кода:

<select   name='api'   onChange='this.form.submit О'> <option value-'Servlets'>servlets</option> option value='JSP'>jsp</option> <option value='Swing'>swing</option> <option value='JDBC >JDBC</option> <option value=' JNDI' >jNDK/option> <option value-'JavaBeans'>JavaBeans</option> </select>

Сгенерированный дескриптор select содержит атрибут cmChange; с его помощью определяется фрагмент JavaScript-кода (this . form, submit ()), который при выборе одного из пунктов списка передает содержимое формы программе на стороне сервера.

Помимо обработки содержимого пользовательского дескриптора, данный пример демонстрирует совместное использован HeJSP и JavaScript. JSP в сочетании с JavaScript позволяет объединять технологии, предназначенные для работы на стороне клиента и на стороне сервера и создавать достаточно сложные и в то же время гибкие приложения. Благодаря механизму пользовательских дескрипторов средства JSP и JavaScript представляются в формате, привычном для авторов Web-страниц.

Вложенные дескрипторы        63

Вложенные дескрипторы

Пользовательские дескрипторы верхнего уровня могут совместно работать, используя контекст документа для сохранения объектов в требуемой области видимости. Вложенные дескрипторы могут поступать подобным образом, но они также могут непосредственно взаимодействовать между собой с помощью метода f indAncestor-WithClass (Tag,   Class) классаTagSupport.

Обнаружение предков дескрипторов

Несмотря на то что метод f indAncestorWithClass объявлен как static и, следовательно, для обращения к нему не нужен экземпляр класса, чаще всего этот метод вызывается в теле одного из методов класса поддержки дескриптора. В этом случае дескриптор, передаваемый методу findAncestorWithClass, является текущим дескриптором, на который указывает переменная this.

Почти всегда предок представляет собой подкласс класса TagSupport. Поскольку метод f indAncestorWithClass возвращает ссылку на объект Tag, чтобы воспользоваться методами TagSupport, необходимо преобразовать возвращаемое значение к типуTagSupport.

Вторым параметром методу findAncestorWithClass передается объект Class. Как правило, при поиске известно имя класса предка, а необходимо найти ссылку па соответствующий класс. Именно это позволяет сделать метод f indAncestorWithClass-.

Простой, но очень удобный метод, пригодный для применения в подклассах класса TagSupport, приведен ниже. Он позволяет решить вопросы, обсуждавшиеся в этом разделе. Данный метод получает имя класса предка и возвращает ссылку на объект TagSupport.

//  Метод,   упрощающий   нахождение   предка  для   подклассов //  класса TagSupport

г

private TagSupport getAncestor(String className)

throws JspException (

Class klass = null; // имя class использовать нельзя try <

klass = Class.forName(className}; } catch(ClassNotFoundException ex) f

throw new JspException(ex.getMessage());

return (TagSupport)findAncestorWithClass(thia, klass);

64   Глава 2. Дополнительные вопросы...

Разделение данных

Возможность нахождения дескрипторов-предков, а также способность хранить именованные значения позволяют дескриптору разделять данные с его предками. В следующем фрагменте кода дескриптор устанавливает значение, которое извлекается одним из его потомков:

<smp:ancestor>

<smp:offspring/> </smp:ancestor>

В методе doStartTag предок связывает с именем "name" значение "value". package tags;

public class AncestorTag extends TagSupport { public int doStartTag() throws JspException I setValue("name", // только строка

"value"); // любой объект

return EVAL BODY INCLUDE;

Метод AncestorTag.doStartTag возвращает значение EVAL_BODY_INCLUDE; в результате контейнер передает тело дескриптора без изменений. В теле родительского дескриптора содержится дочерний дескриптор, класс поддержки которого выглядит следующим образом:

public class OffspringTag  extends  TagSupport   ( public  int  doStartTag()   throws  JspException   { AncestorTag ancestor = null;

try {

ancestor = (AncestorTag)findAncestorWithClass(this, tags.AncestorTag.class);

pageContext.getOut0.println(

ancestor.getValue["name")) ; } catch(Exception ex) {

throw new JspException(ex.getMessage(}}; } return EVAL_BODY_INCLUDE;

Метод OffSpringTag.doStartTag вызывает метод findAncestorWithClass для обнаружения ближайшего предка класса tags.AncestorTag. Метод TagSupport. getValue ("name") возвращает значение ("value"), которое затем выводится с помощью предопределенной переменной out.

Вложенные дескрипторы        65

Резюме

Приблизительно треть спецификацииJSP 1.1 посвящена пользовательским дескрипторам. Очевидно, что составители этой спецификации рассматривали пользовательские дескрипторы как одно из главных средств JSP. В данной главе обсуждались расширенные средства создания пользовательских дескрипторов. Надеюсь, что теперь читатели разделяют мнение авторов JSP.

В данной главе было рассмотрено несколько пользовательских дескрипторов. Другие примеры вы встретите далее в тексте книги. Эти дескрипторы составляют небольшую библиотеку, которую вы можете без ограничений использовать в своих целях.

HTML-ФОРМЫ

В этой главе.

• Формы и компоненты bean.

Поля редактирования, текстовые области
и переключатели опций.

Флажки опций и списки,

• Проверка корректности данных.

Проверка на стороне клиента с помощью JavaScript-
сценариев.

Проверка на стороне сервера с помощью JSP-документов.

Проверка на стороне сервера с помощью сервлетов.

Использование сервлетов и JSP-документов для проверки
на стороне сервера.

• Базовый набор классов для работы с формами.

Использование образа разработки fa?ade для HTML-форм.

Элементы, допускающие выбор.

Проверка введенных данных.

• Применение пользовательских дескрипторов.

HTML-формы выполняют в Web-приложении ту же роль, что и элементы пользовательского интерфейса (например, компоненты Swing или AWT) в обычных программах.

В спецификации JSP не предусмотрена поддержка форм, однако вы можете использовать действие jsp:useBean для хранения значений форм в объектах bean. Хранение состояния одного объекта {в данном случае элемента формы) в другом объекте (компоненте bean) — пример использования образа разработки Memento.

В начале данной главы будут обсуждаться вопросы инкапсуляции состояния формы в объекте bean. Далее мы рассмотрим проверку корректности на стороне клиента и на стороне сервера, а также создадим пользовательский дескриптор, расширяющий HTML-дескриптор form.

Формы и компоненты bean

Использование компонентов bean для хранения состояния— один из основных способов поддержки форм. Доступ JSP к beans осуществляется достаточно просто, поэтому извлечение значений формы из компонентов не составляет труда.

Передача данных формы

Когда пользователь активизирует форму, броузер передает строку параметров ресурсу, URL которого указан посредством атрибута action формы. {Если атрибут action отсутствует, активизация формы приводит к повторной загрузке Web-структураницы.) В строке параметров содержатся имена и значения в формате 7.имя1=значение1&имя2=зпачение2&. . . &имяN=знaчeниeN. Например, для формы, в составе которой находится единственное поле редактирования с именем name, содержащее текст John, строка параметров будет иметь вид "name=John", а если в ту же форму добавить еще одно поле с именем phone и ввести в нем последовательность спмво-

68        Глава 3. HTML-формы

лов    555-1212,   то   строка   параметров   будет   выглядеть   следующим   образом: "name=John&phone=555-1212".

В сервлетах и JSP-документах данные формы доступны посредством объекта request. Например, для вывода значения, введенного в поле редактирования, можно использовать следующее выражение:

<%ш  request.getParameter("name"J   %>

Кроме того, данные формы можно поместить в соответствующий компонент bean.

<jsp:useBean id='form1   class='beans.Form1   scope='request'>

<jsp:setProperty  name='formf   property='*'/> <ljsp:useBean>

В предыдущем фрагменте кода в качестве значения свойства property указан символ '*'. При этом используется Java-отражение и устанавливаются свойства компонента, соответствующие параметрам запроса. Например, при получении параметра с именем name вызывается метод компонента setName,

В последующих двух разделах описываются особенности применения действия j sp: useBean для хранения состояния формы в компоненте bean. Далее будет обсуждаться хранение состояния элементов формы в отдельных компонентах; этот подход более сложен в реализации, но допускает повторное использование кода.

Совет

Формы, компоненты bean и образ Memento

В соответствии с образом разработки Memento состояние сохраняется вне объекта, что позволяет восстановить его. Реализовать образ разработки Memento для HTML-форм можно, помещая данные формы в один или несколько компонентов bean.

Поля редактирования, текстовые области и переключатели опций

В данном разделе рассматривается работа с полями редактирования, текстовыми областями и переключателями опций. Эти элементы объединены здесь потому, что при активизации формы каждый из них генерирует единственный параметр запроса. Элементы, которые могут генерировать несколько параметров, будут рассмотрены в следующем разделе.

В JSP-документе, показанном на рис. 3.1, содержатся поля редактирования, переключатели опции и текстовая область. В форме, которая находится в этом документе, отсутствует атрибут action, поэтому после щелчка на кнопке Submit Query форма повторно отображается на экране. В левом окне на рис. 3.1 показана Web-страница до активизации формы, а в правом окне— эта же страница после того, как форма была активизирована.

Формы и компоненты bean        69

При пм отображении формы ее элементы иницализируются значениями, хранящимися в компоненте bean. JSP-код документа, показанного на рис. 3.1,

представлен в листинге 3.1,а.

Листинг3.1,a. /form.jsp

<html><title>Textfields, Text Areas, and Radio Buttons</title>

<body>

<jsp:useBean id='form' class='beans.Form' scope='request'>

  <jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<form>

  Name:<input type='text' name='name'

             value='<%=form.getName()%>' /><p>

  <input type='radio' name='credit' value='visa'

  <%= form.creditSelectionAttr("visa") %>>visa

  <input type='radio' name='credit' value='mc'

  <%= form.creditSelectionAttr("mc") %>>master card<br>

  <input type='radio' name='credit' value='disc'

  <%= form.creditSelectionAttr("disc") %>>discovery

  <input type='radio' name='credit' value='amex'

  <%= form.creditSelectionAttr("amex") %>>american express

  </p><p><textarea name='comments' cols='25'

               rows='5'><%= form.getComments() %></textarea>

  </p><p><input type='submit'/></p>

</form>

<%@ include file='showForm.jsp' %>

</body></html>

70        Глава 3. HTML-формы

При каждом обращении к данном)' документу в области видимости запроса создается и сохраняется компонент типа beans . Form. Действие j sp: setProperty устанавливает свойства компонента в соответствии с параметрами аапроса. Затем компонент используется для определения значений поля редактирования и текстовой области, а также для установки состояния переключателя опции.

Документ, приведенный в листинге 3.1,а, включает JSP-файл showForm. j sp. В этом файле, содержимое которого показано в листинге 3.1,6, также производится обращение к компоненту bean.

ЛИСТИНГ 3.1,6. /showForm.jsp

<b>name:     </b> <%= form.getName()     %><br>

<b>comments: </b> <%= form.getComments() %><br>

<b>credit:   </b> <%= form.getCredit()   %></p>

Файл showForm. jsp включен в состав документа посредством директивы include, т.е. обработка содержимого файла производится на этапе компиляции. Поэтому выражения, находящиеся в файле showForm. jsp, могут обращаться к компоненту form. Если бы для включения файла использовалось действие, содержимое файла обрабатывалось бы в момент получения запроса и компонент bean с именем form был бы недоступен из файла showForm. jsp.

Код компонента bean, создаваемого JSP-до куме нтом, представлен в листинге 3.1,в.

ЛИСТИНГ З.1.В. WEB-INF/classes/beans/Form.Java

package beans;

public class Form {

  String name, comments = "Enter comments", credit;

  public void setName(String s) { name = s; }

  public String getName() { return name != null ? name : ""; }

  public void setComments(String s) { this.comments = s; }

  public String getComments() { return comments; }

  public void setCredit(String s) { credit = s; }

  public String getCredit() {

     return credit != null ? credit : "";

  }

  public String creditSelectionAttr(String creditName) {

     if(credit != null) {

        return credit.equals(creditName) ? "checked" : "";

     }

     return "";

  }

}

Формы и компоненты bean        71

Согласно спецификации JavaBeans, компонент Form содержит методы, обеспечивающие доступ к свойствам, в которых хранятся значения поля редактирования (name), текстовой области (comments) и переключателей опции (credit). Данный компонент также инкапсулирует код для доступа к данным формы. При этом значение null обрабатывается специальным образом, чтобы при отсутствии соответствующего параметра отображалась не последовательность символов "null", а пустая строка. Если бы обработка значения null не выполнялась в компоненте bean, соответствующий код пришлось бы включать в состав JSP-до куме н та. Вынесение Java-кода за пределы JSP-файла делает документ более читаемым и упрощает его сопровождение.

Флажки опций и списки

Списки создаются посредством HTML-элементов select и option. Элемент select, содержащий элементы option, имеет следующий вид:

<select  name='years'   size='5'   multiple>

<option value='2000'>2000</option>

<option value='2001'>200K/option>

<option valuer'2002'>2002</option>

. . .   последующие  пункты   ,., </select>

Если в приведенном списке пользователь выберет пункты 2000, 2001 и 2002, строка параметров, передаваемая на сервер при активизации формы, будет выглядеть следующим образом: "... years=2000Syears=2Q01&years=2002 ...". Поскольку параметр years встречается несколько раз, для чтения его значений необходимо использовать следующее JSP-выражение:

<%  String[]   value=  request.getParameterValues("years")   %>

В данном случае, в отличие от поля редактирования и текстовой области, значение параметра представляется в виде массива строк.

Флажки опций, как и списки, могут передавать более одного значения параметра. Это возможно тогда, когда в состав формы входит несколько соответствующих элементов с одним именем. Рассмотрим особенности обработки таких значений.

На рис. 3.2 показан JSP-документ с формой, содержащей флажки опций и список. Как и для документа, изображенного на рис. 3.1. в форме отсутствует атрибут action, и при активизации форма повторно отображается на экране, В левом окне на рисунке показан внешний вид Web-страницы до того, как пользователь щелкнул на кнопке Submit Query, а в правом окне — тот же документ после активизации данной кнопки.

  1.  Глава 3. HTML-формы

Листинг3.2,a. /form.jsp

<html><title>Checkboxes and Options</title>

<body>

<jsp:useBean id='form' class='beans.Form' scope='request'>

  <jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<form>

<font size='5'>Find:</font><br>

  <input type='checkbox' name='categories' value='stanley-cup'

     <%= form.categorySelectionAttr("stanley-cup") %>>

     Stanley Cup Champs<br>

  <input type='checkbox' name='categories' value='super-bowl'

       <%= form.categorySelectionAttr("super-bowl") %>>

     Super Bowl Champs<br>

  <input type='checkbox' name='categories' value='world-series'

       <%= form.categorySelectionAttr("world-series") %>>

     World Series Champs<br>

  <input type='checkbox' name='categories' value='ncaa'

     <%= form.categorySelectionAttr("ncaa") %>>

     NCAA Champs<br>

  <p><font size='5'>For the following years:</font><br>

  <select name='years' size='5' multiple>

<%    for(int year=1999; year > 1989; --year) { %>

          <option value='<%= year %>'

          <%= form.yearSelectionAttr(Integer.toString(year)) %>>

          <%=year%></option>

<%    }    %>

  </select>

  </p><p><input type='submit'/></p>

</form>

<%@ include file='showForm.jsp' %>

</body></html>

Формы и компоненты bean        73

Как и документ, показанный в листинге 3.1, данный JSP-код создает компонент bean типа beans . Form и инициализирует его в соответствии с параметрами запроса. После этого данный компонент используется для определения состояния флажков опций и пунктов списка.

Как и в рассмотренном ранее примере, в состав данного документа включается JSP-файл, в котором содержится код, предназначенный для отображения значений формы. Этот файл представлен в листинге 3.2,6.

Листинг 3.2,6. /showForm.

<% if(form.getCategories() != null) { %>

     <b>find:</b>

<%      String[] strings = form.getCategories();

     for(int i=0; i < strings.length; ++i) { %>

        <%= strings[i] %>

<%      }

  }

   if(form.getYears() != null) { %>

     <br><b>dates:</b>

<%      String[] strings = form.getYears();

     for(int i=0; i < strings.length; ++i) { %>

        <%= strings[i] %>

<%      }

  }

%>

Код компонента bean, который используется JSP-документом, представленным в листингах 3.2,а и 3.2,6, показан в листинге 3.2,в.

Листинг 3.2,в. /WEB-INF/classes/beans/Form. Java

package beans;

public class Form {

  String[] years, categories;

  public String[] getCategories() { return categories; }

  public void setCategories(String[] categories) {

     this.categories = categories;

  }

  public String categorySelectionAttr(String category) {

     if(categories != null) {

        for(int i=0; i < categories.length; ++i) {

           if(categories[i].equals(category))

              return "checked";

        }

     }

     return "";

  }

  public String[] getYears() { return years; }

  public void setYears(String[] years) { this.years = years; }

  public String yearSelectionAttr(String year) {

     if(years != null) {

        for(int i=0; i < years.length; ++i) {

           if(years[i].equals(year))

              return "selected";

        }

     }

     return "";

  }

}

74        Глава 3. HTML-формы

Как и bean, приведенный в листинге 3.1, данный компонент предоставляет методы для доступа к свойствам, в которых хранятся значения элементов формы. Однако, в отличие от предыдущего примера, данный компонент работает с массивами, так как параметры, генерируемые флажками опций и списком, представляются как массивы строк.

Поскольку для установки флажка опции используется атрибут checked, а для выбора пункта списка— атрибут selected, методы categorySelectionAttr и уеаг-SelectionAttr компонента возвращают для выбранных флажков опций и пунктов списка соответственно значения "checked" и "selected".

Проверка корректности данных

Проверка корректности данных, введенных пользователем, может производиться как на стороне клиента, так и на стороне сервера. На стороне клиента для этого обычно используются JavaScript-сценарии, а на стороне сервера—JSP-документы.

На стороне клиента проверка выполняется значительно быстрее, поскольку, чтобы проверить данные на стороне сервера, надо их передать но сети. С другой стороны, проверка на стороне сервера дает более надежные результаты, так как пользователь может запретить в клиент-программе выполнение JavaScript-сценариев.

Если проверка на стороне клиента обеспечивает более высокую производительность, а проверка на стороне сервера более высокую надежность, какому типу проверки надо отдать предпочтение? Для создания жизнеспособных приложений желательно осуществлять оба типа проверки. В этом случае средства, выполняемые на стороне сервера, дублируют действия на стороне клиента.

Проверка корректности данных        75

Проверка на стороне клиента с помощью Ja vaScript-сценариев

Поскольку проверка на стороне клиента не имеет непосредственно отношения к разработке JSP-документов, она рассматривается здесь лишь в общих чертах. На рис. 3.3 показан JSP-до куме нт, содержащий простую форму с нолями редактирования, которые предназначены для ввода имени пользователя и его почтового адреса. Б данный документ включен JavaScript-сценарий, который позволяет убедиться, что все поля заполнены, в составе почтового адреса содержится символ '@' и адрес заканчивается последовательностью ". com" или ". edu".

В окне, приведенном на рис. S.3 слева, показана частично заполненная форма, а в правом окне — результат, полученный при активизации этой формы.

Рис. 3.3. Использование JavaScript для проверки корректности данных на стороне клиента

Кодировка JSP-документа, показанного на рис. 3.3, представлена в листинге 3.3.

Листинг 3.3./form.jsp

<html><head>

  <title>Client Side Validation with JavaScript</title>

</head>

<body>

<jsp:useBean id='form' scope='request' class='beans.Form'>

  <jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<form name='simpleForm' onSubmit='return validate()'>

<table><tr>

  <td>First Name:</td>

  <td><input type='text' size=15 name='firstName'

        value='<%= form.getFirstName() %>'/></td></tr><tr>

  <td>Last Name:</td>

  <td><input type='text' size=15 name='lastName'

        value='<%= form.getLastName() %>'/></td></tr><tr>

  <td>E-mail Address:</td>

  <td><input type='text' size=25 name='emailAddress'

        value='<%= form.getEmailAddress() %>'/></td></tr>

  </table>

  <p><input type='submit'/></p>

</form>

<script language='JavaScript'>

  function validate() {

       var firstName = simpleForm.firstName.value,

            lastName = simpleForm.lastName.value,

        emailAddress = simpleForm.emailAddress.value,

            errorMsg = "",

       errorDetected = false;

     if(firstName == "" || lastName == "" || emailAddress == ""){

        errorMsg += "Please fill in all fields";

        errorDetected = true;

     }

     if(!isEmailAddressValid(emailAddress)) {

          if(errorMsg.length > 0)

             errorMsg += "\n";

        errorMsg += "Email Address must contain @ and " +

                    "end in .com or .edu";

         errorDetected = true;

     }

     if(errorDetected)

        alert(errorMsg);

     return !errorDetected;

  }

  function isEmailAddressValid(s) {

     var atSign = new RegExp(".*(@).*"),

         dotEdu = new RegExp(".edu$"),

         dotCom = new RegExp(".com$");

     return atSign.test(s) && (dotCom.test(s) || dotEdu.test(s));

  }

</script>

</body></html>

76        Глава 3. HTML-формы

В качестве значения атрибута onSubmit формы указано выражение 'return validate () ', т.е. при активизации формы вызывается JavaScript-функция validate. Если функция validate возвращает значение true, данные формы передаются на сервер, в противном случае отображается диалоговое окно с сообщением об ошибке.

Как и в предыдущих примерах, значения формы сохраняются в составе компонента bean. Поскольку этот компонент не используется при проверке корректности данных и так как подобные компоненты были рассмотрены ранее, код bean здесь не приводится. При необходимости вы можете скопировать код компонента вместе с кодами других примеров, рассмотренных в данной книге, обратившись по адресу http://www. phptr.com/advj sp.

Проверка корректности данных        77

Документ, представленный в листинге 3.3, демонстрирует совместное использование JSP и JavaScript, Поскольку JSP-контейнер передает HTML-кол в неизменном виде, текст JavaScripL-сценария можно непосредственно включать в состав JSP-документа.

Проверка на стороне сервера с помощью JSP-д окументов

Проверка корректности данных на стороне сервера выполняется с помощью JSP-документов или сервлетов. Здесь рассматривается использование JSP-документов, а проверка посредством сервлетов будет обсуждаться в следующем разделе.

На рис, 3,4 показан JSP-документ, содержащий форму, аналогичную форме на рис, 3,3. В левом окне на рис. 3.4 показано состояние документа перед щелчком на кнопке Submit Query, а в правом окне — состояние документа после активизации формы.

По сравнению с кодом, представленным в листинге 3.3, данный код имеет два отличия. Во-первых, в нем отсутствует JavaScript-сценарий, а во-вторых, в качестве значения атрибута action формы задано значение validate, jsp.

<form name='simpleForm'   action='validate.jsp'/> </form>

При активизации формы запрос передается документу validate , jsp, код которого показан в листинге 3.4.

Листинг 3.4. /validate.jsp

<% String first = request.getParameter("firstName");

  String  last = request.getParameter("lastName");

  String email = request.getParameter("emailAddress");

  String errorMsg = "";

  boolean errorDetected = false;

  if(first.equals("") || last.equals("") || email.equals("")) {

     errorMsg += "Please fill in all fields.";

     errorDetected = true;

  }

  if(email.indexOf("@") == -1 ||

     (!email.endsWith(".com") && !email.endsWith(".edu"))) {

     if(errorMsg.length() > 0)

        errorMsg += "<br>";

     errorMsg += "Email address must contain @ and " +

                 "end in .com or .edu";

     errorDetected = true;

  }

  if(errorDetected) { %>

     <%= errorMsg %>

     <jsp:include page='form.jsp' flush='true'/>

<%   } else { %>

     <jsp:forward page='registrationComplete.jsp'/>

<% } %>

78        Глава 3. HTML-формы

Рассматриваемый JSP-докумснт получает данные формы посредством предопределенной переменной request и использует для проверки те же критерии, что и JavaScript-сценарий, приведенный в листинге 3.3. Если данные формы корректны, запрос перенаправляется документу registrationComplete . j зр, в противном случае отображается Web-страница с сообщением об ошибке, включающая форму ввода, которая присутствовала в исходном документе.

Проверка на стороне сервера с помощью сервлетов

Для проверки корректности данных на стороне сервера вместо JSP-докумснтов могут использоваться сервлеты. Дни этого в JSP-документ, содержащий форму, надо внести единственное изменение— задать значение атрибута action, равное ValidationServlet.

<form name-'simpleForm' action='ValidationServlet'/> </form>

Сервлет, на который ссылается данный фрагмент кода, приведен в листинге 3.5.

Листинг 3.5./WEB-INF/clasaes/ValidationServlet.java

import Java.io.lOException;

import j avax.servlet.RequestDispatcher; import javax.servlet.ServletException;

Проверка корректности данных   79

import javax.servlet.http.HttpServletRequest; import j avax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServlet;

public class ValidationServlet extends HttpServlet ( public void service(HttpServletRequest req,

HttpServletResponse res) throws IOException, ServletException { String first = req.getParameter("firstMame"),

last « req.getParameter("lastName"), email = req.getParameter("emailAddress"), errorMsg = "", nextStop = "/registrationComplete.jsp";

boolean errorDetected = false;

if(first.equals("") || last.equals{"") ||

email.equals!"")) {

errorMsg += "Please fill in all fields."; errorDetected = true; { if(email.indexOf["@") ==-1 ||

(!email.endsWith(".com"} && !email.endsWith(".edu"))) { if(errorMsg.length() > 0) errorMsg += "<br>";

errorMsg += "Email address must contain @ and " + "end in .сои or ,edu";

errorDetected = true; } if(errorDetected) (

res.getWriter().print(errorMsg);

nextStop = "/form.jsp"; )

RequestDispatcher rd; rd - getServletContext().getRequestDispatcher(nextStop);

if(nextStop.equals("/form.jsp"))

rd.include(req, res); else

rd.forward(req, res) ;

Сервлет, представленный в листинге 3.5, выполняет те же функции, что и JSP-документ, код которого был приведен в листинге 3.4. Длл того чтобы исключить Java-код из JSP-документа, желательно, чтобы проверка корректности данных выполнялась посредством сервлетов или компонентов bean. Дополнительную информацию о проверке корректности и использовании для этой цели сервлетов и JSP вы найдете в конце данной главы.

80        Глава 3. HTML-формы

Совет

Проверка корректности данных на стороне клиента и на стороне сервера

Для того чтобы повысить жизнеспособность приложения, в нем надо реализовать проверку корректности данных как на стороне клиента, так и на стороне сервера. : Средства проверки на стороне сервера выполняют те же функции, что и средства на стороне клиента и предусматриваются на случай, если в клиент-Программе запрещено выполнение JavaScript-кода.

Использование сервлетов и JSP-документов для проверки на стороне сервера

В предыдущих разделах рассматривалась проверка данных на стороне сервера, выполняемая либо с помощью сервлетов, либо с помощью JSP. Ни одно из этих решений нельзя назвать идеальным, потому что в первом случае сервлет должен генерировать HTML-код, а во втором случае JSP-документ должен содержать код, выполняющий проверку. Поскольку сервлеты используются как контроллеры, а jSP-документы выполняют роль просмотра, сервлеты не должны заниматься отображением содержимого, а JSP-документы не должны содержать бизнес-логику.

Гораздо лучшим будет решение, при котором сервлеты содержат логику проверки, а JSP-документы используются для отображения сообщений об ошибках.

Сервлет, показанный в листинге 3.5, легко модифицировать так, чтобы он вместо вывода сообщения об ошибке сохранял его в области видимости запроса. Фрагменты кода модифицированного сервлета показаны ниже.

public class ValidationServlet  extends  HttpServlet   { public void service(HttpServletRequest   req,

HttpServletResponse   res)

throws   IOException,   ServletException   { String  first  =  req.getParameter("firstName"),

last  =  req.getParameter("lastName"), email  «  req.getParameter("emailAddress"), errorMsg =  "",   nextStop =   "/registrationComplete.jsp";

boolean errorDetected =  false;

if(errorDetected)   {

req.satAttributa("validate-error",   errorMsg); nextStop «  "/form.jsp";   }

Проверка корректности данных        81

Поскольку сервлет сохраняет сообщение об ошибке в области видимости запроса, JSP-документ имеет доступ к этому сообщению и может извлечь его с помощью следующего скриптлета:

<html><headxtitle>Server  Side Validation</title> <%@  taglib uri-'validate'   prefix='validate'   %> </head>

<body>

<% String errorMsg =   (String)request.getAttribute(

"validate-error");

if[errorMsg   != null)    {   %>

<*= errorMsg  %> <%   }   %>

<p>

<form name='simpleForm'   action='ValidationServlet'/>

</form>

</p>

</bodyx/html>

Если вы хотите удалить данный скриптлет из состава JSP-документа, то должны реализовать пользовательский дескриптор, отображающий сообщение об ошибке. Этот дескриптор использовался бы следующим образом:

<htmlxhead><title>Server  Side Validation</title> <%@  taglib uri='validate'   prefix='validate'   %> </head>

<body>

•«validate: showValidateError/>

<form name='simpleForm'   action='ValidationServlet'/>

</form>

</p>

</body></html>

Класс поддержки данного пользовательского дескриптора может выглядеть так, как показано в листинге 3.6.

Листинг3.6. /WEB-INF/olasses/tags/ShowValidateErrorTag.Java

package tags;

import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport;

public class ShowValidateErrorTag extends TagSupport { public int doEndTagO throws JspException {

String msg = (String)pageContext.getRequest().

getAttribute("validate-error") if(msg != null) { try {

82        Глава 3. HTML-формы

pageContext.getOut().print(msg);

}

catch(Java.io.lOException ex) {

throw new JspException(ex.getMessage());

return EVAL_PAGE; } }

Базовый набор классов для работы с формами

Вернемся к рассмотрению компонентов bean, предназначенных для сохранения состояния элементов формы. Обычно фрагмент кода, с помощью которого создается компонент, включается а начало JSP-докумснта и имеет следующий вид:

<jsp:useBean  id='form'   class='beans.Form'   scope='request'>

<jsprsetProperty  name='form1   property='*'/> </jsp:useBean>

Если в качестве значения атрибута property дескриптора j sp : setProperty задан символ '*', JSP-коатейнер использует Java-отражение для установки свойств компонента bean в соответствии с параметрами запроса. Например, для параметра запроса с именем category JSP-контейнср обращается к методу setCategory. Если этот метод существует, JSP-контейнср вызывает его и передает значение параметра category. Такие же действия предпринимаются для остальных параметров запроса.

Применение Java-отражения - простой способ установки свойств beans, однако он имеет существенный недостаток: если а разных формах используются различные имена элементов (а обычно дело обстоит именно так), для каждой из форм необходимо создавать свой Java-класс.

Несмотря на то что для разных форм создаются различные JSP-классы, коды этих классов во многом совпадают. Совпадающие фрагменты кода могут использоваться в различных компонентах bean. Рассмотрим следующий фрагмент кода (он входит в состав компонента, представленного в листинге 3.2,с):

...

private String!] categories;

щ

public String categorySelectionAttr(String category) { if [categories != null) (

for{int i=0; i < categories.length; ++i) { if(categories[i].equals(category) J

return "checked"; ) } return "";  }

Базовый набор классов для работы с формами        83

Метод categorySelectionAttr, приведенный выше, определяет, установлен ли флажок опции с именем category, и возвращает либо строку "checked", либо пустую строку. Возвращаемое значение может быть использовано в качестве атрибута HTML-дескриптора input. Другие компоненты, соответствующие другим формам, содержат аналогичные методы для флажков опций. Например, для набора флажков с именем grocery компонент bean реализует следующий метод, практически идентичный рассмотренному выше:

Реализация практически идентичных методов для каждого из компонентов требует дополнительных усилий и является источником ошибок. Уменьшить объем кода, создаваемого вручную, позволяет образ разработки facade.

Использование образа разработки fagade для HTML-форм

Объект facade предоставляет единый упрощенный интерфейс для общих средств подсистем. Для случая HTML-форм образ разработки facade может быть реализован так, как это показано на рис. 3.5.

84        Глава 3. HTML-формы

Объектом Facade является компонент bean для формы, а общими средствами — классы из пакета beans .html, содержащие код для элементов формы. На рис. 3.5 общие средства составляют повторно используемый код; возможность повторного использования кода является одним из основных преимуществ применения образа

facade для поддержки форм.

Согласно толковому словарю, fa$ade — фальшивое, поверхностное или искусственное представление некоторого явления. Это подходящее определение для компонента, представленного на рис. 3.5, который лишь делегирует функции более общим средствам. Например, рассмотренный ранее метод categorySelectionAttr может иметь следующий вид:

public class  Form  \

CheckboxElement  categories  = new CheckboxElement 0;

public  String categorySelectionAttr()    {

return  categories.selectionAttr(category);

В данном случае Form.categorySelectionAttr возвращает строку символов, которая может быть использована в качестве атрибута HTML-дескриптора input.

Класс CheckboxElement, используемый компонентом Form, выглядит приблизительно так;

public  class  CheckboxElement   ( private  Sttingtl   items;

public String  selactionAttr(String  item)   { if [items   !- null)   {

for(int   i=0;   i  <  items.length;   ++i)    ( if(items[i].equals(item)) return  "checked";

return   "";

}

}

В данном случае код, определяющий, установлен ли флажок опции, инкапсулирован в составе повторно используемого класса.

Классы, аналогичные CheckboxElement, можно создать и для других элементов формы, например для текстовых областей, переключателей опций и др. Базовый набор таких классов рассматривается в следующем разделе.

Реализация базового набора классов

Базовый набор классов позволяет существенно упростить процесс сохранения состояния формы. Реализовать такой набор можно различными способами; один из них описан в данном разделе. Сначала мы рассмотрим вопросы использования классов, а затем обсудим особенности их реализации.

.

Базовый набор классов для работы с формами        85

На рис. 3.6 показан JSP-документ, в состав которого входит форма, содержащая большое количество элементов. Атрибут action для данной формы не указан, поато-му после щелчка на кнопке Submit Query форма повторно отображается на экране.

Листинг 3.7,а. /form, jsp (фрагменты)

<html><title>A Form Framework</title>

<body>

<%@ taglib uri='WEB-INF/tlds/html.tld' prefix='html' %>

<jsp:useBean id='form' class='beans.Form' scope='request'>

  <jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<html:form name='form' method='get' focus='name'>

 <table border='2' cellspacing='3'><tr><td valign='top'>

     <%-- Name textfield --%>

     Name:

        <input type='text' name='name'

               value='<%= form.getName() %>'/><p>

  </td><td>

     <%-- Checkboxes --%>

        Programming Language Experience:<br>

        <input type='checkbox' name='languages' value='html'

               <%= form.languageSelectionAttr("html") %> /> Html

        <input type='checkbox' name='languages' value='java'

              <%= form.languageSelectionAttr("java") %> /> Java

        <input type='checkbox' name='languages' value='perl'

              <%= form.languageSelectionAttr("perl") %> /> Perl

        <input type='checkbox' name='languages' value='jsp'

              <%= form.languageSelectionAttr("jsp") %> /> Jsp<br>

  </td></tr><tr><td>

     <%-- Radio Buttons --%>

     Credit Card:<br>

        <input type='radio' name='credit' value='visa'

        <%= form.creditSelectionAttr("visa") %>>

              visa</input type='radio'><br>

        <input type='radio' name='credit' value='master card'

        <%= form.creditSelectionAttr("master card") %>>

              master card</input type='radio'><br>

        <input type='radio' name='credit' value='american express'

        <%= form.creditSelectionAttr("american express") %>>

              amex</input type='radio'><br>

        <input type='radio' name='credit' value='discovery'

        <%= form.creditSelectionAttr("discovery") %>>

              discovery</input type='radio'><br>

  </td><td>

     <%-- Text Area --%>

     Comments:<br>

     <textarea name='comments' cols='20' rows='5'><%= form.getComments() %></textarea><p>

  </td></tr><tr><td valign='top'>

     Expiration Date:<br>

     <%-- Select --%>

        <select name='expiration' size=5>

        <option <%= form.expirationSelectionAttr("01/00") %>

        value='01/00'  >01/00</option>

        <option <%= form.expirationSelectionAttr("02/00") %>

        value='02/00'  >02/00</option>

        <option <%= form.expirationSelectionAttr("03/00") %>

        value='03/00'  >03/00</option>

        <option <%= form.expirationSelectionAttr("04/00") %>

        value='04/00'  >04/00</option>

        <option <%= form.expirationSelectionAttr("05/00") %>

        value='05/00'  >05/00</option>

        <option <%= form.expirationSelectionAttr("06/00") %>

        value='06/00'  >06/00</option>

        <option <%= form.expirationSelectionAttr("07/00") %>

        value='07/00'  >07/00</option>

        <option <%= form.expirationSelectionAttr("08/00") %>

        value='08/00'  >08/00</option>

        </select>

  </td><td valign='top'>

     Select a Fruit:<br>

     <%-- Select --%>

        <select name='fruit'>

        <option <%= form.fruitSelectionAttr("apple") %>

        value='apple'  >apple</option>

        <option <%= form.fruitSelectionAttr("pear") %>

        value='pear'  >pear</option>

        <option <%= form.fruitSelectionAttr("lemon") %>

        value='lemon'  >lemon</option>

        <option <%= form.fruitSelectionAttr("orange") %>

        value='orange'  >orange</option>

        <option <%= form.fruitSelectionAttr("plum") %>

        value='plum'  >plum</option>

        </select>

  </td></tr></table>

  <%-- Submit Button --%>

  <p><input type='submit'/></p>

</html:form>

</body></html>

86        Глава 3. HTML-формы

 ;

При загрузке данный JSP-докумепт создает экземпляр компонента beans . Form и устанавливает его свойства в соответствии с параметрами запроса. Затем с помощью компонента bean задаются значения формы.

Код компонента bean, использованного в листинге 3.7,а, представлен в листинге 3.7,6.

ЛИСТИНГ3.7,6. /WEB-INF/classes/beans/Formoava

package beans;

import beans.html.CheckboxElement;

import beans.html.OptionsElement;

import beans.html.RadioElement;

import beans.html.TextElement;

import beans.html.TextAreaElement;

public class Form {

  TextElement name = new TextElement();

  RadioElement credit = new RadioElement();

  TextAreaElement comments = new TextAreaElement();

  CheckboxElement languages = new CheckboxElement();

  OptionsElement expiration = new OptionsElement();

  OptionsElement fruit = new OptionsElement();

  public String getName() { return name.getValue(); }

  public void setName(String s) { name.setValue(s); }

  public String getComments() { return comments.getValue();}

  public void setComments(String s) { comments.setValue(s); }

  public String getCredit() { return credit.getValue(); }

  public void setCredit(String s) { credit.setValue(s); }

  public String[] getLanguages() { return languages.getValue(); }

  public void setLanguages(String[] s) { languages.setValue(s); }

  public String[] getFruit() {return fruit.getValue();}

  public void setFruit(String[] s) {fruit.setValue(s);}

  public String[] getExpiration() {return expiration.getValue();}

  public void setExpiration(String[] s) {expiration.setValue(s);}

  public String creditSelectionAttr(String s) {

     return credit.selectionAttr(s);

  }

  public String languageSelectionAttr(String s) {

     return languages.selectionAttr(s);

  }

  public String expirationSelectionAttr(String s) {

     return expiration.selectionAttr(s);

  }

  public String fruitSelectionAttr(String s) {

     return fruit.selectionAttr(s);

  }

}

Базовый набор классов для работы с формами        87

Данный компонент bean использует несколько классов из пакета beans .html, например TextElement, RadioElement и др. Сравнив данный компонент с компонентом /WEB-INF/classes/beans/Form. Java, показанным в листинге 3.2,в и выполняющим более простые функции, можно увидеть, насколько упрощает процесс разработки использование классов, принадлежащих beans . html.

Поскольку компонент bean, приведенный в листинге 3.7,6, не делает ничего, кроме делегирования функций объектам, он представляет собой самый настоящий объект faЈade и может быть сгенерирован при разборе HTML-кода.

При рассмотрении JSP-документа, показанного на рис. З.й, наибольшего внимания заслуживают вопросы реализации пакета beans.html. Обсуждение этого пакета мы начнем с диаграммы классов, представленной на рис. 3.7.

С точки зрения содержимого пакета beans.html элементы формы можно разделить на две категории: элементы, генерирующие единственный параметр (например, поля редактирования, текстовые области и переключатели опций), и элементы, генерирующие несколько параметров в составе запроса (например, флажки опций и списки). Эти две категории элементов представляются двумя абстрактными классами StringElement и StringArrayElement.

Поскольку все классы, представляющие элементы формы, должны включать методы для проверки корректности содержимого, классы StringElement и StringArrayElement реализуют интерфейс ValidateElement, показанный в листинге 3.7,в.

В интерфейсе ValidatedElement объявлен метод validate, который указывает, корректно ли содержимое элемента. Если элемент заполнен неправильно, т.е. если метод validate возвращает значение false, метод getValidationError должен возвращать строку, указывающую причины, по которым содержимое элемента считается некорректным.

Код класса StringElement показан в листинге 3.7,г.

Базовый набор классов для работы с формами   89

Листинг 3.7,г. /WEB-INF/classes/beans/html/StringElemant. java

package beans.html;

public class StringElement implements ValidatedElement {

  final protected String emptyString = "";

  private String value;

  public void setValue(String value) {

     this.value = value;

  }

  public String getValue() {

     return value != null ? value : emptyString;

  }

  public boolean validate() {

     return true;

  }

  public String getValidationError() {

     return emptyString;

  }

}

Класс StringElement поддерживает строку символов, доступ к которой осуществляется с помощью метода getValue. Если значение не задано, данный метод возвращает пустую строку. Это необходимо, так как в противном случае метод возвращал бы значение null, которое отображалось бы в соответствующем HTML-элементе,

По умолчанию данные, описываемые подклассами StringElement, считаются корректными. Чтобы изменить поведение класса, надо переопределить методы validate и getValidationError. Пример переопределения этих методов приведен в следующем разделе.

Классы TextElement и TextAreaElement являются подклассами StringElement, причем в них не реализуются новые средства. Определения этих классов приведены ниже.

public  class  TextElement extends   StringElement   {1 public  class  TextAreaElement  extends  StringElement   1}

В настоящий момент классы TextElement и TextAreaElement нужны лишь для того, чтобы текст программы был более удобочитаемым. Впоследствии в них, возможно, будут добавлены новые функции.

Код класса StringArrayElement, выступающего в роли суперкласса для классов CheckboxElement и OptionsElement, показан в листинге 3.7,д.

Листинг 3.7,Д. /WEB-IKF/classes/beans/html/StringArrayElement.Java

package beans.html;

public abstract class StringArrayElement

              implements SelectableElement, ValidatedElement {

  final String emptyString = "";

  private String[] value;

  public void setValue(String[] value) {

     this.value = value;

  }

  public String[] getValue() {

     return value != null ? value : new String[]{};   

  }

  public boolean validate() {

     return true;

  }

  public String getValidationError() {

     return "";

  }   

  public boolean contains(String s) {

     String[] strings = getValue();

     for(int i=0; i < strings.length; ++i) {

        if(strings[i].equals(s))

           return true;

     }

     return false;

  }

}

90        Глава 3. HTML-формы

Класс StringArrayEleraent похож на StringElement, отличие состоит лишь в том, что StringArrayEleraent поддерживает массив строк. Как и для класса StringElement, содержимое StringArrayElement по умолчанию считается корректным.

Метод contains — вспомогательный метод, применяемый подклассами класса StringArrayElement. Он используется для того, чтобы определять, установлен ли флажок опции либо выбран ли пункт списка.

Элементы, допускающие выбор

Флажки и переключатели опций, а также пункты списка представляют собой группы однотипных элементов. При этом один или несколько элементов группы могут быть выбраны. Элементы такого типа описывает интерфейс SelectableEleraent, код которого приведен в листинге 3.7,е.

ЛИСТИНГ 3.7,в. /WEB-INF/classes/beans/html/SelectableElement. ;)ava

package beans.html;

public  interface   SelectableElement   { String   selectionAttr(String   s};

Интерфейс SelectableElement реализуется классами RadioElement, Checkbox-Element и Opt ions Element. Для выбранного элемента, в зависимости от его типа, метод selectionAttr соответствующего класса возвращает значение "checked" или

Базовый набор классов для работы с формами   91

"selected". Если элемент не выбран, возвращается пустая строка. Коды трех указанных классов приведены ниже.

// Переменная emptyString  объявлена в  классе // StringElement  как protected,

public class  RadioElement  extends  StringElement

implements   SelectableElement   { public String selectionAttr(String value)   {

return  getValue().equals{value)    ?   "checked"   :   emptyString; }}

public class  CheckboxElement  extends   StringerrayElement   { public  String  selectionAttr(String s)    t

return  contains(s)   ?   "checked"   :   amptyString;

public class OptionsElement extends StringArrayElement { public String selectionAttr(String s) (

return contains(s) ? "selected" : emptyString; }

По умолчанию данные, поддерживаемые всеми классами пакета beans . html, считаются корректными. Для реализации процедуры проверки необходимо создать подкласс соответствующего класса и переопределить в нем методы, объявленные в интерфейсе ValidatedElement. Вопросам проверки корректности данных, вводимых пользователем в элементах формы, посвящен следующий раздел.

Проверка введенных данных

В JSP-документе, показанном на рис. 3.8, проверяется правильность ввода имени и пша платежной карты. Данные формы считаются корректными, если поле, предназначенное для ввода имени, заполнено и не содержит пробелов или цифр, а также если выбран один из типов платежной карты. В левом окне на рис. 3.8 показана частично заполненная форма, а а правом окне — результат активизации этой формы.

При наличии базового набора классов добавление средств проверки не составляет большого труда. Сначала надо указать в качестве значения атрибута action формы JSP-документ, выполняющий проверку.

// Так должен выглядеть  дескриптор  form для документа, // представленного в листинге   3.7,а.

<form action='validate.jsp1>

Далее следует создать документ validate, j sp, код которого содержится в листинге 3.8,а.

92        Глава 3. HTML-формы

Лиотинг3.8,Э. /validate.jsp

<jsp:useBean id='form' class='beans.Form' scope='request'>

  <jsp:setProperty name='form' property='*'/>

</jsp:useBean>

<%   String errorMsg = "";

  boolean errorDetected = false;

  if(!form.validate()) {

        errorMsg += form.getValidationError();

        errorDetected = true;

  }

  if(errorDetected) { %>

     <font color='red' size='5'>

     The form was not filled out correctly because:<p>

     </font><font size='3'>

     <%= errorMsg %></font></p>

     <jsp:include page='form.jsp' flush='true'/>

<%   } else { %>

     <jsp:forward page='registrationComplete.jsp'/>

<% } %>

В дшсументе validate, jsp создается компонент bean с именем form. Свойства этого компонента устанавливаются в соответствии со значениями параметров запроса. Созданный компонент используется при проверке корректности данных, содержащихся п элементах формы. Если данные формы недопустимы, отображаются сообщение об ошибке, полученное из bean, и форма ввода. Если данные формы корректны, управление передается другому^Р-документу.

Базовый набор классов для работы с формами        93

Поскольку документ validate . j sp использует дескриптор setProperty, реализовать проверку с помощью сервлета достаточно сложно. Ссрвлеты, в отличие от JSP-документов, не могут применять Java-отражение для установки свойств bean в соответствии со значениями параметров.

Код компонента bean, использованного в рассматриваемом документе, представлен влистинге 3.8,6.

ЛИСТИНГ3.8,6. /WEB-INF/classes/beans/Form. Java

// Пропущенные  фрагменты  кода   были   представлены // в листинге  3.7,6

public class   Form implements ValidatedElement   (

private CreditElement  credit = new CreditElement()
private NameElement name  = new NameElement();

private  String error;

public boolean validate ()    1 error  =   "";

if(Iname.validate(})   {

error += name.getValidationError [); } if(!credit.validate())    {

if(error.length()   >   0) error  +-   "<br>";

error += credit.getValidationError(};

)

return error == ""; } public String getValidationError() {

return error;

Компонент bean, приведенный в листинге 3.8,6, похож на компонент, код которого содержится в листинге 3.7,6. Отличие состоит в том, что вместо объектов TextElement и CheckboxElement используются экземпляры классов NameElement и CreditElement, Кроме того, в компоненте реализован метод validate, выполняющий проверку имени и типа платежной карты. Если данные формы не выдерживают проверку, создается сообщение об ошибке.

Классы NameElement и CreditElement представляют собой подклассы классов TextElement и CheckboxElement. В них переопределены методы, объявленные в интерфейсе ValidatedElement. Код класса CreditElement показан влистинге 3.8,в.

94        Глава 3. HTML-формы

Листинг 3.8,в. /WEB-INF/classes/beans/CreditElement. Java

package beans.html;

public class CreditElement extends RadioElement { private String error;

public boolean validate () { boolean valid = true; String value = getValue();

error = "";

if(value == null || value.length{) == 0) ( valid = false;

error = "Credit card must be selected"; }

return valid; }

public String getValidationError () { return error;

Метод validate класса CreditElement возвращает значение true, если кнопка переключателя выбрана, и false — в противном случае. Код класса NameElement показан в листинге 3,8,г.

Листинг3.8,г. /WEB-INF/claases/beans/NameElement.Java

package beans.html;

public class NameElement extends TextElement ( private String error;

public boolean validated { boolean valid = true; String value = getValue();

error = "";

if[value.length О == 0) {

valid - false;

error - "Name field must be filled in";

} else {

forfint i=0; i < value.length (); ++i) {

.char с = value. charAt (i) ;

if (c == v ' Illc > "0' ss с < '9')M valid = false; if(c == ' ')

error = "Name cannot contain spaces";

Применение пользовательских дескрипторов        95

else

error = "Name cannot contain digits";{

return valid;

}

public String getValidationError() return error;

Если поле ввода имени заполнено и не содержит пробелов или цифр, метод NameElement .validate возвращает значение true.

Как видите, реализация проверки на базе имеющихся классов — простая задача. К компоненту bean добавляются новые методы, а элементы классов расширяются и в них переопределяются методы validation и getValidationError.

Совет

Повторное использование кода для поддержки форм

Рассмотренный базовый набор классов существенно упрощает задачу поддержки форм в JSP-документах. В данном случае не имеет значения, используете вы предложенный набор или реализуете собственные классы, важно понимать общие принципы, лежащие в основе используемых средств.

Главным преимуществом применения базового набора классов является возможность повторного использования кода. Эта цель может быть достигнута и другими способами, например, необходимые функции могут быть инкапсулированы в составе пользовательских дескрипторов.

Применение пользовательских дескрипторов

Наверное, вы согласитесь с тем, что наличие расширяемых HTML-дескрипторов упростило бы задачу создания Web-страниц. Например, было бы удобно, если бы дескриптор select можно было расширить для создания списка, состоящего из гипертекстовых ссылок. В HTML не предусмотрены средства создания новых дескрипторов па базе имеющихся, но это можно сделать, реализуя пользовательские дескрипторы JSP. Например, описанный выше вариант дескриптора select рассматривался в главе 2,

В данном разделе речь пойдет о пользовательском дескрипторе, расширяющем HTML-дескриптор form. В частности, мы реализуем в нем поддержку дополнительного атрибута focus, позволяющего при загрузке формы передать фокус ввода указанному элементу. Пример использования данного дескриптора приведен в листинге 3.9.а.

96        Глава 3. HTML-формы

Листинг 3.9,а. Пример применения пользовательского дескриптора form

<%@ taglib uri='form.tldr prefix='html' %>

<%-- При загрузке формы поле редактирования name

получает фокус ввода --%>

<html:form name='myForm' focus='name' method='post'> <l~- Остальные элементы формы были представлены в листинге 3.7,а —%>

</htrrd: f orm>

 

Форма, создаваемая дескриптором, приведенным в листинге 3.9,а, почти идентична форме, показанной на рис. 3.8. Отличие состоит лишь в том, что сразу после загрузки формы фокус ввода получает поле редактирования с именем name. В данном листинге элементы отсутствуют, частично они приведены в листинге 3.7,а.

Класс поддержки дескриптора form представлен в листинге 3.9,6.

Листинг 3.9,6. /WEB-INF/classes/tags/FormTag.Java

package tags;

import javax.servlet.jsp.*;

import javax.servlet.jsp.tagext.*;

public class FormTag extends BodyTagSupport {

  public String method, name, focus;

  public void setName(String name) { this.name = name; }

  public void setMethod(String method) { this.method = method; }

  public void setFocus(String focus) { this.focus = focus; }

  public int doEndTag() throws JspException {

     try {

        String body = bodyContent.getString();

        bodyContent.clearBody();

        StringBuffer buffer = new StringBuffer(

           "<form name='" + name + "' method='" + method + "'>"

           + body + "\n</form>\n" +

           "<script language='JavaScript'>\n" +

           "document." + name + "." + focus + ".focus()" +

           "\n</script>");

        bodyContent.print(buffer.toString());

        bodyContent.writeOut(pageContext.getOut());

     }

     catch(java.io.IOException ex) {

        throw new JspException(ex.getMessage());   

     }

     return EVAL_PAGE;

  }

}

Применение пользовательских дескрипторов        97

В данном разделе детали создания пользовательских дескрипторов не обсуждаются. Подробно этот вопрос был рассмотрен в первых двух главах данной книги. Здесь же лишь приводится пример использования дескрипторов для расширения HTML-элементов.

Класс поддержки, показанный в листинге 3.9,6, генерирует HTML-дескриптор form и передает ему в неизменном виде атрибуты method И name. Заметьте, что прочие атрибуты, в частности, такой важный атрибут, как action, пользовательский дескриптор не поддерживает. Добавить их несложно; дескриптор должен обрабатывать их так же, как и атрибуты method и name.

Передачу фокуса ввода элементу осуществляет JavaScripl-cденарий. Пользовательские дескрипторы, генерирующие JavaScript-код, объединяют в себе возможности технологий, применяемых на стороне клиента и на стороне сервера. Они позволяют добиться результатов, достичь которых другими средствами было бы слишком сложно.

Резюме

В данной главе описывались средства поддержки HTML-форм. Эти средства соответствуют двум образам разработки: Memento, согласно которому состояние формы хранится в компонентах bean, и facade, определяющему принцип инкапсуляции функций и упрощения доступа к повторно используемому код)'. Эти образы разработки были использованы при создании базового набора классов, который вы можете расширять, реализуя новые функциональные возможности.

В данной главе также был рассмотрен пользовательский дескриптор, который может применяться вместо HTML-дескриптора form. Он дает возможность организовать передачу фокуса ввода требуемому элементу формы. При желании вы можете реализовать JSP-расширсния других HTML-дескрипторо». Информацию о подобных разработках вы можете найти по следующим адресам.

ШАБЛОНЫ

В этой главе...

Инкапсуляция алгоритмов компоновки.

Необязательное содержимое.

          • Содержимое, зависящее от роли.

• Раздельное определение областей.
            • Вложенные области.

Расширение областей.

Объединение различных подходов к созданию облает

Реализация дескрипторов поддержки областей.

Как правило, в наборах инструментов для создания оконных систем предусмотрены три типа объектов: компоненты, контейнеры и диспетчеры компоновки. Эти объекты позволяют создавать гибкие расширяемые приложения. Компонентами считаются такие графические объекты, как кнопки, меню Ш1И списки; контейнеры служат для объединения компонентов, а диспетчеры компоновки задают размеры компонентов и их расположение в контейнере'.

При реализации компонентов, контейнеров и диспетчеров компоновки обычно используют образы разработки Composite и Strategy"'. Образ разработки Composite применяется для создания компонентов и контейнеров; при этом оговаривается, что контейнер является компонентом. Таким образом, вы можете поместить в контейнер любой компонент, даже если компонент сам является контейнером. Благодаря этому вы можете реализовывать любые уровни вложенности контейнеров.

Диспетчеры компоновки реализуются в соответствии с образом разработки Strategy, определяющим семейство алгоритмов и инкапсулирующим алгоритмы в отдельных объектах. Благодаря такой инкапсуляции алгоритмы компоновки можно заменять, не изменяя контейнер, с которым связывается конкретный диспетчер компоновки.

В JSP отсутствуют аналоги компонентов, контейнеров и диспетчеров компоновки. Однако в JSP есть возможность определять пользовательские дескрипторы и включать Web-крмпоиенты, а это позволяет рсализовывать и компоненты, и контейнеры, и диспетчеры компоновки. (Web-компонентами считаются сервлеты, HTML-файлы и JSP-документы. Термин Web-компонент никак не связан с термином компонент, используемым в этой главе.) В данной главе будут рассмотрены вопросы создания расширяемых Web-приложений, простых в сопровождении и допускающих повторное использование.

Компоненты, контейнеры и диспетчеры компоновки, которые обсуждаются в этой главе, несколько отличаются от подобных элементов, используемых в оконных системах. Чтобы подчеркнуть эти отличия, введем следующие обозначения.

1См. Geary. Graphic. Java Volume 1:AWT, Prentice Halt, 1998. -Прим. авт.

2См. Gamma, Helms,Johnson, Vtissides. Design Patterns, Addhon-Wesky, 1994. -Прим. авт.

100      Глава 4. Шаблоны

Раздел (section) — объект, который представляет HTML или JSP в контексте до
кумента.

Область (region) — объект, содержащий разделы.

Шаблон (template) — JSP-документ, который определяет размещение областей и
разделов.

Области и шаблоны соответствуют контейнерам и диспетчерам компоновки. Разделы похожи на компоненты, поскольку они представляют некоторое содержимое (HTML-файл или JSP-документ) и отличаются от компонентов тем, что они не поддерживают события.

Материал данной главы можно условно разделить на две части. В первой части рассматривается использование библиотеки дескрипторов для создания JSP-докуменов средствами разделов, областей и шаблонов. Вторая часть посвящена вопросам реализации пользовательских дескрипторов и связанных с ними компонентов bean.

Подход, используемый в данной главе, соответствует образу J2EE Composite View и позволяет составлять единый просмотр из нескольких частных просмотров, применяя компоненты bean и пользовательские дескрипторы. Дополнительную информацию о шаблонах можно найти в работе Элара (Alur), Крапи (Crupi) и Малкса (Malks) CoreJ2EE Patterns, опубликованной Prentice Hall и Sun Microsystems Press.

Инкапсуляция алгоритмов компоновки

Поскольку в процессе разработки расположение элементов неоднократно изменяется, важно инкапсулировать средства компоновки так, чтобы вносимые в них изменения оказывали минимальное влияние на остальную часть приложения. Диспетчеры компоновки демонстрируют один из главных принципов объектно-ориентироцан-ного проектирования: элементы, подвергающиеся частым изменениям, должны быть инкапсулированы.

Большинство Web-страниц состоит из нескольких разделов. Например, страница, показанная на рис. 4.1, содержит заголовок, колонтитул, оглавление в левой части и основное содержимое.

Для размещения элементов Web-страницы может быть использован дескриптор table. Код конкретного JSP-документа приведен в листинге 4.1.

102      Глава 4. Шаблоны

<html><head><title>Templates</title></head>

<body background='graphics/blueAndWhiteBackground.gif'>

<table>

  <tr valign='top'><td><%@include file='sidebar.html'%></td>

     <td><table>

        <tr><td><%@include file='header.html'%></td></tr>

        <tr><td><%@include file='introduction.html'%></td></tr>

        <tr><td><%@include file='footer.html'%></td></tr>

        </table>

     </td>

  </tr>

</table>

</body></html>

Документ, представленный в листинге 4.1, использует для включения данных действие j sp: include. Благодаря этому появляется возможность изменять содержимое документа, заменяя включаемые файлы, при этом код основного документа не изменяется. Однако в данном случае расположение элементов определяется структурой основного документа, и для перекомпоновки Web-страницы надо изменять ее код. Если на Web-узле содержится большое число Web-страниц одинакового формата, то даже для незначительного изменения их внешнего вида приходится затрачивать большие усилия по модификации кода.

Автор может не только отделять содержимое от Web-страницы, в которой оно отображается, как это было показано в листинге 4.1, но и отделять от Web-страницы рас-плюжение элементов. При этом появляется возможность изменять компоновку документа, не внося изменений eJSP-файлы.

Разделы, области и шаблоны

Для отделения компоновки содержимого от документа можно применить один из самих старых приемов программирования — косвенные обозначения. Мы разделяем JSP-документ, содержащий данные и реализующий их размещение (листинг 4.1), на два документа: область, которая определяет содержимое, и шаблон, с помощью которого задается размещение. Область показана в листинге 4.2,а.

Листинг 4.2,а. JSP-документ, определяющий область

 taglib uri=fregions'   prefix^'region'   %>

Templates'   direct^'true'/> /header.jsp'   /> /sidebar.jsp'   /> /introduction.jsp' />

<region:render  template=' /template.jsp'> <region:put  section^'title'     content= <region:put   section='header'   content= <region:put  section='sidebar'content^ <region:put  section^'content'content=

<region:put  section^'footer'   content='/footer.jsp'   /> </region:render>

Документ, код которого представлен выше, использует пользовательские дескрипторы для определения области. Область содержит четыре раздела, показанные в листинге 4.1.

Каждый раздел связан с шаблоном, который задается посредством атрибута template дескриптора region: render. Открывающий дескриптор region : render создает область (region) и помещает ее в область видимости приложения. Подробно вопросы создания области будут рассматриваться далее в этой главе.

Дескрипторы region:put сохраняют пару имя-значение в области, созданной с помощью открывающего дескриптора region: render. Эти имя и значение представляют имя раздела и его содержимое; например, в коде, приведенном в листинге 4.2,а, помимо прочих разделов, определяется раздел с именем header, содержимое которого находится в файле /header . j sp.

При обработке закрывающего дескриптора region: render включается шаблон, заданный ранее с помощью атрибута template. Код шаблона представлен в листинге 4.2,6.

Инкапсуляция алгоритмов компоновки      103

Листинг 4.2,6. Шаблон, который используется областью, определенной алистинге 4.2,а.

<$@ taglib uri='regions' prefix='region' %>

<htmlxhead>

<title><region:render section='title'/></title> </head>

<body background='graphics/blueAndWhiteBackground.gif>

<table>

<tr valign='top'> <td>

<region:render section='sidebar'/> </td> <td>

<table> <tr> <td>

<region:render section='header'/> </td> </tr> <tr> <td>

<region:render section='content'/> </td> </tr> <tr> <td>

<region:render section='footer'/> </td> </tr> </table> </td>

</table>

</body> </html>

Подобно всем шаблонам, шаблон, приведенный в листинге 4.2,6, использует для представления разделов дескриптор region: render. Этот дескриптор имеет доступ к области, включающей шаблон и сохраненной в области видимости приложения. Дескриптор region: render получает имя содержимого, связанного с разделом, и включает соответствующий ресурс. (Следует заметить, что дескриптор region: render представляет как области, так и разделы.)

В дескрипторе region : render может быть задан атрибут direct. Если значение этого атрибута равно true, содержимое не включается в region: tender, а непосредственно передается в поток, соответствующий предопределенной переменной out. Например, в листинге 4.2,а содержимое дескриптора title ("Templates") используется в качестве заголовка окна.

104      Глава 4. Шаблоны

На Web-узлах, содержащих большое количество Web-страниц одинакового формата, используется один шаблон, подобный представленному в листинге 4.2,6, и JSP-до-кументы, использующие этот шаблон (пример такого документа приведен в листинге 4.2,а). При переходе к новому формату изменения вносятся только в шаблон.

Другим преимуществом шаблонов и включаемого содержимого является поддержка модульного подхода к разработке. Например, JSP-файл, показанный в листинге 4.2,а, включает ресурс /header, jsp, содержимое которого приведено в листинге 4.2,в.

Поскольку код в файле /header. j sp является включаемым содержимым, нет необходимости повторять его в каждом документе. Заметьте также, что в /header. j sp отсутствуют привычные HTML-дескрипторы html, head, body и т.д. Эти дескрипторы содержатся в шаблоне. Код в файле /header . j sp прост и удобен для сопровождения.

В данном разделе вы ознакомились с пользовательскими дескрипторами region: render и region:put из библиотеки, предназначенной для создания областей. Данная библиотека предоставляет и другие средства, например, поддержку необязательного содержимого и содержимого, определяемого ролями. Эти вопросы будут рассмотрены в последующих разделах.

Совеп

ют Используйте шаблоны при разработке Web-приложений

Разделы, области и шаблоны позволяют следовать модульному подходу при создании Web-приложений. Включаемое содержимое дает возможность модифицировать данные, не изменяя WetxrrpaHHUbi, в которых они отображаются. Аналогично, инкапсуляция способов размещения дает возможность изменять расположение элементов в нескольких^Р-документах, модифицируя лишь один шаблон.

Необязательное содержимое

Данные, представляемые посредством шаблонов, необязательны, благодаря этому один шаблон можно применять для нескольких областей. Например, на рис. 4.2 показаны две области, использующие один и тот же шаблон. В области, расположенной слева, содержимое определено для оглавления, заголовка и нижнего колонтитула. Как видно из рис. 4.2, если шаблон не может найти содержимое раздела, он игнорирует этот раздел.

Необязательное содержимое      105

Листинг 4.3,а. Содержимое одного из разделов пропущено

<I3 taglib uri='regions' prefix='region' %>

<table> <tr>

<td valign='top'>

<font size='5'>Specifying All Content:</font> <table cellspacing=f20'> <tr> <td>

<%— Содержимое этой области задано —%> <region:render template='hscf.jsp'> <region:put section='header'

content='/header.jsp'/>

<region:put section^'sidebar'

content=r/sidebar.jsp' />

<region:put section='content'

content='/content.j sp' />

<region:put section='footer'

content='/footer,jsp' /> </region:render> </td> </table> </td>

<td valign='top'>

<font size='5'>0mitting Content:</font> <table cellspacing='20f> <tr>

<%— Содержимое этой области пропущено —%>

106      Глава 4. Шаблоны

Для приведенного выше JSP-документа JSP-файлы, определяющие содержимое разделов, очень просты. Например, файл /sidebar, j sp имеет следующий вид:

<font  size™'5'>SIDEBAR</font>

Остальные JSP-файлы, соответствующие содержимому разделов, отличаются от /sidebar, jsp только текстом, расположенным между дескрипторами. Например, в файле /header. j sp находятся следующие данные:

<font   size='5f>HEADER</font>

Понятно, что в реальных приложениях данные в составе разделов более содержательны, однако JSP-файлы, на которые ссылается код в листинге 4.3,а, лишь иллюстрируют использование областей и шаблонов. Именно эти примеры и будут использоваться далее в этой главе.

Шаблон для областей в листинге 4.3,а содержится в файле /hscf. j sp и представлен в листинге 4.3,6.

Листинг 4.3,6. /hscf. jsP: шаблон, используемый областями в листинге 4.3,а

<html><head>

 <%@ taglib uri='regions' prefix='region' %>

</head>

<table border='1' height='450' width='450'>

  <tr> <%-- Sidebar --%>

     <td valign='top' width='25%'>

        <region:render section='sidebar'/>

     </td>

     <td valign='top' align='center' width='*'>

        <table height='450'>

           <tr> <%-- Header --%>

              <td align='center' height='20%'>

                 <region:render section='header'/>

              </td>

           </tr> <%-- Main Content --%>

              <td align='center' height='*'>

                 <region:render section='content'/>

              </td>

           </tr> <%-- Footer --%>

              <td align='center' height='15%'>

                 <region:render section='footer'/>

              </td>

           </tr>

        </table>

     </td>

  </tr>

</table>

</body></html>

Содержимое, зависящее от роли      107

Как и шаблон, показанный в листинге 4.2,6, шаблон, код которого представлен в листинге 4.3,6, использует для размещения разделов дескриптор table.

Для того чтобы разделить две части документа, в шаблоне, представленном в листинге 4.3,6, указано обрамление таблицы. Как правило, в шаблонах отображаются только разделы.

Содержимое, зависящее от роли

Часто в Web-приложениях информация отображается в зависимости от роли пользователя. Например, два документа, показанные на рис. 4.3, созданы на базе одного JSP-шабл она, в котором панель редактирования включается только в том случае, если пользователь принадлежит роли curator.

108      Глава 4. Шаблоны

Шаблон, на базе которого созданы документы на рис. 4.3, имеет следующий вид:

<table>

<tcl><region:render section='editPanel'   role='curator'/></td> </table>

Дескриптор region:render представляет содержимое раздела только в том случае, если роль пользователя соответствует значению атрибута role. Атрибут role можно не указывать, в этом случае содержимое раздела (если оно присутствует) отображается.

В предыдущем примере шаблон задает отображение раздела editPanel в случае, если значение роли пользователя равно curator. Это ограничение распространяется на все области, использующие данный шаблон. В некоторых случаях бывает необходимо задать содержимое, зависящее от роли, для конкретных областей. Сделать это можно, задавая атрибут role в дескрипторе region: put. Этот атрибут применяется следующим образом:

<region:render  template='hscf.jsp' >

                               <region:put  section='header'   content='/header.jsp' role-'curator'/>

</region:render>

В данном примере раздел header будет добавлен к области, создаваемой посредством дескриптора region: render, только в том случае, если значение роли пользователя равно curator.

Раздельное определение областей

До сих пор определение и отображение всех областей задавалось в одном файле. Рассмотрим, например, JSP-документ, показанный на рис. 4.4.

Раздельное определение областей  109

JSP-документ, показанный на рис. 4.4, может быть создан как одна область, содержащая четыре раздела. Отображение этой области, как показано в листинге 4.4, задается одновременно с ее определением.

Листинг 4.4. Определение и представление области

<%@ taglib uri='regions'   prefix='region'   %> <table>

<td>

<%--  Содержимое области  задано s  составе  элемента  —%> <region:render  teraplate='hscf,j sp'>

<region:put  section='header'     content"'/header.jsp'/> <region:put   section='sidebar'   content^'/sidebar.jsp'/> <region:put  section=f content'   content^'/content.jsp'/> <region:put  section='footer'     content-'/footer.jsp'/> </region:render> </td> </tr> </table>

Определение и представление области может быть разделено. Например, в листинге 4.5 приведен JSP-документ, показанный на рис. 4.4, в котором представляется уже существующая область.

Листинг 4.5,а. JSP-документ, который использует существующую область

<%@  taglib uri='regions'   prefix^'region'   %> <%@  include  file='/regionDefinitions.jsp'   %> <region:render  region^'SIDEBAR_REGION'/>

В документе, код которого представлен в листинге 4.5,а, используется область SIDEBAR_REGION, определенная в файле /regionDef initions . jsp (листинг 4.5,6).

Листинг 4.5,6. /regionDefinitions. jsp: определение области

<%?  taglib uri='regions'   prefix='region'   %>

<region: define  id=' SIDEBAR_REGIO1SI'   scope=' application'

template='hscf.jsp' >

<region:put  section='header' content='/header.jsp'/>

<region:put  section='sidebar' content='/sidebar.jsp'/>

<region:put  section^'content' content='/content.jsp'/>

<region:put  section='footer' content='/footer.jsp'/>

</region:define>

110      Глава 4. Шаблоны

Для определения области с именем SIDEBAR_REGION, предназначенной для хранения в области видимости приложения, в листинге 4.5,6 используется дескриптор region:define. Имя области и область видимости задаются посредством атрибутов id и scope. В дескрипторе region : define обязательно должен присутствовать атрибут template, который определяет шаблон, используемый областью.

Подобно region: render, дескриптор region: define может содержать дескрипторы region:put, которые включают имена и содержимое разделов в область, созданную с помощью открывающего дескриптора region: define.

Зачем, спросите вы, определять области отдельно, а затем отображать их? Такое разделение позволяет объединять определения областей в одном файле, предоставляя таким образом доступ ко всем областям приложения. Это существенно упрощает процесс сопровождения. Как вы узнаете из следующего раздела, области допускают вложенность и наследование, поэтому несколько областей проще сопровождать, если они определены в одном файле.

Вложенные области

Поскольку разделы и области реализуются в соответствии с образом разработки Composite, в качестве содержимого раздела может быть указана область. Пример вложенных областей показав на рис. 4.5.

JSP-документ, показанный на рис. 4.5, идентичен JSP-документу, код которого представлен в листинге 4.5,а. Этот JSP представляет существующую область SIDEBAR_ REGION, которая определена в листинге 4.5,в.

Вложенные области      111

Листинг 4.5,В. /regionDefinitions. jsp

<%@ taglib uri='regions' prefix='region' %>

<region:define id='SIDEBAR_REGION' scope='application'

                   template='hscf.jsp'>

  <region:put section='header'  content='/header.jsp'/>

  <region:put section='sidebar' content='/sidebar.jsp'/>

  <region:put section='content' content='/content.jsp'/>

  <region:put section='footer'  content='/footer.jsp'/>

</region:define>

В области SIDEBAR_REGION в качестве содержимого раздела content задана область B0RDER_REGION. Таким образом, BORDER_REGION является вложенной в область SIDEBAR_REGION.

Обратите внимание, что область BORDER_REGION не обязательно определять преиедс, чем она будет включена в состав SIDEBAR_REGION. Дело в том, что SIDEBAR_REGION lie обращается к BORDER_REGION до начала отображения SI DEBAR_REGION.

Чтобы завершить данный пример, рассмотрим шаблон, приведенный в листинге 4.5,г; он используется BORDER_REGION.

Листинг 4.5,г. Шаблон, используемый BORDER^region

<%@ taglib uri='regions' prefix='region' %>

<region:define id='SIDEBAR_REGION' scope='application'

                               template='hscf.jsp'>

  <region:put section='header'  content='/header.jsp'/>

  <region:put section='sidebar' content='/sidebar.jsp'/>

  <region:put section='content' content='BORDER_REGION'/>

  <region:put section='footer'  content='/footer.jsp'/>

</region:define>

<region:define id='BORDER_REGION' scope='application'

                               template='tlbr.jsp'>

  <region:put section='top'    content='/top.jsp'/>

  <region:put section='left'   content='/left.jsp'/>

  <region:put section='right'  content='/right.jsp'/>

  <region:put section='bottom' content='/bottom.jsp'/>

</region:define>

112     Глава 4. Шаблоны

Шаблон, приведенный в листинге 4.5,г, представляет разделы top, left, right и bottom, определенные в области BORDER_REGION. Имя BORDER_REGION выбрано по аналогии с диспетчером компоновки AWT BorderLayout, компоненты которого располагаются аналогичным образом.

Расширение областей

Большие возможности при работе с областями предоставляет определение области как расширения другой области. Например, JSP-документ, показанный на рис. 4.6, воспроизводит область, которая является расширением области SIDEBAR_REGION, неоднократно встречавшейся в этой главе.

Код JSP-документа, показанного на рис. 4.6, приведен в листинге 4.6,а.

Подобно JSP-документу, представленному в листинге 4.5,а, документ в листинге 4.6,а включает JSP-файл, содержащий определение области. Затем в документе воспроизводится область EXTENDED_SIDEBAR_REGION.

Листинг 4.6,а. Расширение существующей области

taglib uri='regions'   prefix='region'   %> <%Q   include   file-'/regionDefinitions-override.jsp'   %>

<region:render   region='EXTENDED_SIDEBAR_REGION'/>

Объединение различных подходов к созданию областей      113

JSP-код, в котором определены области SIDEBAR_REGION и EXTENDED_SIDEBAR REGION, приведен в листинге 4.6,6.

Листинг 4.6,6. /regionDefinitions-override.

<%@ taglib uri='regions' prefix='region' %>

<region:define id='SIDEBAR_REGION' scope='request'

        template='hscf.jsp'>

 <region:put section='header'  content='/header.jsp'/>

 <region:put section='sidebar' content='EXTENDED_BORDER_REGION'/>

 <region:put section='content' content='/content.jsp'/>

 <region:put section='footer'  content='/footer.jsp'/>

</region:define>

<region:define id='BORDER_REGION' scope='request'

        template='tlbr.jsp'>

  <region:put section='top'    content='/top.jsp'/>

  <region:put section='bottom' content='/bottom.jsp'/>

  <region:put section='left'   content='/left.jsp'/>

  <region:put section='right'  content='/right.jsp'/>

</region:define>

<region:define id='EXTENDED_SIDEBAR_REGION' scope='request'

          region='SIDEBAR_REGION'>

 <region:put section='header' content='/overridden-header.jsp'/>

 <region:put section='sidebar'content='BORDER_REGION'/>

 <region:put section='content'content='SIDEBAR_REGION'/>

</region:define>

<region:define id='EXTENDED_BORDER_REGION' scope='request'

          region='BORDER_REGION'>

  <region:put section='top'    content='/overridden-top.jsp'/>

  <region:put section='bottom' content='/overridden-bottom.jsp'/>

  <region:put section='right'>

     <font size='4'>Direct Content</font>

  </region:put>

</region:define>

Как правило, при определении областей указывается шаблон, как в случае с областью SIDEBAR_REGION, определенной в листинге 4.6,6. Однако область может определяться посредством другой области; так, например, при определении EXTENDED_ SIDEBAR_REGION указывается область SIDEBAR_REGION.

При определении одной области посредством другой вновь определяемая область становится дочерней областью по отношению к указанной. Например, EXTENDED_ SIDEBAR_REGION определена в терминах SIDEBAR_REGION, поэтому EXTENDED_ SIDEBftR_REGION наследует содержимое SIDEBAR_REGION. Кроме того, посредством дескрипторов region:put в EXTENDED_SIDEBAR_REGION переопределено содержимое двух разделов,

Объединение различных подходов к созданию областей

На практике вам, наверное, никогда не придется создавать Web-страницы, которые были бы настолько сложны, как страница, показанная на рис. 4.7. Данный раздел призван лишь проиллюстрировать возможности библиотеки по созданию вложенных областей, использующих расширение.

JSP-код документа, показанного на рис. 4.7, представлен в листинге 4.7,а.

Листинг 4.7,a. /index, jsp

<%@  taglib uri='regions'   prefix='regions'   %>

<%@  include  file+'/regionDefinitions=override.jsp'   %>

Бregion:render region='EXTENDED_SlDEBAR_REG!ON'/>

  1.  

Глава 4. Шаблоны

Листинг 4.7,6. /regionDefinitions-override. Jsp

<%@ taglib uri='regions' prefix='region' %>

<region:define id='SIDEBAR_REGION' scope='request'

        template='hscf.jsp'>

 <region:put section='header'  content='/header.jsp'/>

 <region:put section='sidebar' content='EXTENDED_BORDER_REGION'/>

 <region:put section='content' content='/content.jsp'/>

 <region:put section='footer'  content='/footer.jsp'/>

</region:define>

<region:define id='BORDER_REGION' scope='request'

        template='tlbr.jsp'>

  <region:put section='top'    content='/top.jsp'/>

  <region:put section='bottom' content='/bottom.jsp'/>

  <region:put section='left'   content='/left.jsp'/>

  <region:put section='right'  content='/right.jsp'/>

</region:define>

<region:define id='EXTENDED_SIDEBAR_REGION' scope='request'

          region='SIDEBAR_REGION'>

 <region:put section='header' content='/overridden-header.jsp'/>

 <region:put section='sidebar'content='BORDER_REGION'/>

 <region:put section='content'content='SIDEBAR_REGION'/>

</region:define>

<region:define id='EXTENDED_BORDER_REGION' scope='request'

          region='BORDER_REGION'>

  <region:put section='top'    content='/overridden-top.jsp'/>

  <region:put section='bottom' content='/overridden-bottom.jsp'/>

  <region:put section='right'>

     <font size='4'>Direct Content</font>

  </region:put>

</region:define>

 

Реализация дескрипторов поддержки областей      115

Количество областей, представленных в листинге 4.7,6, и зависимость их друг от друга настолько велики, что такая структура скорее помешает, чем поможет в работе. Однако целью данного примера является лишь демонстрация возможностей библиотеки по поддержке областей.

Область EXTENDED_SIDEBAR_REGION отображается при просмотре документа, показанного на рис. 4.7. Данная область расширяет область SIDEBAR_REGION и переопределяет разделы header, sidebar и content. В разделах sidebar и content EXTENDED_SIDEBAR_REGION содержатся соответственно области BORDER_REGION И SIDEBAR_REGION.

В области SIDEBAR_REGION в качестве содержимого раздела sidebar используется область EXTENDED_BORDER__REGION. Эта область расширяет BORDER_REGION и переопределяет разделы top, bottom и right. Заметьте, что содержимое раздела right EXTENDED_BORDER_REGION определяется в теле дескриптора region:put. Такая возможность подробно обсуждается далее в этой главе.

Реализация дескрипторов поддержки областей

Библиотека поддержки областей, используемая в данной главе, состоит из четырех компонентов bean и трех пользовательских дескрипторов. Обсуждению этих компонентов и дескрипторов посвящена оставшаяся часть главы.

Компоненты bean

Компоненты bean, которые входят в состав библиотеки пользовательских дескрипторов, предназначенных для поддержки областей, перечислены в табл. 4.1. Все эти компоненты принадлежат пакету beans . regions.

Таблица 4.1. Компоненты bean в составе библиотеки поддержки областей (все указанные компоненты принадлежат пакету beans. regions)

Компонент Описание

Content Содержимое, которое воспроизводится в JSP PageContext

Section Содержимое, которое входит в состав области

Region Контейнер, содержащий разделы

RegionStack Стек областей (region), поддерживаемых в области видимо-

сти (scope) приложения

116      Глава 4. Шаблоны

Диаграмма классов для beans, приведенных в табл. 4.1, показана на рис. 4.8.

Класс Content представляет собой абстрактный класс, являющийся суперклассом классов Section и Region. Поскольку абстрактный класс Content представляет как примитивы (разделы), так и их контейнеры, указанные выше три класса соответствуют образу разработки Composite.

Классы Section и Region реализуют единственный абстрактный метод класса Content — метод render. Класс Region содержит хэш-таблицу разделов.

Код класса Content приведен в листинге 4.8,а

import javax.servlet.jsp.PageContext;

public abstract class Content implements java.io.Serializable {

  protected final String content, direct;

  // Render this content in a JSP page

  abstract void render(PageContext pc) throws JspException;

  public Content(String content) {

     this(content, "false");

  }

  public Content(String content, String direct) {

     this.content = content;

     this.direct  = direct;

  }

  public String getContent() {

     return content;

  }

  public String getDirect() {

     return direct;

  }

  public boolean isDirect() {

     return Boolean.valueOf(direct).booleanValue();

  }

  public String toString() {

     return "Content: " + content;

  }

}

Реализация дескрипторов поддержки областей      117

Абстрактный класс Content поддерживает два свойства: content и direct. Свойство content представляет содержимое, воспроизводимое посредством метода render, а свойство direct указывает, как содержимое должно воспроизводиться. Если значение direct равно true, содержимое отображается непосредственно, а если direct принимает значение false, воспроизведение осуществляется посредством включения.

Код класса Section приведен в листинге 4.8,6.

Листинг 4.6,6. /WEB-iNF/clasaes/beans/templates/Section. Java

package beans.regions;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

// A section is content with a name that implements

// Content.render. That method renders content either by including

// it or by printing it directly, depending upon the direct

// value passed to the Section constructor.

//

// Note that a section's content can also be a region;if so,

// Region.render is called from Section.Render().

public class Section extends Content {

  protected final String name;

  public Section(String name, String content, String direct) {

     super(content, direct);

     this.name = name;

  }

  public String getName() {

     return name;    

  }

  public void render(PageContext pageContext)

                                 throws JspException {

     if(content != null) {

        // see if this section's content is a region

        Region region = (Region)pageContext.

                                findAttribute(content);

        if(region != null) {

           // render the content as a region

           RegionStack.push(pageContext, region);

           region.render(pageContext);

           RegionStack.pop(pageContext);

        }

        else {

           if(isDirect()) {

              try {

                 pageContext.getOut().print(content.toString());

              }

              catch(java.io.IOException ex) {

                 throw new JspException(ex.getMessage());

              }

           }

           else {

                try {

                 pageContext.include(content.toString());

              }

              catch(Exception ex) {

                   throw new JspException(ex.getMessage());

              }

           }

        }

     }

  }

  public String toString() {

     return "Section: " + name + ", content= " +

                                    content.toString();

  }

}

118      Глава 4. Шаблоны

Класс Section является подклассом класса Content и реализует метод render. Если значение атрибута direct равно true, для вывода содержимого используется предопределенная переменная out. Если значение direct равно false, класс Section использует для включения содержимого кон текст JSP-документа.

Если содержимое раздела представляет собой область, класс Section помещает эту область в стек и воспроизводит ее (реализация стека показана в листинге 4.8,г). После воспроизведения области класс Section извлекает ее из стека.

Код класса Region приведен в листинге 4.8,в.

ЛИСТИНГ4.8,8. /WEB-INF/classes/beans/templates/Region. Java

package beans.regions;

import java.util.Enumeration;

import java.util.Hashtable;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.JspException;

// A region is content that contains a set of sections.

public class Region extends Content {

  private Hashtable sections = new Hashtable();

  public Region(String content) {

     this(content, null); // content is the name of a template

  }

  public Region(String content, Hashtable hashtable) {

     super(content);

     if(hashtable != null)

        sections = (Hashtable)hashtable.clone();

  }

  public void put(Section section) {

     sections.put(section.getName(), section);

  }

  public Section get(String name) {

     return (Section)sections.get(name);

  }

  public Hashtable getSections() {

     return sections;

  }

  public void render(PageContext pageContext)

                                 throws JspException {

     try {

        pageContext.include(content);

     }

     catch(Exception ex) { // IOException or ServletException

        throw new JspException(ex.getMessage());

     }

  }

  public String toString() {

     String s = "Region: " + content.toString() + "<br/>";

     int indent = 4;

     Enumeration e = sections.elements();

     while(e.hasMoreElements()) {

        Section section = (Section)e.nextElement();

        for(int i=0; i < indent; ++i) {

           s += "&nbsp;";

        }

        s += section.toString() + "<br/>";

     }

     return s;   

  }

}

Подобно классу Section, класс Region является подклассом класса Content и реализует метод render. Дополнительно класс Region поддерживает хэш-таблицу разделов. Для доступа к разделам области могут быть использованы методы Region.get и Region. getSections. Первый из этих методов возвращает раздел по его имени, а второй предоставляет хэш-таблицу разделов.

120     Глава 4. Шаблоны

В области видимости (scope) приложения области (region) содержатся в стеке. Этот стек реализуется с помощью класса RegionStack, код которого представлен в листинге 4.8, г.

Листинг4.8,г. /WEB-INF/classas/beans/templstes/RegionStack. Java

package beans.regions;

import javax.servlet.jsp.PageContext;

import java.util.Stack;

public class RegionStack {

  private RegionStack() { } // no instantiations

  public static Stack getStack(PageContext pc) {

     Stack s = (Stack)pc.getAttribute("region-stack",

                                  PageContext.APPLICATION_SCOPE);

     if(s == null) {

        s = new Stack();

        pc.setAttribute("region-stack", s,

                        PageContext.APPLICATION_SCOPE);

     }

     return s;

  }

  public static Region peek(PageContext pc) {

     return (Region)getStack(pc).peek();

  }

  public static void push(PageContext pc, Region region){

     getStack(pc).push(region);

  }

  public static Region pop(PageContext pc) {

     return (Region)getStack(pc).pop();

  }

}

Области размещаются в стеке для того, чтобы шаблон текущей области не замещал шаблон включающей области. Класс RegionStack предоставляет статические методы для помещения области в стек, извлечения из стека и чтения верхнего элемента стека.

Классы поддержки дескрипторов

Классы поддержки дескрипторов, предназначенных для работы с областями, перечислены в табл, 4.2.

Реализация дескрипторов поддержки областей      121

Таблица 4.2. Пользовательские дескрипторы, предназначенные для поддержки областей (все дескрипторы, приведенные здесь, принадлежат пакету tags. regions)

Компонент

Описание

RegionTag

Базовый класс для RegionDefinitionTag и RenderTag

RenderTag PutTag

Region                               DefinitionTag     Создает область (region) и сохраняет в указанной области видимости (scope)

Воспроизведение области или раздела

Создание раздела и сохранение его п области (region)

122  Глава 4. Шаблоны

Для создания областей предназначены дескрипторы region; render и region: define, классами поддержки которых соответственно являются RenderTag и RegionDef initionTag. Функции по созданию областей инкапсулированы в классе RegionTag, который представляет собой суперкласс RenderTag и RegionDefini-tionTag. Класс RegionTag, в свою очередь, является подклассом TagSupport.

Класс PutTag представляет собой класс поддержки дескриптора region:put. Суперклассом этого класса является BodyTagSupport, т.е. данный дескриптор обрабатывает содержимое.

Код класса RegionTag представлен в листинге 4.9,а.

Листинг4,9,a. /WEB-INF/classes/tags/regions/RegionTag.

package tags.regions;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.TagSupport;

import beans.regions.Content;

import beans.regions.Section;

import beans.regions.Region;

import beans.regions.RegionStack;

public class RenderTag extends RegionTag {

  private String sectionName=null, role=null;

  public void setSection(String s)  { this.sectionName = s; }

  public void setRole(String s)     { this.role = s;        }

  protected boolean renderingRegion() {

     return sectionName == null;

  }

  protected boolean renderingSection() {

     return sectionName != null;

  }

  public int doStartTag() throws JspException {

     HttpServletRequest request = (HttpServletRequest)

                                   pageContext.getRequest();

     if(role != null && !request.isUserInRole(role))

        return SKIP_BODY;

     if(renderingRegion()) {

        if(!findRegionByKey()) {

           createRegionFromTemplate();

        }

        RegionStack.push(pageContext, region);

     }

     return EVAL_BODY_INCLUDE;

  }

  public int doEndTag() throws JspException {

     Region region = RegionStack.peek(pageContext);

     if(region == null)

        throw new JspException("Can't find region");

     if(renderingSection()) {

        Section section = region.get(sectionName);

        if(section == null)

           return EVAL_PAGE; // ignore missing sections

        section.render(pageContext);

     }

     else if(renderingRegion()) {

        try {

           region.render(pageContext);

           RegionStack.pop(pageContext);

        }

        catch(Exception ex) { // IOException or ServletException

             throw new JspException(ex.getMessage());

        }

     }

     return EVAL_PAGE;

  }

  public void release() {

     super.release();

     sectionName = role = null;

  }

}

Реализация дескрипторов поддержки областей  123

Область создается на базе шаблона или другой области, определяемых соответственно с помощью атрибутов template и region. Класс RegionTag предоставляет set-методы для этих атрибутов, реализует методы для создания областей и, кроме того, содержит метод для поиска существующей области по имени. Указанные средства используются подклассами RegionTag.

Код класса RegionDefinitionTag приведен в листинге 4,9,6.

ЛИСТИНГ 4.9,6. /WEB-INF/ classes/ tags/regions /RegionDefinitionTag. Java

package tags.regions;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.TagSupport;

import beans.regions.Content;

import beans.regions.Region;

public class RegionDefinitionTag extends RegionTag {

  private String scope = null;

  public void setScope(String scope) {

     this.scope = scope;

  }

  public int doStartTag() throws JspException {

     if(region != null && template != null)

        throw new JspException("regions can be created from "  +

                               "a template or another region," +

                               "but not both");

     createRegionFromRegion();

     if(region == null)

        createRegionFromTemplate();

     return EVAL_BODY_INCLUDE;

  }

  public int doEndTag() throws JspException {

     pageContext.setAttribute(id, region, getScope());

     return EVAL_PAGE;

  }

  protected int getScope() {

     int constant = PageContext.PAGE_SCOPE;

     scope = (scope == null) ? "page" : scope;

     if("page".equalsIgnoreCase(scope))

        constant = PageContext.PAGE_SCOPE;

     else if("request".equalsIgnoreCase(scope))

        constant = PageContext.REQUEST_SCOPE;

     else if("session".equalsIgnoreCase(scope))

        constant = PageContext.SESSION_SCOPE;

     else if("application".equalsIgnoreCase(scope))

        constant = PageContext.APPLICATION_SCOPE;

     return constant;

  }

  public void release() {

     super.release();

     scope = "page";

  }

}

124      Глава 4. Шаблоны

Класс RegionDefinitionTag является подклассом RegionTag и создает область либо на базе шаблона, либо на базе другой области. Заметьте, что если одновременно заданы атрибуты region и template, то при выполнении метода RegionDefinitionTag. doStartTag генерируется исключение. Это происходит потому, что область не может быть создана и на основе шаблона, и на основе другой области.

Код класса RenderTag приведен в листинге 4.9,в.

ЛИСТИНГ4.9,В. /WEB-INF/classes/tags/ragion3/RenderTag. Java

package tags.regions;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.TagSupport;

import beans.regions.Content;

import beans.regions.Section;

import beans.regions.Region;

import beans.regions.RegionStack;

public class RenderTag extends RegionTag {

  private String sectionName=null, role=null;

  public void setSection(String s)  { this.sectionName = s; }

  public void setRole(String s)     { this.role = s;        }

  protected boolean renderingRegion() {

     return sectionName == null;

  }

  protected boolean renderingSection() {

     return sectionName != null;

  }

  public int doStartTag() throws JspException {

     HttpServletRequest request = (HttpServletRequest)

                                   pageContext.getRequest();

     if(role != null && !request.isUserInRole(role))

        return SKIP_BODY;

     if(renderingRegion()) {

        if(!findRegionByKey()) {

           createRegionFromTemplate();

        }

        RegionStack.push(pageContext, region);

     }

     return EVAL_BODY_INCLUDE;

  }

  public int doEndTag() throws JspException {

     Region region = RegionStack.peek(pageContext);

     if(region == null)

        throw new JspException("Can't find region");

     if(renderingSection()) {

        Section section = region.get(sectionName);

        if(section == null)

           return EVAL_PAGE; // ignore missing sections

        section.render(pageContext);

     }

     else if(renderingRegion()) {

        try {

           region.render(pageContext);

           RegionStack.pop(pageContext);

        }

        catch(Exception ex) { // IOException or ServletException

             throw new JspException(ex.getMessage());

        }

     }

     return EVAL_PAGE;

  }

  public void release() {

     super.release();

     sectionName = role = null;

  }

}

Реализация дескрипторов поддержки областей  125

Класс RenderTag отображает как разделы, так и области. Если задан атрибут section, воспроизводится раздел, в противном случае воспроизводится область. При воспроизведении области метод doStartTag помещает ее в стек, а метод doEndTag извлекает из стека. Особенности реализации стека областей были рассмотрены ранее в этой главе.

Код класса PutTag представлен в листинге 4.9,г.

126  Глава 4. Шаблоны

Листинг 4.9,г. /WEB-INF/classes/tags/regions/PutTag. Java

package tags.regions;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.BodyTagSupport;

import javax.servlet.jsp.tagext.TagSupport;

import beans.regions.Content;

import beans.regions.Section;

public class PutTag extends BodyTagSupport {

  private String section, role, content, direct = null;

  public void setSection(String section){this.section = section;}

  public void setRole   (String role)   {this.role    = role;   }

  public void setDirect (String direct) {this.direct  = direct; }

  public void setContent(String cntnt)  {this.content = cntnt;  }

  

  public String getSection() { return section; }

  public String getRole()    { return role;    }

  public String getContent() { return content; }

  public String getDirect()  { return direct;  }

  public int doAfterBody() throws JspException {

     HttpServletRequest request =

                   (HttpServletRequest)pageContext.getRequest();

     

     if(role != null && !request.isUserInRole(role))

        return EVAL_PAGE;

     RegionTag regionTag = (RegionTag)getAncestor(

                                      "tags.regions.RegionTag");

     if(regionTag == null)

        throw new JspException("No RegionTag ancestor");

     regionTag.put(new Section(section, getActualContent(),

                                        isDirect()));

     return SKIP_BODY;

  }

  public String isDirect() {

     if(hasBody()) return "true";

     else          return direct == null ? "false" : "true";

  }

  public void release() {

     super.release();

     section = content = direct = role = null;

  }

  private String getActualContent() throws JspException {

     String bodyAndContentMismatchError =

        "Please specify template content in this tag's body " +

        "or with the content attribute, but not both.",

            bodyAndDirectMismatchError =

        "If content is specified in the tag body, the " +

        "direct attribute must be true.";

     boolean hasBody = hasBody();

     boolean contentSpecified = (content != null);

     if((hasBody && contentSpecified) ||

        (!hasBody && !contentSpecified))

        throw new JspException(bodyAndContentMismatchError);

     if(hasBody && direct != null &&

        direct.equalsIgnoreCase("false"))

        throw new JspException(bodyAndDirectMismatchError);

     return hasBody ? bodyContent.getString() : content;

  }

  private boolean hasBody() {

     if (bodyContent == null)

        return (false);

     return ! bodyContent.getString().equals("");

  }

  private TagSupport getAncestor(String className)

                                throws JspException {

     Class klass = null; // can’t name variable "class"

     try {

        klass = Class.forName(className);

     }

     catch(ClassNotFoundException ex) {

        throw new JspException(ex.getMessage());

     }

     return (TagSupport)findAncestorWithClass(this, klass);

  }

}

Реализация дескрипторов поддержки областей      127

Класс PutTag создает раздел и сохраняет его в области, созданной посредством дескриптора region .'define или region; render. Класс PutTag является подклассом BodyTagSupport; благодаря этому есть возможность непосредственно задавать содержимое раздела в теле дескриптора. Например, дескриптор region: put может иметь следующий вид:

<region:render  template='/WEB-INF/jsp/templates/hscf.jsp'> <region:put  section=f title'>

The  Fruitstand </region:put>

</region:render>

Если тело дескриптора region:put присутствует, оно предстаиляет собой содержимое, которое должно непосредственно отображаться. Если тело дескриптора задано, класс PutTag проверяет атрибут direct, чтобы убедиться, что его значение равно true.

128      Глава 4. Шаблоны

Резюме

Компоненты, контейнеры и диспетчеры компоновки позволяют следовать при создании приложений модульному подходу, поэтому они поддерживаются многими наборами инструментов для создания оконных систем. В данной главе было показано, как эти типы объектов реализуются в Web-приложениях на базе JSP. Используя разделы, области и шаблоны, вы можете создавать расширяемые приложения, удобные для сопровождения и допускающие повторное использование.

РАЗРАБОТКА WEB-ПРИЛОЖЕНИЙ

В этой главе...

Model 1.

Model 2: архитектура MVC.

Пример использования архитектуры Model 2.

Дескриптор доставки.
                        Успешная регистрация.

Создание новой учетной записи.

Создать HTML-документ нетрудно.  Создать Web-приложсние,  объединяющее средства HTML, JSP и Java, обеспечивающее взаимодействие с базами данных и существующими приложениями, которое отличается гибкостью и удобно в сопровождении, значительно сложнее. В данной главе рассматриваются общие подходы к созданию Web-приложений.

Авторы спецификации JSP обеспечили достаточную гибкость описываемых в ней средств. Вы можете создать Web-приложение на базе JSP различными способами. Основные способы реализации Web-проектов описаны ниже.

Создание документа как набора HTML-дескрипторов и JSP-скриптлетов,

Делегирование функций компонентам bean.

Использование сервлетов, JSP-документов и компонентов bean для реализации

архитектуры "модель-просмотр-контроллер" (MVC — Model-View-Controller).

Первый подход— включение больших объемов Java-кода в JSP-документы — приводит к созданию приложений, которые трудно сопровождать и модифицировать, поэтому следовать ему не рекомендуется.

При делегировании функций компонентам bean Java-код перемещается из JSP-документов в компоненты, поэтому приложения, созданные по такому принципу, становятся более жизнеспособными. Этот подход, известный как архитектура Model 1, был рекомендован в версии 0.91 спецификации JSР. (Скопировать версии 0.91 и 0.92 спецификации JSP можно, обратившись по адресу http: //www. kirkdorf fer.   com/j spspecs.)

Последний подход — объединение сервлетов, JSP и beans в рамках архитектуры MVC — позволяет создавать расширяемые программы, удобные для сопровождения. При этом инкапсулируются функции и уменьшаются побочные эффекты при внесении изменений. Этот подход также был рекомендован в версии 0.91 спецификации JSP и носит название архитектуры Model 2.

В начале данной главы мы кратко рассмотрим архитектуру Model 1, а затем перейдем к подробному обсуждению архитектуры Model 2. В главе 6 Model 2 будет расширена средствами, упрощающими реализацию приложений в рамках данной архитектуры.

132      Глава 5. Разработка Web-приложений

Model 1

Архитектура Model 1, условно показанная на рис. 5.1, включает JSP-документы, компоненты bean и бизнес-объекты.

Броузер

Запрос

JSP-документ

Доступ/ модификаций

Виз не с-объекты

Ответ

bean

Рис. 5.1. Архитектура Model 1: JSP-документ, компоненты bean и бизнес-объекты

Согласно данной архитектуре, запросы передаются JSP-документам, которые взаимодействуют с бизнес-объектами посредством компонентов bean. Такое косвенное обращение уменьшает зависимость JSP-документов от изменений, вносимых в бизнес-объекты. Это важно, поскольку в больших проектах модификация бизнес-объектов производится достаточно часто. Если интерфейс, реализуемый компонентами bean, остается без изменений, изменение бизнес-объектов не влияет HaJSP-документы.

Бизнес-объекты и компоненты bean реализуются разработчиками программного обеспечения, а авторы Web-страниц отвечают за создание JSP-документов. В идеале такое разделение обязанностей должно было бы обеспечить возможность параллельной работы сотрудников различных специальностей, участвующих в выполнении проекта. Однако на практике реализовать одновременное выполнение работ достаточно сложно, поскольку JSP-документы не только представляют содержимое, но и генерируют его, а для этого необходимо создавать Java-к од.

Поскольку обеспечить разделение обязанностей между авторами Web-страниц и разработчиками программного обеспечения в рамках архитектуры Model 1 трудно, данный подход применим только для небольших проектов с участием нескольких разработчиков, каждый из которых владеет Java, JSP, HTML и XML.

В больших проектах приходится применять другой подход. Чаще всего в таких случаях для реализации приложения выбирается архитектура Model 2,

Совет

Преимущества и недостатки Model 1

Архитектура Model 1 уменьшает зависимость JSP-документов от бизнес-объектов, позволяя параллельно вести работы над созданием бизнес-объектов и Web-страниц. Однако в рамках данной архитектуры разработчики программного обеспечения помимо создания бизнес-объектов оказываются вовлеченными в процесс создания JSP-документов. При работе над большими проектами это недопустимо.

Model 2: архитектура MVC      133

Model 2: архитектура MVC

Подобно Model 1, Model 2 позволяет разделить бизнес-объекты и JSP-документы, что очень важно для большинства проектов, где бизнес-объекты претерпевают постоянные изменения. Кроме того, архитектура Mode! 2 (рис. 5.2) отделяет генерацию содержимого от его представления.

Согласно архитектуре Model 2, запросы передаются сервлету, который обращается к бизнес-объектам и создает содержимое. Это содержимое сохраняется в компоненте bean, к которому имеет доступ JSP-документ. Документ представляет содержимое, применяя для этого, как правило, средства HTML.

Возможность разделять генерацию и представление содержимого очень важна, поскольку для генерации данных в большинстве случаев используется Java-код, (Java-код, применяемый для представления, легко заменяется пользовательскими дескрипторами.) Инкапсуляция Java-кода позволяет разработчикам программного обеспечения сосредоточить внимание на сервлетах и бизнес-объектах, в то время как авторы Web-страниц занимаются созданием JSP-документов.

Model 2 представляет собой архитектуру MVC, где бизнес-объекты соответствуют модели (предоставляют данные), сервлет является контроллером {обрабатывает запросы), aJSP-документы играют роль просмотра. Архитектура MVC появилась "на заре" вычислительной техники и была реализована в Smalltalk. Благодаря разделению бизнес-логики и средств представления, что позволяет легко заменять компоненты и обеспечивать создание гибкого программного обеспечения, эта технология выдержала испытание временем.

На самом деле Model 2 представляет собой модифицированную архитектуру MVC, так как модель не генерирует события, предназначенные для обработки просмотром. Такой подход выбран потому, что в Web-приложениях обычно не используется несколько просмотров одновременно.

134      Глава 5. Разработка Web-приложений

Совет Преимущества МУС

Много лет назад MVC стала базой, на которой создавались приложения Smalltalk. Одним из основных принципов объектно-ориентированной разработки является выделение понятий и инкапсуляция их в классах. Например, приложение, предна^ значенное для расчета заработной платы, оперирует с такими понятиями, как сотрудники, оклады и т.д. Инкапсуляция этих понятий в классах позволяет распределять функции между объектами и снижать зависимость объектов друг от друга. Благодаря такому подходу увеличивается гибкость приложений и появляется возможность повторного использования их компонентов. В архитектуре MVC инкапсулируются три основных понятия, присутствующих в большинстве графических приложений: модели, просмотры и контроллеры. По сравнению с другими приложениями, где эти понятия не выделяются, MVC-программы отличаются большей гибкостью.

Пример использования архитектуры Model 2

В данном разделе описывается одна из реализаций знакомого всем Web-приложения, предназначенного для регистрации пользователей. Это приложение соответствует архитектуре Model 2. Оно, как показано на рис. 5.3, состоит из шести JSP-документов, одного пользовательского дескриптора, двух компонентов bean и двух сервлетов— один из сервлетов предназначен для регистрации, а второй— для создания новых учетных записей. Кроме того, в состав приложения входят дескриптор доставки (/WEB-INF/web. xral), который определяет отображение сервлета, и файл описания библиотеки пользовательских дескрипторов (/WEB-INF/t Ids /utilities, tld). В файле описания библиотеки дескрипторов определен единственный пользовательский дескриптор, применяемый в данном приложении.

Компоненты bean

Приложение, предназначенное для регистрации пользователей, содержит два компонента bean. Один из них представляет пользователей, а другой — базу данных учетных записей. Код класса User показан в листинге 5.1,а.

Листинг 5.1 ,а. /WEB-lNF/classes/beans/User. Java

package beans;

// Users are immutable

public class User implements java.io.Serializable {

  private final String userName, password, hint;

  public User(String userName, String password, String hint) {

     this.userName = userName;

     this.password = password;

     this.hint = hint;

  }

  public String getUserName() { return userName; }

  public String getPassword() { return password; }

  public String getHint()     { return hint; }

  public boolean equals(String uname, String pwd) {

     return getUserName().equals(uname) &&

            getPassword().equals(pwd);

  }

}

Пример использования архитектуры Model 2  135

Рис. 5.3. Структура каталогов и файлы, используемые при создании приложения

В классе User предусмотрены три свойства, представляющие имя пользователя, пароль и подсказку для ввода пароля. Значение каждого из них устанавливается при создании объекта. В даЕшом классе также содержатся методы, обеспечивающие доступ к этим свойствам.

Кроме того, в классе User реализован метод equals, который возвращает true в случае, если пароль, указанный при вызове метода, соответствует паролю, заданному при создании объекта.

Информация о пользователе хранится в неизменном виде; значения свойств могут быть установлены только при выполнении конструктора User. Это является естественной защитой при доступе из различных потоков. Если объект не может изменять-

136      Глава 5. Разработка Web-приложений

ся при выполнении программы, автоматически обеспечивается его целостность. Так как при объявлении свойств использован модификатор final, свойства не могут изменяться после инициализации.

Код класса, представляющего учетную запись пользователя, приведен в листинге 5.1,6.

Листинг 5.1 ,б.  /WEB-lKF/classes/beans/LoginDB.java

package beans;

import java.util.*;

public class LoginDB implements java.io.Serializable {

  private Vector users = new Vector();

  public void addUser(String uname, String pwd, String hint) {

     users.add(new User(uname, pwd, hint));

  }

  public User getUser(String uname, String pwd) {

     Iterator it = users.iterator();

     User bean;

     

     synchronized(users) {

        while(it.hasNext()) {

           bean = (User)it.next();

           if(bean.equals(uname, pwd))

              return bean;

        }

     }

     return null;

  }

  public String getHint(String uname) {

     Iterator it = users.iterator();

     User bean;

     synchronized(users) {

        while(it.hasNext()) {

           bean = (User)it.next();

           if(bean.getUserName().equals(uname))

              return bean.getHint();

        }

     }

     return null;

  }

}

Класс LoginDB поддерживает вектор пользователей и предоставляет методы для добавления и извлечения пользователей из набора. Кроме того, в состав данного класса входит метод, который по имени пользователя возвращает подсказку для ввода пароля.

Пример использования архитектуры Model 2      137

Для обеспечения многопотокового выполнения синхронизируются фрагменты кода. Класс Java, u til .Vector синхронизирован, поэтому необходимость в синхронизации метода LoginDB.addUser не возникает. Однако итераторы не синхронизированы, и если данные изменятся при просмотре набора, будет сгенерировано исключение.

Дескриптор доставки

В состав Web-приложения входит дескриптор доставки, который определяет конфигурацию приложения и содержит следующую информацию,

Инициализационные параметры контекста сервлетов.

Конфигурация сеанса.

Определение сер влетов/JSP.

Отображение сервлетов/JSP.

Отображение МШЕ-типов.

Страница приветствия.

Страницы, отображаемые при возникновении ошибок.

Средства защиты.

Дескрипторы доставки хранятся в каталоге /WEB-INF приложения в файле с именем web.xml. Дополнительную информацию о дескрипторах доставки вы найдете в спецификации сервлетов, которую можно скопировать, обратившись по адресу http://java.s-un.com/products/servlet. Дескриптор доставки для рассматриваемого приложения представлен влистинге 5.1,в.

138      Глава 5. Разработка Web-приложений

Дескриптор доставки, представленный в листинге 5.1,в, определяет отображение сервлетов и предоставляет информацию о файле описания библиотеки дескрипторов для приложения. Отображение сервлетов связывает имена сервлетов с относительными URL и классами сервлетов. Например, к сервлетам, применяемым в данном приложении для регистрации и создания учетных записей, можно обращаться по именам, как показано ниже,

<Јorm action='<%=  response.encodeURL("login")   %>'> <form action='<%=  response.encodeURL("new-account")   %>'>

В дескрипторе доставки задан UR1 описания библиотеки дескрипторов. Благодаря этому к описанию можно обращаться следующим образом:

<%@  taglib uri-'utilities'   prefix-'util'   %>

Ниже рассматриваются сценарии развития событий, соответствующие успешной регистрации и созданию новой учетной записи, реализующиеся при работе пользователей с данным приложением.

Совет

Сценарии развития событий

Сценарии развития событий были впервые описаны в книге Ливера Джекобсена (Ivarjacobsen) Object-Oriented Software Engineering и по сути представляют собой формализацию требований к программе. Сценарий развития событий — это набор ситуаций, которые могут возникать при выполнении текущей задачи пользователя, например при регистрации на Web-узле.

Несмотря на то что сценарии развития широко используются при описании требований к программному обеспечению, лучше всего они подходят для разработки Web-приложений, поскольку HTTP-запрос соответствует одному сценарию развития событий.

Успешная регистрация

Сценарий успешной регистрации пользователя предполагает следующие действия. •    Пользователь указывает в поляxJSP-документа имя и пароль.

Пример использования архитектуры Model 2      139

Сервлет регистрации проверяет, включен ли данный пользователь в базу учет
ных записей.

Если в базе данных существует запись для этого пользователя, запрос перена
правляется странице приветствия.

Информация в окне броузера, соответствующая успешной регистрации, показана на рис. 5.4.

Страница, содержащая регистрационную форму, отображается в окне, показанном на рис. 5.4, слева. Когда данные формы передаются ла сервер, вызывается сервлет регистрации. Этот сервлет проверяет, соответствует ли пользовательское имя и пароль данным, содержащимся в базе. При положительном результате проверки сервлет помещает объект User в область видимости сеанса и перенаправляет запрос странице приветствия, которая извлекает имя пользователя и создает приветственное сообщение.

140     Глава 5. Разработка Web-приложений

JSP-код страницы приветствия показам в листинге 5.1,г.

Листинг 5.1,г. /login, j

<html><head><title>Login  Page</title></head> <body>

<l@  include  file='login-form.jsp'   %> </body> </html>

Поскольку регистрационная форма используется двумя другими JSP-документами в данном приложении (листинги 5.1,з и 5.1,л), она реализована в отдельном JSP-файле, который включается в файл login. j sp.

Код, реализующий регистрационную форму, показан в листинге 5.1,д.

Листинг 5.1,д. /login-form.j

<%@ taglib uri='utilities' prefix='util' %>

<font size='5' color='blue'>Please Login</font><hr>

<form action='<%= response.encodeURL("login") %>' method='post'>

  <table>

     <tr>

        <td>Name:</td>

        <td><input type='text' name='userName'

            value='<util:requestParameter property='userName'/>'>

        </td>

     </tr><tr>

        <td>Password:</td>

        <td><input type='password' name='password' size='8'></td>

     </tr>

  </table>

  <br>

  <input type='submit' value='login'>

</form>

Поскольку файл login-form, jsp предназначен для включения в другие файлы, в нем не содержатся такие дескрипторы, как html, head и body; эти дескрипторы находятся в составе включающего JSP-документа.

Для обработки запроса применяется сервлет регистрации; в дескрипторе доставки этому сервлету присвоено имя login. На случай, если работа с cookie запрещена или поддержка сеанса не используется, имя кодируется посредством метода HttpServ-letResponse.encodeURL.

Единственный пользовательский дескриптор применяется в данном приложении для сохранения содержимого поля ввода имени при повторном отображении документа. О реализации подобных дескрипторов см. в главе 1.

При передаче данных формы на сервер запрос направляется сервлету регистрации, код которого показан в листинге 5.1,е.

Пример использования архитектуры Model 2  141

Листинг 5.1 ,е. /WEB-lNF/classes/LoginServlet. java

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

import beans.User;

public class LoginServlet extends HttpServlet {

  private LoginDB loginDB;

  public void init(ServletConfig config)

                             throws ServletException {

     super.init(config);

     config.getServletContext().setAttribute("loginDB",

                                        loginDB = new LoginDB());

  }

  public void service(HttpServletRequest req,

                   HttpServletResponse res)

                   throws java.io.IOException, ServletException {

     User user = loginDB.getUser(req.getParameter("userName"),

                                 req.getParameter("password"));

     if(user != null) { // user is in the login database

        req.getSession().setAttribute("user", user);

        getServletContext().getRequestDispatcher(

           res.encodeURL("/welcome.jsp")).forward(req,res);

     }

     else { // user must open a new account or retry login

        getServletContext().getRequestDispatcher(

           res.encodeURL("/loginFailed.jsp")).forward(req,res);

     }

  }

}

Сервлет регистрации выполняет две задачи: инициализирует базу данных и обрабатывает запросы. Первая задача решается в методе init, который создает базу и сохраняет ее в области видимости приложения.

Обработка запросов осуществляется посредством метода service. При этом сервлет обращается к базе данных и проверяет, содержится ли в ней запись, соответствующая имени пользователя и паролю, введенным в форме. Если запись о пользователе существует, объект User сохраняется в области видимости сеанса и управление передается странице приветствия. Если пользователь не учтен в базе, вызывается страница, соответствующая некорректной регистрации. Код этой страницы показан влистинге 5.1,з.

Несмотря на то что сервлет взаимодействует с базой данных и при этом возможно одновременное обращение из нескольких потоков, в сервлете не осуществляется

142      Глава 5. Разработка Web-приложений

синхронизация фрагментов кода, поскольку средства обеспечения многопотоковой работы предусмотрены в классе LoginDB.

Код страницы приветствия приведен в листинге 5.1,ж.

ЛИСТИНГ 5.1 ,Ж, /welcome.jsp

<html><head><title>Welcome</title></head> <body>

<jsp:useBean id='user'   scope-'session'   с1ass='beans.User1   /> Welcome <%= user.getUsenName()   %>

</body> </html>

Для получения имени пользователя документ обращается к компоненту User, содержащемуся в области видимости сеанса. Результаты показаны на рис. 5.2.

Совет

Архитектура Model 2 позволяет инкапсулировать Java-код в сервлетах

В рамках архитектуры Model 2 Java-код инкапсулируется в сервлетах. В данном приложении практически весь Java-код сосредоточен в сервлетс, код которого по-;; казан в листинге 5.1 ,е. BJSP-файлах, представленных в листингах 5.1,г и 5.1 д Java-код отсутствует. Такой подход позволяет разработчикам программ заниматься только созданием сервлетов, а авторам WeV>crpaHH4 сосредоточить усилия на создании JSP-Документов.

Вы можете возразить, что страница приветствия в листинге 5.1,ж содержит вызов метода, с помощью которого определяется пользовательское имя, и будете правы. Если автор Web-страницы считает недопустимым присутствие даже такого фрагмента кода, разработчик программного обеспечения может заменить его пользовательским дескриптором.

Создание новой учетной записи

Если процедура регистрации заканчивается неудачей, пользователь получает возможность создать новую учетную запись. Сценарий развития, соответствующий созданию новой учетной записи, может быть описан следующим образом.

  1.  Пользователь задает имя и пароль в форме, предоставляем о uJSP-до куме нтом.

Сервлет регистрации проверяет, содержится ли в базе запись для данного
пользователя.

S. Если пользователь не учтен в базе, запрос перенаправляется документу, посредством которого пользователь может повторить попытку регистрации или создать новую учетную запись.

Пример использования архитектуры Model 2      143

4. Если пользователь выбирает создание новой учетной записи, управление пере
дается с оответс твую ще му JS Р-д о куме нту.

5, Пользователь заполняет форму, содержащуюся в новой Web-странице, и пере
дает данные сервлету. Сервлет создает в базе данных новую запись и перена
правляет запрос JSP-документу, с помощью которого пользователь может заре
гистрироваться.

Действия по созданию новой учетной записи показаны на рис. 5.6. Первой отображается Web-страница регистрации (Login Page), находящаяся в левом верхнем углу, последующие страницы {Login Failed, New с

144     Глава 5. Разработка Web-приложений

На рис. 5.7 показана диаграмма событий, соответствующая неудачной попытке регистрации и созданию новой учетной записи.

Подобно сценарию удачной регистрации, после того, как данные, введенные посредством регистрационной формы, передаются на сервер, сервлет проверяет, содержится ли соответствующая запись в базе данных.

Если пользователь не учтен в базе, запрос перенаправляется документу, посредством которого отображается Web-страница Login Failed. На этой странице находится ссылка, которая указывает на JSP-документ, содержащий форму. В качестве атрибута action данной формы указан сервлет, предназначенный для создания новой учетной записи. Этот сервлет получает информацию, введенную в полях формы, создает запись для нового пользователя и добавляет запись в базу данных.

После создания новой учетной записи запрос перенаправляется JSP-документу Account Created.

Код JSP-документа, соответствующего неудачной регистрации, приведен в листинге 5.1.з.

Листинг 5.1,3. /loginPailed. Jep

<html><head><title>Login Failed</title></head>

<body>

<font color='red' size='5'>Login Failed</font>

<font color='red' size='4'><p>

  Please enter a valid username and password, or create

  a new account

</font></p>

<%@ include file='/login-form.jsp' %>

<hr>Click <a href='<%= response.encodeURL("newAccount.jsp") %>' >

here</a> to open a new account.

</body>

</html>

Пример использования архитектуры Model 2      145

Данная Web-страница не содержит ничего примечательного. В ней отображаются сообщение об ошибке, регистрационная форма и ссылка на документ, предназначенный для создания новой учетной записи.

Код JSP-документа New Account, посредством которого создается новая учетная запись, приведен в листинге 5.1,и.

Листинг5.1,И. /n«wAcoount. jep

<html><head><title>New Account</title></head>

<body>

<font size='5' color='blue'>Open a New Account</font>

<hr>

<form action='<%= response.encodeURL("new-account") %>'

     method='post'>

  <table><tr>

        <td> User Name: </td>

        <td><input type='text' name='userName'></td>

     </tr><tr>

        <td> Password: </td>

        <td><input type='password' name='password' size='8'></td>

     </tr><tr>

        <td> Confirm Password: </td>

        <td><input type='password' name='confirm-password'

                   size='8'></td>

     </tr><tr>

        <td> Password Hint: </td>

        <td><input type='text' name='password-hint'>

        </td>

     </tr>

  </table>

  <br>

  <input type='submit' value='create account'>

</form>

</body>

</html>

Документ New Account содержит форму с полями для ввода имени пользователя, пароля и подсказки. Атрибут action данной формы имеет значение new-account — имя, присвоенное в дескрипторе доставки сервлету, который предназначен для создания новой учетной записи.

Код сервлета, создающего учетную запись, приведен в листинге 5.1 ,к.

146      Глава 5. Разработка Web-приложений

Листинг 5.1 ,К. /WEB-IHF/classes/NewAccountServlet. Java

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

public class NewAccountServlet extends HttpServlet {

  public void doPost(HttpServletRequest req,

                 HttpServletResponse res)

                   throws java.io.IOException, ServletException {

     LoginDB loginDB = (LoginDB)getServletContext().

                        getAttribute("loginDB");

     loginDB.addUser(req.getParameter("userName"),

                     req.getParameter("password"),

                     req.getParameter("password-hint"));

     getServletContext().getRequestDispatcher(

        res.encodeURL("/accountCreated.jsp")).forward(req, res);

  }

}

Данный сервлет имеет доступ к базе данных и информации, введенной пользователем в форме. Этот сервлет создает в базе данных новую запись, используя для этого метод LoginDB, addUser, а затем перенаправляет запрос документу Account Created.

В реальном приложении при создании учетной записи следует проверять, совпадают ли последовательности символов, заданные пользователем в поле пароля и в поле подтверждения пароля, но в данном случае такая проверка не выполняется.

Код^Р-документа Account Created приведен в листинге 5.1,л.

Листинг 5.1 ,Л. /accountcreated.jsp

<html><head><title>Account Created</title></head>

<body>

<font size='4' color='blue'>

  An account has been created for

  <%= request.getParameter("userName") %>

</font>

<p><%@ include file='login-form.jsp' %></p>

</body>

</html>

Документ Account Created отображает сообщение о том, что новая учетная запись создана, а также форму для регистрации пользователя.

Пример использования архитектуры Model 2      147

Резюме

Реализовать Web-приложение на базе JSP можно различными способами. В данной главе описаны два подхода, соответствующие архитектуре Model 1 и архитектуре Model 2. Архитектура Model 2 имеет ряд преимуществ перед Modei 1 и позволяет создавать гибкие, расширяемые приложения, удобные для сопровождения.

В следующей главе будут рассмотрены средства, упрощающие создание приложений, соответствующих архитектуре Model 2.

БАЗОВЫЙ НАБОР КЛАССОВ ДЛЯ

СОЗДАНИЯ

ПРИЛОЖЕНИЙ

MODEL 2

В этой главе...

• Базовый набор классов Model 2.

Интерфейс Action.

Фабрика действий.

Маршрутизатор действий.

Сервлет действий.

Модернизация программ.

Учет новых сценариев развития.

Применение пользовательских дескрипторов.

JSP-сценарии.

Model 2 представляет собой реализацию архитектуры "модель-просмотр-контроллер", позволяет обеспечить независимую работу авторов Web-страниц и разработчиков программного обеспечения, поэтому очень хорошо подходит для создания Web-приложений. Однако можно пойти дальше и инкапсулировать часто используемый код в базовом наборе классов, что еще больше упростит работу над приложениями.

В данной главе описан простой набор классов, соответствующий Model 2. При разработке данного набора использовались те же базовые понятия, что и в Apache Struts (информацию о продукте Struts можно получить на узле http: //www. apache ,org).

В данной главе мы вернемся к приложению, рассмотренному в главе 5, и модифицируем его с помощью базового набора классов.

Базовый набор классов Model 2

В базовом наборе классов, рассматриваемом в данной главе, используется единственный сервлет, который исполняет роль контроллера. Этот сервлет называется сервлетом действий (action servlet). Все HTTP-запросы, оканчивающиеся на .do, обрабатываются сервлетом действий. Как показано на рис. 6.1, сервлет действий передает эти запросы компонентам bean, которые называются действиями (action).

Компоненты-действия обновляют бизнес-объекты и возвращают сервлету действий маршрутизатор действий {action router). Сервлет действий использует маршрутизатор для перенаправления запросов JSP-документу. JSP-документ также обращается к бизнес-объектам (часто такие обращения осуществляются с помощью пользовательских дескрипторов) и возвращает броузеру ответ на запрос.

150      Глава 6. Базовый набор классов для создания приложений Model 2

1. Запрос

Сервлет действий

2. Передача запроса

С~~ ~~1й

Броузер

 ■ ►

(контроллер)

Бизнес-объекта

L

(модель)

5. Перенаправление

{ Компон

г

"хЗ. Обновление

ентЛ                  ^

(toward или send Redirect)

"V действие   )

4. Возвр

ащ

аетсямаршрутизатор

ействий

г

IX

7. Ответ

JSP-документ

просмотр

Доступ

Реализуется разработчиками программ

Реализуется авторами Web-страниц

Рис. 6.1. Базовый набор классов Model 2

В базовом наборе классов (см. рис. 6.1) определены четыре типа объектов, перечисленных в табл. 6.1.

Таблица 6.1. Классы и интерфейс, входящие в состав базового набора (интерфейс выделен курсивом)

Имя

Описание

Action

ActionFactory ActionServlet ActionRouter

Данный интерфейс реализуется действиями, специфическими для приложения

Создание экземпляров действий Отображение запросов в действия Перенаправлениезапросов^Р-документам

На рис. 6.2 показано взаимодействие объектов этих типов.

Сервлет действия обычно вызывается из JSP-документа или другого сервлета. Этот сервлет обращается к фабрике действий и в зависимости от запроса получает соответствующий тип действия.

Обычно Web-приложение поддерживает множественные запросы, поэтому фабрика действий хранит по одному экземпляру действий каждого типа. Один экземпляр действия можете применяться для обработки нескольких запросов, благодаря этому число экземпляров действий, создаваемых приложением, существенно уменьшается.

Следует помнить, что один экземпляр действия может быть использован несколькими потоками, поэтому при создании действия необходимо принимать меры для обеспечения многопотоковой обработки. Проще всего сделать это, отказавшись от поддержки внутреннего состояния объекта, т.е. применяя локальные переменные вместо переменных экземпляра и переменных класса. К локальным переменным может обращаться только один поток.

Базовый набор классов Model 2      151

После того как сервлет действий получает действие, он вызывает метод perform этого действия. Метод perform реализует функциональные возможности, специфические для конкретного приложения (обычно он обращается к бизнес-объектам). Метод Act ion. perform возвращает маршрутизатор действий, который содержит URI и переменную типа boolean, указывающую, какой метод должен использоваться для пере неправления по данному URL forward или sendRedirect.

Получив маршрутизатор, сервлет действий вызывает его метод route, который перенаправляет запрос указанному Web-компоненту. Обычно этим Web-компонентом является JSP-документ, HTML-страница или другой сервлет. Как правило, в составе Web-компонента находится форма или ссылка, при активизации которой формируется за-лрос. При получении запроса последовательность его обработки начинается сначала.

Далее в этом разделе мы рассмотрим классы, перечисленные в табл. 6.1, а затем обсудим возможность применения базового набора для создания приложения из главы 5.

Интерфейс Action

Интерфейс Act ion приведен в листинге 6.1 ,а.

Листинг 6,1,a. /WEB-INF/classes/actions/Action. java

package actions;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public interface Action {

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

           throws java.io.IOException,

                  javax.servlet.ServletException;

}

// Данный интерфейс реализуется действиями, //  специфическими для приложения

152     Глава 6. Базовый набор классов для создания приложений Model 2

В интерфейсе Action объявлен единственный метод perform, которому при вызове передаются ссылка на сервлет действий, а также объекты, представляющий HTTP-запрос и ответ.

Фабрика действий

Код класса ActionFactory представлен в листинге 6.1,6.

Листинг 6.1,6. /WEB-INF/classes/actions/ActionFactory. Java

package actions;

import java.util.Hashtable;

public class ActionFactory {

  private Hashtable actions = new Hashtable();

  public Action getAction(String classname, ClassLoader loader)

                                throws ClassNotFoundException,

                                       IllegalAccessException,

                                       InstantiationException {

     Action action = (Action)actions.get(classname);

     if(action == null) {

        Class klass = loader.loadClass(classname);

        action = (Action)klass.newInstance();

        actions.put(classname, action);

     }

     return action;

  }

}

Фабрика действий содержит хэш-таблицу действий и метод getAction, который вызывается сервлетом действий. Этот метод возвращает действие, соответствующее заданному имени класса. Если действие отсутствует в хэш-таблице, фабрика создает новое действие и помещает его в таблицу. При последующих запросах, предполагающих использование того же действия, требуемый объект извлекается из хэш-таблицы.

Базовый набор классов Model 2      153

Маршрутизатор действий

Код класса Act ionRouter показан в листинге 6.1,в.

ЛИСТИНГ6.1,В. /WSB-INF/clasaes/actions/ActionRouter.J

package actions;  import java.util.ResourceBundle;  import javax.servlet.GenericServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse; // Action routers are immutable

public class ActionRouter {

  private final String  key;

  private final boolean isForward;

  public ActionRouter(String key) {

     this(key, true); // forward by default   }

  public ActionRouter(String key, boolean isForward) {

     this.key = key;       this.isForward = isForward;    }   // This method is called by the action servlet

  public synchronized void route(GenericServlet servlet,

                                 HttpServletRequest req,

                                 HttpServletResponse res)

                            throws java.io.IOException,

                                   javax.servlet.ServletException {

     ResourceBundle bundle = (ResourceBundle)servlet.

                                getServletContext().

                                getAttribute("action-mappings");

     String url = (String)bundle.getObject(key);       if(isForward) {

        servlet.getServletContext().getRequestDispatcher(

        res.encodeURL(url)).forward(req, res);      }      else {

        res.sendRedirect(res.encodeRedirectURL(url));      }   }}

Маршрутизатор действий перенаправляет запрос, используя для этого либо метод forward, либо метод sendRedirect. Конкретный способ обработки запроса определяется при выполнении конструктора класса.

Сервлет действий

Дескриптор доставки отображает URL, заканчивающиеся символами ".do", в сервлет действий.

154      Глава 6. Базовый набор классов для создания приложений Model 2

// Фрагмент  /WEB-INF/web.xml

<web-app>

  <servlet>

     <servlet-name>action</servlet-name>

     <servlet-class>ActionServlet</servlet-class>

 <init-param>

  <param-name>action-mappings</param-name>

  <param-value>actions</param-value>

 </init-param>

  </servlet>

  <servlet-mapping>

     <servlet-name>action</servlet-name>

     <url-pattern>*.do</url-pattern>

  </servlet-mapping>

 <taglib>

   <taglib-uri>utilities</taglib-uri>

   <taglib-location>/WEB-INF/tlds/utilities.tld</taglib-location>

 </taglib>

 <taglib>

   <taglib-uri>application-tags</taglib-uri>

   <taglib-location>/WEB-INF/tlds/app.tld</taglib-location>

 </taglib>

</web-app

Суффикс ". do" запомнить просто, поскольку он ассоциируется с действиями. Перед суффиксом ".do" указывается имя действия. Например, для действия регистрации, которое реализуется посредством класса с именем LoginAction из пакета actions, URL задается следующим образом:

<form action='<%= response.encodeURM"actions-LoginAction.do") %>' method='post'>

Рассмотрим, как сервлет действий, представленный в листинге 6,1,г, отображает URL в класс действия.

Листинг 6.1,г. /WEB-INF/classes/ActionServlet. Java

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import actions.Action; import actions.ActionFactory; import actions.ActionRouter;

public class ActionServlet extends HttpServlet {

private ActionFactory factory = new ActionFactory ();

public void service(HttpServletRequest req,

HttpServletResponse res)

throws Java.io.IOException,

javax.servlet.ServletException { try (

Action action = factory.getAction(getClassname(req),

getClass().getClassLoader());

ActionRouter router - action.perform(this,req,res); router.route(this, req, res); } catch(Exception e) {

throw new ServletException(e);

Базовый набор классов Model 2  155

private String getСlassname(HttpServletRequest req) ( String path = req.getServletFath (); int slash = path.lastlndexOf("/"), period = path.lastlndexOf(".");

if[period > 0 &s period > slash) path = path.substring(slash+1, period);

return path;

Реализация метода ActionServlet.service не составляет труда. Для получения действия сервлет обращается к фабрике действий и вызывает метод perform действия. Метод perform возвращает маршрутизатор действий, который перенаправляет запрос.

Метод ActionServlet .getClassname отвечает за отображение URL в имя класса действия. Этот метод получает ссылку на путь к сералету и выделяет строку между косой чертой и точкой. Для URL действия регистрации, о котором шла речь выше, путь к серв-лету имеет вид /actions. LoginAction. do, а имя класса — actions. LoginAction.

Модификация приложения, предназначенного для регистрации

В предыдущей главе мы рассмотрели пример приложения, предназначенного для регистрации пользователей. Это приложение было создано в соответствии с архитектурой Mode] 2. Сейчас мы модифицируем данный пример и используем в нем базовый набор классов. На рис. 6.3 показана структура каталогов и файлы модифицированного примера. Та же информация для исходного примера была приведена на рис. 5.3.

Рис. е.З. Структура каталогов и файлы, использованные при создании приложения

156      Глава 6. Базовый набор классов для создания приложений Model 2

Если сервлет действий находится в каталоге /WEB-INF/classes, а классы, поддерживающие действия,— в каталоге /WEB-INF/classes/actions/, то в исходное приложение надо внести лишь два изменения. Во-первых, в файлах login, jsp и newAccount. j sp надо изменить URI сервлетов. В исходных JSP-файлах строки, содержащие URI, выглядят следующим образом:

<%--  Фрагмент исходного файла   login.jsp  --%>

<form action='<%=  response.encodeURL("login")   %>'   method=fpost'>

<%--  Фрагмент исходного файла newAccount.jsp --%> <form action='<%=  response.encodeURL("new-account")   %>' method^'post'>

Модифицированные URI имеют вид

<%— Фрагмент  нового файла  login.jsp —%> <form action^'<%= response.encodeURL("actions.LoginAction.do")   %>' method='post'>

<%— Фрагмент нового файла newAccount.jsp —%>

<form

action='<%= response.encodeURL("actions.NewAccountAction.do") %>'

method^'post' >

Во-вторых, сервлеты, предназначенные для регистрации и создания новой учетной записи, должны быть переписаны как действия. Эти действия представлены в листингах б.2,а и 6.2,6.

ЛИСТИНГ 6.2,a. /WEB-INF/classes/actions/LoginAction.Java

package actions;

import javax.servlet.ServletContext;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

import beans.User;

public class LoginAction implements Action {

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                         throws java.io.IOException,

                              javax.servlet.ServletException {

     LoginDB loginDB = getLoginDB(servlet.getServletContext());

     User user = loginDB.getUser(req.getParameter("userName"),

                                 req.getParameter("password"));

     if(user != null) { // user is in the login database

        req.getSession().setAttribute("user", user);

        return new ActionRouter("welcome-page");

     }

     else { // store userName request parameter in session

        req.getSession().setAttribute("userName",

                         req.getParameter("userName"));

        return new ActionRouter("login-failed-page");

     }

  }

  private LoginDB getLoginDB(ServletContext context) {

     LoginDB loginDB = (LoginDB)context.getAttribute("loginDB");

     if(loginDB == null)

        context.setAttribute("loginDB", loginDB = new LoginDB());

     return loginDB;

  }

}

Листинг 6.2,6. /WEB-INF/classes/actions/NewAecountAction . Java

package actions;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

import beans.User;

public class NewAccountAction implements Action {

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                          throws java.io.IOException,

                                 javax.servlet.ServletException {

     LoginDB loginDB = (LoginDB)servlet.getServletContext().

                        getAttribute("loginDB");

     String uname = req.getParameter("userName");

     loginDB.addUser(uname, req.getParameter("password"),

                            req.getParameter("password-hint"));

     req.setAttribute("userName", uname);

     return new ActionRouter("account-created-page");

  }

}

Оба действия выполняют те же функции, что и соответствующие сервлеты, но, в отличие от сервлетов, они возвращают маршрутизаторы действий.

Модернизация программ

Как и любой программный проект, рассмотренный пример оставляет разработчику возможность для модернизации. Так, например, в JSP-докумеитах используется непосредственное обращение к действиям, это иллюстрирует дескриптор form, находящийся в файле login-form.jsp:

<form action='<%=  response.encodeURL("actions.LoginAction.do")   %>' method='post' >

158      Глава 6. Базовый набор классов для создания приложений Model 2

Действия, в свою очередь, как это видно из следующего фрагмента файла Login-Action . j ava, непосредственно обращаются JSP-документам:

if(us                                       er   != null)    {   II  информация о пользователе //   содержится  в базе данных

гeq.getSession().setAttribute("user",   user);

return new ActionRouter("/welcome.jsp"); ) else

return new ActionRouter("/loginFailed.jsp");

Такая взаимосвязь между JSP-документами и действиями нежелательна, поскольку изменение имени JSP-файла приводит к изменениям в составе действия, а изменение имени файла, реализующего действие, или даже перемещение его в другой пакет приводит к изменениям в JSP-до кум енте.

В идеале, доступ к JSP-до куме нтам и действиям должен осуществляться с использованием логических имен. Например, обращение в составе JSP-документа может выглядеть так:

<form action='<%= response.encodeURL("login-action")   %>' method='post'>

Пример использования логических имен в действии выглядит следующим образом:

if[user   ! =   null)    {   //   информация  о  пользователе

//   содержится   в  Сазе  данных

req.getSession[).setAttribute("user",   user); return new ActionRouter("welcome-page");

} else

return new ActionRouter("login-failed-page");

Для отображения логических имен в классы действий и JSP-документы могут использоваться наборы ресурсов (механизм наборов ресурсов будет рассматриваться в главе 8). Для этого надо в первую очередь реализовать файл свойств, показанный в листинге 6.3,а, и поместить его в каталог /WEB-INF/classes.

ЛИСТИНГ 6.3,a. /WEB-INF/classes/actions .properties

# Отображение действий,   используемое  ActionServlet

login-action=actions.LoginAction new-account-action=actions.NewAccountAction

# Отображение  JSP,   используемое маршрутизаторами

login-failed-page^/loginFailed.jsp welcome-page=/welcome.j sp account-created-page=/accountCreated.jsp

Затем надо добавить к сервлету действий метод init, в теле которого создается набор ресурсов, определенный в файле свойств. Набор ресурсов сохраняется в области видимости приложения и доступен как сервлету действий, так и маршрутизатору действий. Код сервлета действий показан в листинге 6.3,6.

Модернизация программ  159

ЛИСТИНГ G.3,6. /WEB-INF/classes/ActionServlet. Java

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

import actions.*;

public class ActionServlet extends HttpServlet {

  private ActionFactory factory = new ActionFactory();

  public void init(ServletConfig config) throws ServletException{

     super.init(config);

     ResourceBundle bundle = null;

     try {

        bundle = ResourceBundle.getBundle(

                 config.getInitParameter("action-mappings"));

     }

     catch(MissingResourceException e) {

        throw new ServletException(e);

     }

     getServletContext().setAttribute("action-mappings", bundle);

  }

  public void service(HttpServletRequest req,

                      HttpServletResponse res)

                   throws java.io.IOException, ServletException {

     try {

        String actionClass = getActionClass(req);

        Action action = factory.getAction(actionClass,

                                getClass().getClassLoader());

        ActionRouter router = action.perform(this,req,res);

        router.route(this, req, res);

     }

     catch(Exception e) {

        throw new ServletException(e);

     }

  }

  private String getClassname(HttpServletRequest req) {

     String path = req.getServletPath();

     int slash = path.lastIndexOf("/"),

         period = path.lastIndexOf(".");

     if(period > 0 && period > slash)

      path = path.substring(slash+1, period);

     return path;

  }

  private String getActionClass(HttpServletRequest req) {

     ResourceBundle bundle = (ResourceBundle)getServletContext().

                             getAttribute("action-mappings");

     return (String)bundle.getObject(getActionKey(req));

  }

  private String getActionKey(HttpServletRequest req) {

     String path = req.getServletPath();

     int slash = path.lastIndexOf("/"),

         period = path.lastIndexOf(".");

     if(period > 0 && period > slash)

      path = path.substring(slash+1, period);

     return path;

  }

}

160      Глава 6. Базовый набор классов для создания приложений Model 2

Метод service сервлета действий получает имя класса посредством метода getActionClass, который использует набор ресурсов для отображения логических имей в имена действий.

Класс ActionRouter, код которого приведен в листинге 6.3,в, также модифицирован с тем, чтобы выполнять отображение логических имен в JSP-документы.

ЛИСТИНГ6.3,В. /WEB-INF/classes/actions/ActionRouter.Java

package actions;

import java.util.ResourceBundle;

import javax.servlet.GenericServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

// Action routers are immutable

public class ActionRouter {

  private final String  key;

  private final boolean isForward;

  public ActionRouter(String key) {

     this(key, true); // forward by default

  }

  public ActionRouter(String key, boolean isForward) {

     this.key = key;

     this.isForward = isForward;

  }

  // This method is called by the action servlet

  public synchronized void route(GenericServlet servlet,

                                 HttpServletRequest req,

                                 HttpServletResponse res)

                            throws java.io.IOException,

                                   javax.servlet.ServletException {

     ResourceBundle bundle = (ResourceBundle)servlet.

                                getServletContext().

                                getAttribute("action-mappings");

     String url = (String)bundle.getObject(key);

     if(isForward) {

        servlet.getServletContext().getRequestDispatcher(

        res.encodeURL(url)).forward(req, res);

     }

     else {

        res.sendRedirect(res.encodeRedirectURL(url));

     }

  }

}

Модернизация программ      161

Если сервлет действий и маршрутизаторы действий выполняют отображение логических имен, действия и JSP-документы могут использовать эти имена. Например, в листингах 6.3,г и б.З.д представлены соответственно коды класса NewAccountAction

HjSP-документа login-form, j sp.

Листинг 6.3,г. /WEB-lNF/classes/actions/NewAccountAction. Java

package actions;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

import beans.User;

public class NewAccountAction implements Action {

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                          throws java.io.IOException,

                                 javax.servlet.ServletException {

     LoginDB loginDB = (LoginDB)servlet.getServletContext().

                        getAttribute("loginDB");

     String uname = req.getParameter("userName");

     loginDB.addUser(uname, req.getParameter("password"),

                            req.getParameter("password-hint"));

     req.setAttribute("userName", uname);

     return new ActionRouter("account-created-page");

  }

}

162      Глава 6. Базовый набор классов для создания приложений Model 2

В коды приложения можно внести и другие изменения, позволяющие упростить его сопровождение. Так, например, в сервлетс действий, показанном в листинге в.3,6, явно задано имя файла свойств. Если имя файла изменится, разработчикам придется учитывать эти изменения в исходном коде сервлета и перекомпилировать его. Данную зависимость можно устранить за счет инициализационных параметров сервлета, которые указываются в дескрипторе доставки,

<web-app>

<servlet>

<servlet-name>action</servlet-name> <servlet-class>ActionServlet</servlet-class> <init-param>

<param-naiue>action-mappings</param-name> <param-value>actions</param-value> < / init-param> </servlet>

</web-app>

Для использования инициализационных параметров соответствующий фрагмент кода сервлета действий должен выглядеть так:

public class ActionServlet extends HttpServlet {

public void init (ServletConfig config) throws ServletException( super.init(config) ;

ResourceBundle bundle = null;

try {

bundle = ResourceBundle.getBundle [

config.getlnitParameter("action-mappings")); ) catch(MissingResourceException e) (

throw new ServletException(e);

Инициализационные параметры устраняют зависимость между файлом свойств и сервлетом действий, но создают зависимость между файлом свойств и дескриптором доставки. Поскольку изменения дескриптора доставки не предполагают изменения кода н перс-компиляцию, последняя зависимость предпочтительнее первой.

Для отображения логических имен также могут использоваться XML-файлы. Такие файлы часто применяются для определения конфигурации приложений, однако в данном случае мы отдаем предпочтение файлу свойств, поскольку он реализуется проще.

Учет новых сценариев развития

Качество базового набора класса можно оценить по тому, насколько просто могут быть модифицированы приложения, созданные на его основе, либо по тому, какие усилия надо приложить, чтобы расширить функциональные возможности приложе-

Учет новых сценариев развития      163

ния. Для набора классов, рассматриваемого в этой главе, учет нового сценария развития событий предполагает следующие меры.

  1.  Реализация действия, которое обращается к бизнес-объектам (модель) и, воз
    можно, сохраняет компоненты bean в соответствующей области видимости для
    последующего отображения в JSP-документе (просмотр).   Эту задачу решают
    разработчики программного обеспечения.

Реализация JSP-документа, обращающегося к бизнес-объектам {которые мог
ли быть модифицированы на предыдущем этане) и к компонентам bean, сохра
ненным в некоторой области видимости (компоненты также создаются на пре
дыдущем этапе). Эту работу выполняют авторы Web-страниц.

Связывание с логическими именами действий и JSP, созданных на этапе 1 и 2.

Данную задачу решают авторы Web-страниц и разработчики  программного обеспечения.

Обратите внимание на разделение обязанностей по разработке действий и JSP-документов между программистами и Web-дизайнерами.

Для некоторых сценариев развития событий могут потребоваться дополнительные меры, например, модификация существующих действий (разработчики программ), JSP-документов (авторы Web-страниц) или реализация пользовательских дескрипторов (разработчики программ). Однако во многих случаях достаточно выполнить действия, перечисленные выше.

В этом разделе мы модернизируем Web-приложение, которое создали в главе 5 и доработали в начале данной главы. Соответствующий сценарий развития событий может быть описан следующим образом.

  1.  Попытка регистрации оказывается неудачной; запись о пользователе присутст
    вует в базе, но пароль задан неправильно.

Сервлет действий перенаправляет запрос странице, соответствующей неудач
ной регистрации; на этой странице содержится ссылка на подсказку для ввода
пароля.

Пользователь активизирует ссылку, в результате чего посредством сервлста
действий запрос передается действию, связанному с подсказкой.

Действие извлекает из базы данных учетных записей подсказку для конкретно
го пользователя и сохраняет имя пользователя и подсказку в области видимо
сти запроса.

Сервлет действий передает управление JSP-документу, который отображает
имя пользователя и подсказку, сохраненные в области видимости запроса. В со
став этого документа входит форма, посредством которой пользователь может
повторить попытку регистрации.

На рис. 6.4 показаны два JSP-документа; один из них соответствует неудачной попытке регистрации, а второй отображает подсказку.

164     Глава 6. Базовый набор классов для создания приложений Model 2

Рассмотрим действия, которые необходимо выполнить для того, чтобы учесть данный сценарий развития событий в приложении.

Этап 1: реализация действия, связанного с подсказкой

Код действия ShowHintAction показан в листинге 6.4,а.

Листинг 6.4,3. /HEB-INF/classes/actiona/ShowHintAotion. Java

package actions;

import javax.servlet.ServletContext; import javax.servlet.http.HttpSecvlet; import javax.servlet.http.HttpServletRequest; import javax,servlet.http.HttpServletResponse;

import beans.LoginDB;

public class ShowHintAction implements Action { public ActionRouter perform(HttpServlet servlet,

HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {

LoginDB loginDB = getLoginDB(servlet.getServletContext String  шпате = (String)req.getSession().

getAttribute("userName");

req.setAttribute("hint", loginDB.getHint(uname)}; req.setAttribute("userName", uname);

return new ActionRouter("show-hint-page");

Учет новых сценариев развития      165

private LoginDB getLoginDB(ServletContext context) (

LoginDB loginDB = (LoginDB)context.getAttribute("loginDB");

iffloginDB = null)

context.setAttribute("loginDB", loginDB = new LoginDBO);

return loginDB;

Действие, представленное в листинге 6.4,а, реализует п. 4 описанного выше сценария развития событий, т.е. сохраняет имя пользователя и пароль в области видимости запроса. Конструктору маршрутизатора действия, возвращаемого методом perform, передается логическое имя show-hint-page. Это логическое имя соответствует JSP-до куме нту подсказки и связано с ним в файле свойств приложения.

Этап 2: реализация JSP-документа, соответствующего подсказке

Код JSP-документа, соответствующего подсказке, приведен в листинге 6.4,6,

Листинг 6.4,6. /showHintAotion. jsp

<html><title>Password Hint</title>

<body>

Hint for <b><%= request.getAttribute("userName") %></b> is

<i><%= request.getAttribute("hint") %></i>

<p><font size='5' color='blue'>Please Login</font></p><hr>

<form action='<%= response.encodeURL("login-action.do") %>'

 method='post'>

  <table>

     <tr>

        <td>Name:</td>

        <td><input type='text' name='userName'

            value='<%= request.getAttribute("userName") %>'>

        </td>

     </tr><tr>

        <td>Password:</td>

        <td><input type='password' name='password' size='8'></td>

     </tr>

  </table>

  <br>

  <input type='submit' value='login'>

</form>

</body>

</html>

166      Глава 6. Базовый набор классов для создания приложений Model 2

JSP-документ, показанный в листинге 6.4,6, реализует п. 5 сценария развития событий, извлекая и отображая имя пользователя и подсказку.

С действием, соответствующим регистрации, связано логическое имя login-action. Оно, как и другие логические имена, задается в файле свойств приложения.

Этап 3: модификация файла свойств

Содержимое файла свойств приложения показано в листинге 6.4,в.

ЛИСТИНГ 6.4,В. /WEB-INF/classes/actions.properties

# Action mappings used by ActionServlet

login-action=actions.LoginAction

new-account-action=actions.NewAccountAction

show-hint-action=actions.ShowHintAction

# JSP mappings used by Routers

login-failed-page=/loginFailed.jsp

welcome-page=/welcome.jsp

account-created-page=/accountCreated.jsp

show-hint-page=/showHint.jsp

Этап 4: модификация документа, соответствующего неудачной регистрации

Модифицированный вариант документа, который отображается при неудачной попытке регистрации пользователя, показан в листинге 6.4,г. Ссылка, включаемая в состав документа, зависит от того, задана ли подсказка для ввод пароля.

Листинг 6.4,Г. /loginFaiXed. Jap

<html><head><title>Login Failed</title></head>

<%@ taglib uri='application-tags' prefix='app' %>

<body>

<font color='red' size='5'>Login Failed</font>

<font color='red' size='4'><p>

  Please enter a valid username and password, or create

  a new account

</font></p>

<%@ include file='/login-form.jsp' %>

<app:hintAvailable>

  Click <a href='<%=response.encodeURL("show-hint-action.do")%>'>

  here</a> to see your password hint.

</app:hintAvailable>

<app:hintNotAvailable>

  Click <a href='<%=response.encodeURL("newAccount.jsp")%>'>

  here</a> to open a new account.

</app:hintNotAvailable>

</body>

</html>

Применение пользовательских дескрипторов      167

Данный документ обращается к базе данных и определяет, существует ли для данного пользователя подсказка для ввода пароля. При наличии подсказки включается ссылка на документ, соответствующий подсказке. Если подсказка отсутствует, включается ссылка, которая указывает на документ, предназначенный для создания новой учетной записи.

Применение пользовательских дескрипторов

Одно из основных преимуществ архитектуры Model 2 состоит в том, что Java-код инкапсулируется в сервлетах. Это важно при работе над большими программными проектами; при этом Web-диэаннеры работают над кодом HTML- и JSP-документов, а разработчики программ обеспечивают функциональные возможности, используемые при создании Wcb-страииц, Если JSF-докумеиты свободны от Java-к ода, авторы Web-страниц и программисты могут параллельно выполнять поставленные перед ними задачи; их зависимость друг от друга минимальна.

Однако, даже при использовании архитектуры Model 2, небольшие фрагменты Java-кода часто "просачиваются" в JSP-докумснты. Например, страница, соответствующая неудачной попытке регистрации, код которой показан в листинге 6.4,г, содержит следующий фрагмент:

Java-код в JSP-документе можно заменить пользовательскими дескрипторами, специфическими для конкретного приложения. Так, в приведенном ниже примере содержатся два пользовательских дескриптора.

168      Глава 6. Базовый набор классов для создания приложений Model 2

Если подсказка доступна, в сгенерированный HTML-документ включается тело дескриптора hint Available, в противном случае — тело дескриптора hintNotAvailable.

Большинство пользовательских дескрипторов, например, дескрипторы, приведенные выше, реализуются очень просто. В листинге 6.5,а показан класс поддержки дескриптора hintAvailable, а в листинге 6.5,6 — класс поддержки hintNot-Available.

Листинг6.5,a. /WEB-INF/classes/tags/HintAvailableTag. Java

package tags;

import javax.servlet.ServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

import beans.LoginDB;

public class HintAvailableTag extends TagSupport {

  public int doStartTag() throws JspException {

     ServletRequest req = pageContext.getRequest();

     LoginDB loginDB = (LoginDB)pageContext.

                                findAttribute("loginDB");

     if(loginDB.getHint(req.getParameter("userName")) != null)

        return EVAL_BODY_INCLUDE;

     else

        return SKIP_BODY;

  }

}

Листинг 6.5,6. /WEB-INF/classes/tags/HintKotAvailableTag. Java

package tags; import javax.servlet.ServletRequest;

import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport;

import beans.LoginDB; public class HintNotAvailableTag extends TagSupport {

  public int doStartTag() throws JspException {

     ServletRequest req = pageContext.getRequest();

     LoginDB loginDB = (LoginDB)pageContext.

                                findAttribute("loginDB");

     if(loginDB.getHint(req.getParameter("userName")) != null)

        return SKIP_BODY;       else         return EVAL_BODY_INCLUDE;

  } }

JSP-сценарии      169

Для того, чтобы определить, доступна ли подсказка для данного пользователя, оба класса поддержки обращаются к базе данных регистрации. Методы doStartTag этих классов возвращают либо SKIP_BODY, либо EVAL_BODY_INCLUDE. Возвращаемое значение определяет, должно ли включаться тело дескриптора в сгенерированный HTML-документ. Подробно процесс создания пользовательских дескрипторов описан в главах 1 и 2.

Одной из важных особенностей пользовательских дескрипторов является возможность инкапсулировать Java-код, благодаря чему авторы JSP-документов избавляются от необходимости работать с языковыми конструкциями Java. В этом причина того, что пользовательские дескрипторы считаются одними из самых важных средств JSP.

JSP-сценарии

До сих пор в данной книге все время подчеркивалось, насколько важно разделять Java-код и JSP-документы. В большинстве случаев этим советом не стоит пренебрегать, особенно тогда, Ko^aJSP-flOKyMeiiTbi используются в качестве просмотра в приложениях, соответствующих архитектуре MVC. Однако возможны ситуации, в которых оказываются полезны JSP-документы, содержащие Java-код. В данной книге такие документы будем называть JSP-сценариями. Следует заметить, что JSP — достаточно гибкий инструмент, позволяющий поддерживать различные подходы к построению Web-приложений.

JSP-сценарии — это JSP-документы, содержащие как HTML-дескрипторы, так и Java-код, Подобно пользовательским дескрипторам, JSP-сценарии инкапсулируют функции, необходимые авторам Web-страниц. Рассмотрим сценарий, приведенный в листинге 6,6,а, который выводит параметры запроса. Этот сценарий можно использовать для отладки.

170      Глава 6. Базовый набор классов для создания приложений Model 2

JSP-документ, использующий сценарий, код которого представлен в листинге 6.6,а, показан на рис. 6.5.

В верхнем окне на рисунке отображается HTML-документ, код которого приведен в листинге 6.6,6.

Листинг6.6,6. test.html

<htmlxtitle>JSP Scripts</title> <body>

<form action='showParams.jsp'>

<font size-M' color='blue'>Select Your Age Bracket:</font>

ll-20'>ll-20</input> 21-30'>21-30</input> 31-40'>31-40</input> 41-50'>41-50</input>

<input type='radio' name='age' value= <input type='radio' name='age' value=

<input type='radio' name='age' valu.e= <input type='radio' name='age' value= <input type='radio' name='age' value=

<font4' color='blue'>Select Your Favorite Fcuits:</font>

<input type-'checkbox' name='fruit' value=' Kiwi'>Kiwi</input>

<input type='checkbox' name='fruit' value='Apple'>Apple</input>

<input type='checkbox' name='fruit' value='Pear'>Pear</input>

<input type='checkbox' name='fruit' value='Grape'>Grape</input>

<p><input type='submit'/></p> </form>

</body> </html>

В дшшом HTML-документе атрибут action формы имеет значение showParams. j sp. Содержимое файла showParams. j sp показало в листинге 6.6,в.

JSP-сценарии      171

Листинг 6.6,в. /showParams.jsp

<htmlxtitle>JSP Scripts</title> <body>

<font  size='4'   color='blue'>Request   Parameters:</font> <%@   include file='showRequestParameters.jsp'   %>

</body> </html>

Как видно из приведенного примера, JSP-сценарии включаются в состав других JSP-документов.

И JSP-сценарии, и пользовательские дескрипторы инкапсулируют функции, необходимые авторам Web-страниц. По сравнению с JSp4:4eiiapiinMii, пользовательские дескрипторы сложнее в реализации; для создания пользовательского дескриптора надо написать класс поддержки, скомпилировать его и включить в описание библиотеки дескрипторов. JSP-сценарии реализуются проще, но для работы с ними необходимо использовать директиву include. Несмотря на то что пользовательские дескрипторы лучше обеспечивают повторное применение кода, для JSP-сценариев также найдется место в наборе инструментов pa3pa6oT4HKaJSPv*OKyMciiTOB.

Резюме

В некг горых публикациях высказывается мнение о том, что JSP плохо подходят для разработки сложных Web-приложений, поскольку они представляют собой "смесь" HTML-дескрипторов и Java-кода. Я не согласен с этим. При создании больших приложений на 6a3cJSP я всегда следую архитектуре Model 2, а средства, описанные в данной глане, полностью устраивают меня.

В данной Главе вы узнали о некоторых способах объектно-ориентированного проектирования, в частности, познакомились с архитектурой "модель-просмотр-контроллер" и сценариями развития событий. Эти подходы хорошо зарекомендовали себя при разработке сложных программных систем. В книгах и статьях но этим темам нет недостатка; ниже приведены ссылки на некоторые из них.

Объект; ю-орн оптированное проектирование:

Gamma, Helm, Johnson, Vljssidcs. Design Patterns, Addison-Wesley, 1992. Wirfs-Brock, Wilkerson, Wiener. Designing Object-oriented Software, Prentice Hall, 1990. Meyer, Bertand. Object-Oriented Software Construction, Prentice Hall, J997. Budd, Timothy. An Introduction to Object-oriented Programming, Addison-Wesley, 1991.

Архитектура"модель~просмотр-контроллср":

Alpert, Brown, Woolf. The Design Patterns Smalltalk Companion, Addison-Wesley, 1998. Gamma, Helm, Johnson, Vlissides. Design Patterns, Addison-Wesley, 1992.

Сценарии развития событий:

Fowler, Scott. UML Distilled Second Edition, Addison-Wesley, 2000.

Schneider, Winters. Applying Use Cases, a Practical Guide, Addison-Wesley, 2000.

ПОДДЕРЖКА СОБЫТИЙ

В этой главе...

Поддержка событий в базовом наборе классов Model 2. Повторная активизация форм.

Обработка форм, чувствительных к повторной активизации, средствами базового набора классов Model 2.

Перехват повторной активизации без использования базового набора классов.

Oбработка событий составляет основу функционирования приложений с графическим пользовательским интерфейсом. Например, пакет Swing обеспечивает поддержку самых разнообразных событий, связанных с мышью, окнами и фокусом ввода.

До того, (сак была реализована спецификация Servlet 2.3, контейнеры сервлетов генерировали события только в ответ на включение и удаление объекта. (О возникновении событий могут оповещаться только объекты, реализующие интерфейс HttpSessionBindingListener.) В спецификации Servlet 2.3 была добавлена поддержка событий, связанных с жизненным циклом приложения. Это позволило разработчикам отслеживать создание или удаление контекста сервлета и сеанса. Во время написания данной книги спецификация Servlet 2.3 была еще не закончена, и детали поддержки событий постоянно изменялись. Поэтому, если вам нужна подробная информация о поддержке событий жизненного цикла, обратитесь к одной из книг, посвященных сервлетам, в которой были учтены решения спецификации Servlet 2.3.

События жизненного цикла приложения генерируются контейнером сервлетов и обрабатываются приложениями. В данной главе рассказывается о том, как приложения генерируют и обрабатывают собственные события. Средства поддержки событий, рассмотренные здесь, представляют собой расширение базового набора классов Model 2, который обсуждался в главе б, поэтому, чтобы понять изложенный здесь материал, надо ознакомиться с содержимым предыдущей главы.

В данной главе мы реализуем обработку событий в базовом наборе классов Model 2. Далее вы увидите, как механизм обработки событий может быть использован для поддержки форм, чувствительных к повторной активизации. Повторная активизация возникает в результате неправильного использования закладок или кнопки Reload.

Перехват нежелательной повторной активизации формы должен быть предусмотрен во всех приложениях, независимо от того, использовался ли при их создании базовый набор классов с поддержкой событий. В связи с этим, в конце данной главы будет рассмотрен перехват повторной активизации для приложений, не соответствующих Model 2.

174      Глава 7. Поддержка событий

Поддержка событий в базовом наборе классов Model 2

Базовый набор классов Model 2 хорошо подходит для создания Web-приложений, поскольку он позполяет разделить бизнес-ЛОГИКу и средства представления данных и обеспечить параллельную работу авторов Web-страниц и разработчиков программного обеспечения. Если же набор классов Model 2 генерирует события, приложение может быть расширено. Например, если базовый набор классов генерирует события до и после выполнения некоторого действия, приложение имеет возможность обрабатывать эти события для выполнения ряда задач, таких как аутентификация, поддержка национальных кодировок или перехват повторной активизации форм.

В данном разделе мы расширим созданный ранее базовый набор ьслассов Mode] 2 так, чтобы до и после вызова метода perform действия генерировались события. Доработка набора ьслассов осуществляется в соответствии с моделью делегирования событий Java, т.е. источники событий передают события обработчикам, или объектам прослушивания.

Роль источников событий выполняют действия, которые генерируют события и передают их обработчикам, реализующим интерфейс ActionListener. События представляют собой экземпляры класса ActiortEvent. На рис. 7.1 показана последовательность событий, которые генерируются при каждом выполнении действия (вызове метода ре г form).

Поддержка событий в базовом наборе классов Model 2      175

Перед выполнением действия сервлет создает событие действия, указывая в качестве типа события ActionEvent.ACTION_BEFORE_PERFORM. Это событие передается методу f ireEvent действия, который вызывает метод beforeAction каждого зарегистрирован н о го об раб отч и ка.

После выполнения события сервлет действий создает событие типа Action-Event .ACTION_AFTER_PERFORM. Это событие снова передается методу fireEvent ^ действия, который вызывает метод afterAction каждого зарегистрированного обработчика.

Интерфейс ActionListener показан в листинге 7.1. Заметьте, что класс Action и интерфейс ActionListener не имеют никакого отношения к Action и ActionListener AWT.

ЛИСТИНГ 7.1 ,а. /WEB- INF/ classes /actions /events /ActionLi a tener. Java

package  actions.events;

import   javax.servlet.ServletException;

public   interface  ActionListener  extends   Java.util.EventListener   { public void beforeAction[ActionEvent  event)

throws  ServletException; public void  afterAction[ActionEvent  event)

throws   ServletException;

Интерфейс ActionListener расширяет интерфейс Java.util .EventListener, который должны реализовать все обработчики событий. Методы в интерфейсе EventListener отсутствуют.

В интерфейсе ActionListener объявлены два метода: beforeAction и afterAction. Как показано на рис. 7.1, эти методы нызываются до и после обращения к методу perform действия.

Оба метода ActionListener могут генерировать исключения, так, например, исключение возникает при повторной активизации формы.

При вызове каждому из методов ActionListener передастся объект исключения. Код класса ActionEvent представлен в листинге 7.1,6.

Листинг7.1,6. /WEB-INF/classes/actions/events/ActionEventoava

package actions.events;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import actions.Action;

public class ActionEvent extends java.util.EventObject {

  public static final int BEFORE_ACTION=0, AFTER_ACTION=1;

  private int eventType;

  private HttpServletRequest request;

  private HttpServletResponse response;

  public ActionEvent(Action action, int eventType,

                     HttpServletRequest request,

                     HttpServletResponse response) {

     super(action);   

     this.eventType = eventType;

     this.request = request;

     this.response = response;

  }

  public int getEventType() {

     return eventType;

  }

  public HttpServletRequest getRequest() {

     return request;

  }

  public HttpServletResponse getResponse() {

     return response;

  }

}

При построении события конструктору класса передаются объект Action, тип события, а также объекты, представляющие запрос и ответ. Конструктор передает объект Action конструктору суперкласса (java.util. EventObject), определяя действие как источник события. Для получения действия можно воспользоваться методом getSource, унаследованным от суперкласса.

Код интерфейса Action, который обсуждался а главе 6, приведен в листинге 7.1,в, На этот раз в интерфейсе предусмотрена поддержка событий, связанных с действиями. Теперь объект Action позволяет регистрировать обработчики и передавать им события в том порядке, в котором они были зарегистрированы. Для передачи событий используется метод Action . f ireEvent.

Листинг 7.1,в. /WEB-INF/classes/action/Action. Java

package actions;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import actions.events.ActionListener;

// Application-specific actions implement this interface

public interface Action extends ActionListener {

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                              throws ServletException;

   abstract void addActionListener(ActionListener listener);

  

  public boolean hasSensitiveForms();

  public boolean isSensitive();

}

Поддержка событий в базовом наборе классов Model 2  177

Реализацию по умолчанию методов Action.addActionListener и Action. FireEvent предоставляет абстрактный класс ActionBase, код которого приведен в листинге 7.1,г.

Листинг 7.1,Г. /WEB-INF/classes/actions/ActionBaae. Java

package actions;

import java.util.Enumeration;

import java.util.Vector;

import javax.servlet.ServletException;

import actions.events.ActionEvent;

import actions.events.ActionListener;

public abstract class ActionBase implements Action,

                                           ActionListener {

  private Vector listeners = new Vector();

  public void addActionListener(ActionListener listener) {

     listeners.addElement(listener);   

  }

  public void removeActionListener(ActionListener listener) {

     listeners.remove(listener);   

  }

  public boolean isSensitive() { // override this method

     return false;               // if this action is sensitive

  }

  public boolean hasSensitiveForms() { // override this method

     return false;               // if you have sensitive content

  }

  public void beforeAction(ActionEvent event)

                                       throws ServletException {

     fireEvent(event);

  }

  public void afterAction(ActionEvent event)

                                       throws ServletException {

     fireEvent(event);

  }

  protected void fireEvent(ActionEvent event)

                                      throws ServletException {

     Enumeration it = listeners.elements();

     while(it.hasMoreElements()) {

        ActionListener listener =

                       (ActionListener)it.nextElement();

        switch(event.getEventType()) {

           case ActionEvent.BEFORE_ACTION:

                        listener.beforeAction(event);

                        break;

           case ActionEvent.AFTER_ACTION:

                    listener.afterAction(event);

                    break;

        }

     }

  }

}

Наконец, модифицированный код сервлета действий, в котором учтена обработка событий, показан в листинге 7.1,д. Предыдущий вариант сервлета содержался в листинге 6.3,6.

178  Глава 7. Поддержка событий

Листинг7.1,д. /WEB-lNF/classes/ActionServlet. Java

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

import actions.*;

import actions.events.ActionEvent;

public class ActionServlet extends HttpServlet {

  private ActionFactory factory = new ActionFactory();

  private StateManager stateManager = new StateManager();

  public void init(ServletConfig config) throws ServletException{

     super.init(config);

     ResourceBundle bundle = null;

     try {

        bundle = ResourceBundle.getBundle(

                 config.getInitParameter("action-mappings"));

     }

     catch(MissingResourceException e) {

        throw new ServletException(e);

     }

     getServletContext().setAttribute("action-mappings", bundle);

  }

  public void service(HttpServletRequest req,

                      HttpServletResponse res)

                        throws ServletException {

     Action action = getAction(req);

     action.beforeAction(new ActionEvent(action,

                       ActionEvent.BEFORE_ACTION,

                       req, res));

     ActionRouter router = performAction(action, req, res);

     action.afterAction(new ActionEvent(action,

                      ActionEvent.AFTER_ACTION,

                      req, res));

     // routers could fire events in a manner similar to actions

     routeAction(router, req, res);

  }

  protected Action getAction(HttpServletRequest req)

                                       throws ServletException {

     Action action = null;

     try {

        action = factory.getAction(getActionClass(req),

                                   getClass().getClassLoader());

        //action.addActionListener(stateManager);

     }

     catch(Exception ex) {

        throw new ServletException(ex);

     }

     return action;

  }

  protected ActionRouter performAction(Action action,

                                       HttpServletRequest req,

                                       HttpServletResponse res)

                   throws ServletException {

     ActionRouter router = null;

     try {

        router = action.perform(this,req,res);

     }

     catch(Exception ex) {

        throw new ServletException(ex);

     }

     return router;

  }

  protected void routeAction(ActionRouter router,

                             HttpServletRequest req,

                               HttpServletResponse res)

                               throws ServletException {

     try {

        router.route(this, req, res);

     }

     catch(Exception ex) {

        throw new ServletException(ex);

     }

  }

  private String getClassname(HttpServletRequest req) {

     String path = req.getServletPath();

     int slash = path.lastIndexOf("/"),

         period = path.lastIndexOf(".");

     if(period > 0 && period > slash)

      path = path.substring(slash+1, period);

     return path;

  }

  private String getActionClass(HttpServletRequest req) {

     ResourceBundle bundle = (ResourceBundle)getServletContext().

                             getAttribute("action-mappings");

     return (String)bundle.getObject(getActionKey(req));

  }

  private String getActionKey(HttpServletRequest req) {

     String path = req.getServletPath();

     int slash = path.lastIndexOf("/"),

         period = path.lastIndexOf(".");

     if(period > 0 && period > slash)

      path = path.substring(slash+1, period);

     return path;

  }

}

Теперь, когда мы добавили средства поддержки событий к базовому набору классов Model 2, рассмотрим, как можно использовать эти средства для перехвата повторной активизации форм.

Совет

Расширение базового набора классов без модификации кода

Если ваш набор классов генерирует события, вы можете расширять его функциональные возможности, обрабатывая события; при этом модификация кода не требуется. Предыдущий раздел был посвящен реализации средств поддержки событий в базовом наборе классов Model 2. В следующем разделе речь пойдет об использовании механизма событий для расширения возможностей набора классов.

Повторная активизация форм

При работе с формами пользователь может неумышленно выполнить некоторые действия, например, перейти по закладке или щелкнуть па кнопке Reload, в результате чего произойдет повторная активизация формы.

Повторная активизация форм      179

На рис. 7.2 показаны Web-страницы, при работе с которыми форма может быть повторно активизирована. Приведенные Web-странипы демонстрируют последовательность действий, выполняемых пользователем с именем Timothy (его регистрационное имя timothy) при создании новой учетной записи.

Сначала пользователь Timothy пытается зарегистрироваться в системе, но поскольку для него еще не создана учетная запись, запрос перенаправляется документу Login Failed. Здесь Timothy активизирует ссылку на страницу, предназначенную для создания новой учетной записи, в результате чего вызывается докртент, озаглавленный Open a New Account. На Wcb-страшще, отображаемой на экране, пользователь заполняет форму и щелкает на кнопке create account.

На этом этапе для пользователя Timothy создается учетная запись, и на экране отображается Web-страница Please Login, содержащая форму. Посредством этой формы Timothy должен зарегистрироваться, используя вновь созданную учетную запись. Но что произойдет, если вместо регистрации пользователь щелкнет на кнопке

180  Глава 7. Поддержка событий

Reload, вызвав тем самым повторную загрузку Web-страницы? Или если он воспользуется кнопкой Back, а на странице регистрации, не изменяя данных в форме, щелкнет на кнопке create account? В обоих случаях будет сгенерирован повторный запрос, т.е, возникнет ситуация, которая в большинстве приложений вызывает нежелательные последствия.

В данной главе рассматриваются два способа противодействия повторным запросам; в одном из них используется обработка событий, а в другом— пользовательские дескрипторы JSP. В обоих случаях при попытке повторной активизации формы генерируется исключение {рис. 7.3).

Error: 500

Location: /events/new-account-action-do

Internal Servlet Error:

javaxservletServletException: Sorry, but this is a sensitive page that cent be resubmitted.

at actions SensitiveActionListener.beCoreAction(actions/SensitiveAetionListener.jav»:Z4)

ataotions.ActionBase.fireEventCactions/AotionBasejava:48)

«tBetions.ActionBasebeforeAct[onCactions/ActionBasejava3Z)

Рис, 7.3. При попытке повторной активизации формы генерируется исключение

Обработка форм, чувствительных к повторной активизации, средствами базового набора классов Model 2

Как следует из сказанного выше, некоторые действия чувствительны к повторным запросам, возникающим при использовании закладок и кнопки Reload. В нижнем окне на рис. 7.2 показан результат выполнения действия new-account-action (см. URL в поле Address). Действия, чувствительные к повторным запросам, называются чувствительными действиями.

Повторное обращение к чувствительным действиям приводит к ошибке или к возникновению недопустимого состояния. Так, например, при повторном вызове действия new-account-action, код которого представлен в листинге 7.2,а, могут быть созданы две одинаковые учетные записи.

Повторная активизация форм      181

Листинг7.2,в. /WEB-INF/classes/actions/NewAocountAction. Java

package actions;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import beans.LoginDB;

import beans.User;

public class NewAccountAction extends ActionBase {

  public NewAccountAction() {

     isSensitive = true; // this is a sensitive action

  }

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                              throws ServletException {

     LoginDB loginDB = (LoginDB)servlet.getServletContext().

                        getAttribute("loginDB");

     String uname = req.getParameter("userName");

     loginDB.addUser(uname, req.getParameter("password"),

                            req.getParameter("password-hint"));

     req.setAttribute("userName", uname);

     return new ActionRouter("account-created-page");

  }

}

Кроме чувствительных действий, мы также будем обсуждать чувствительные формы. Форма называется чувствительной к повторным активизациям, или просто чувствительной, если она вызывает чувствительное действие. Так, например, чувствительной является форма, предназначенная для создания новых учетных записей, поскольку она вызывает чувствительное действие new-account-action.

Некоторые действия перенаправляют запросы JSP-документам, содержащим чувствительные формы. В качестве примера можно привести действие query-account-action, код которого содержится в листинге 7.2,6; его выполнение приводит к отображению JSP-документа, показанного на рис. 7.2, содержащего чувствительную форму. Будем называть такие действия действиями с чувствительными формами.

Листинг 7.2,6. /WEB-INF/classes/aetiona/QueryAccountAction. Java

package actions;

import javax.servlet.ServletException;

import javax.servlet.http.*;

public class QueryAccountAction extends ActionBase {

  public QueryAccountAction() {

     // this action forwards to a JSP page with sensitive forms

     hasSensitiveForms = true;

  }

  public ActionRouter perform(HttpServlet servlet,

                              HttpServletRequest req,

                              HttpServletResponse res)

                              throws ServletException {

     return new ActionRouter("query-account-page");

  }

}

182  Глава 7. Поддержка событий

Для перехвата повторной активизации чувствительных форм необходимо прослеживать чувствительные действия и действия с чувствительными формами. Чтобы добиться этого, надо внести изменения в интерфейс Action и класс ActionBase. Фрагменты модифицированного кода показаны в листингах 7.3,а и 7.3,6,

Листинг 7.3,а. Интерфейс Action с поддержкой чувствительных форм

//Пропущенные фрагменты кода можно найти в листинге 7.1,в. package actions;

public interface Action extends ActionListener  (

public boolean hasSensitiveForms(); public boolean isSensitive();

ЛИСТИНГ 7.3,6. Класс ActionBase С поддержкой чувствительных форм

//Пропущенные фрагменты кода можно найти в листинге 7.1,г. package actions;

public abstract class ActionBase implements Action { protected boolean isSensitive = false,

hasSensitiveForms = false;

public ActionBase() {

addActionListener(new SensitiveActionListener());

public boolean isSensitive() {

return isSensitive; } public boolean hasSensitiveForms() {

return hasSensitiveForms;

Повторная активизация форм      183

Теперь, когда вы можете идентифицировать чувствительные действия и действия с чувствительными формами, рассмотрим, как использовать маркеры для предотвращения повторной активизации форм.

Обработчики и использование маркеров

Для перехвата повторных, активизаций форм будем использовать объекты прослушивания, которые специальным образом обрабатывают чувствительные действия и действия с чувствительными формами. Для каждого действия с чувствительной формой обработчик создаст специальную строку, называемую маркером (token). Затем обработчик помещает маркер в состав области видимости запроса и области видимости сеанса.

При активизации чувствительной формы создается новый запрос и выполняется чувствительное действие. Однако перед выполнением чувствительного действия обработчик события проверяет, существуют ли оба маркера и совпадают ли они. Если маркеры идентичны, чувствительное действие выполняется, в противном случае генерируется исключение. После завершения чувствительного действия оба маркера удаляются из соответствующих областей видимости.

Использование маркеров гарантирует, что чувствительные действия будут вызываться только посредством чувствительных форм. Если пользователь попробует сделать то же самое с помощью кнопки Reload или закладки, будет сгенерировано исключение.

Исходный код класса Token приведен в листинге 7.4,а.

ЛИСТИНГ 7.4,a. /WEB-INF/classes/beans/Token . Java

package beans;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

import java.security.MessageDigest;

public class Token {

  private String token;

  public Token(HttpServletRequest req) throws ServletException {

     HttpSession session = req.getSession(true);

     long systime = System.currentTimeMillis();

     byte[] time  = new Long(systime).toString().getBytes();

     byte[] id = session.getId().getBytes();

     try {

        MessageDigest md5 = MessageDigest.getInstance("MD5");

        md5.update(id);

        md5.update(time);

        token = toHex(md5.digest());

     }

     catch(Exception ex) {

        throw new ServletException(ex);

     }

  }

  public String toString() {

     return token;

  }

  private String toHex(byte[] digest) {

     StringBuffer buf = new StringBuffer();

     for(int i=0; i < digest.length; i++)

        buf.append(Integer.toHexString((int)digest[i] & 0x00ff));

     return buf.toString();

  }

}

184  Глава 7. Поддержка событий

Объект Token создает уникальную закодированную строку, основой для создания которой являются данные о сеансе и текущее время. При обсуждении примера с использованием маркера удобно рассматривать объект Token как "черный ящик": в ответ на обращение объект предоставляет вам строку-маркер.

Обработчик события, предназначенный для перехвата повторных активизаций чувствительных форм, является экземпляром SensitiveActionListener. С каждым действием связан обработчик чувствительных действий. Как видно из следующего фрагмента кода, конструктор ActionBase добавляет обработчик к списку объектов прослушивания.

//  Пропущенные фрагменты кода можно найти в листинге 7.3,6. public abstract  class ActionBase  implements Action   {

public ActionBase()    {

addActionListener(new SensitiveActionLiatener());

}

На рис. 7.4 показана последовательность событий, которые сопутствуют вызову метода bef oreAct ion обработчика, связанного с чувствительным действием.

Обработчики событий для чувствительных действий исполняют роль фильтров. Перед выполнением действия обработчик выясняет, вызвано ли действие посредством чувствительной формы.

После завершения действия обработчик проверяет, приводит ли его выполнение к отображению документа, содержащего чувствительную форму. При положительном результате проверки обработчик создает маркер и сохраняет его в области видимости запроса и области видимости сеанса {рис. 7.5).

Повторная активизация форм  185

186      Глава 7. Поддержка событий

Повторная активизация форм  187

Код класса SensitiveActionListener показан в листинге 7.4,6.

Листинг 7.4,6. /WEB-INF/classea/actions/SensitiveActionListener. Java

package actions;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

import actions.Action;

import actions.events.ActionEvent;

import actions.events.ActionListener;

import beans.Token;

public class SensitiveActionListener implements ActionListener { public void befoceAction(ActionEvent event)

throws ServletException { Action action = (Action)event. getSource();

if [action.isSensitive() ) (

HttpServletRequest req = event.getRequest(); String requestToken = (String)req.getParameter("token"), sessionToken = (String)req.getSession().

getflttribute("token");

if(sessionToken == null || requestToken == null ||

!sessionToken.equals(requestToken)) { throw new ServletException(

"Sorry, but this is a sensitive page " + "that can't be resubmitted.");

public void afterAction(ActionEvent event)

throws ServletException {

Action action = (Action)event.getSource(}; HttpServletRequest req = event.getRequest(); HttpSession session = req.getSession();

if (action.hasSensitiveForms()) ( Token token = new Token(req);

session.setAttribute("token", token.tostring());

req.setAttribute("token", token.toString [)) ; } if(action.isSensitive()) {

session.removeAttribute("token");

188  Глава 7. Поддержка событий

Применение маркеров в программах

Теперь, когда вы знаете, как работают с маркерами объекты прослушивания, рассмотрим вопросы использования маркеров в программах. В первую очередь надо идентифицировать действие как чувствительное. Соответствующий код для действия new-account-action приведен ниже.

// Пропущенные фрагменты кода можно найти в листинге 7.2,а.

public class NewAccountAction extends ActionBase I public NewAccountActicn() {

isSensitive = true; // чувствительное действие

Аналогично, query-account-action идентифицируется как действие с чувствительной формой.

// Пропущенные фрагменты кода можно найти в листинге 7.2,6.

public class QueryAccountAction extends ActionBase { public QueryAccountAction[) {

// Данное действие перенаправляет запрос JSP-документу // с чувствительными формами hasSensitiveForms =s true; }

В файле newAccount. j sp, содержимое которого показано в листинге 7.4, содержится чувствительная форма, для которой в качестве значения атрибута action указано действие new-account-action. К моменту загрузки JSP-документа обработчик сохраняет маркер в области видимости запроса и сеанса. Поскольку при активизации формы генерируется новый запрос, пользовательский дескриптор извлекает маркер и включает его в новый запрос как скрытый элемент формы.

ЛИСТИНГ7.4,В. /newAccount.jsp

<html><head><title>New Account</title>

  <%@ taglib uri='/WEB-INF/tlds/utilities.tld' prefix='util' %>

</head>

<body>

<font size='5' color='blue'>Open a New Account</font>

<hr>

<form action=' <%=response.encodeURL("new-account-action.do")%> '

       method='post'>

  <table><tr>

        <td> User Name: </td>

        <td><input type='text' name='userName'></td>

     </tr><tr>

        <td> Password: </td>

        <td><input type='password' name='password' size='8'></td>

     </tr><tr>

        <td> Confirm Password: </td>

        <td><input type='password' name='confirm-password'

                   size='8'></td>

     </tr><tr>

        <td> Password Hint: </td>

        <td><input type='text' name='password-hint'>

        </td>

     </tr>

  </table>

  <br>

  <input type='submit' value='create account'>

  <util:token/>

</form>

</body>

</html>

Повторная активизация форм  189

В результате активизации формы вызывается действие new-account-action. Обработчик, связанный с чувствительным действием, проверяет маркеры для запроса и сеанса. В результате действие new-account-action будет выполняться только при активизации чувствительной формы и не будет реагировать на переходы по закладке и кнопке Reload.

Чтобы закончить обсуждение данного вопроса, нам осталось рассмотреть структуру пользовательского дескриптора util: token. Класс поддержки для данного дескриптора приведен в листинге 7.5,

ЛИСТИНГ7.5. /WEB-INF/classes/tags/TokenTag.Java

package tags;

import javax.servlet.ServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class TokenTag extends TagSupport {

  private String property;

  public int doStartTag() throws JspException {

     ServletRequest req = pageContext.getRequest();

     String value = (String)req.getAttribute("token");

     if(value == null)

        throw new JspException("No token in request scope");

     try {

        pageContext.getOut().print("<input type='hidden' " +

           "name='token' " + "value ='" + value + "'>");

     }

     catch(java.io.IOException ex) {

        throw new JspException(ex.getMessage());

     }

     return SKIP_BODY;

  }

}

190  Глава 7. Поддержка событий

Дескриптор token генерирует HTML-код скрытого элемента формы. Значением этого элемента является маркер, который обработчик, связанный с чувствительным действием, сохранил в области видимости запроса.

Совет

Не забывайте предусмотреть перехват повторной активизации чувствительных форм

Наличие закладок и кнопки Back способны отравить жизнь Web-разработчикам, поскольку указанные средства броузера предоставляют возможность произвольного доступа к объектам приложений, играющих роль просмотра. Неправильное использование закладок и кнопки Back может привести к некорректному обращению к действиям, поэтому вызовы, соответствующие активизации чувствительных форм, необходимо перехватывать.

В предыдущем разделе мы рассматривали перехват активизации чувствительных форм с использованием базового набора классов, в котором реализована обработ-ка событий. Следующий раздел посвящен во[гросам перехвата активизации: форм средствами пользовательских дескрипторев.

Перехват повторной активизации без использования базового набора классов

Далеко не все разработчики применяют в своих программах базовый набор классов Model 2, однако необходимость в перехвате повторных активизаций возникает практически у всех. Поэтому рассмотрим, как можно перехватывать повторные активизации, применяя для этого лишь пользовательские дескрипторы. Эти дескрипторы используют маркеры, которые обсуждались ранее в данной главе.

Пользовательские дескрипторы, предназначенные для перехвата повторной активизации чувствительных форм, перечислены в табл. 7.1.

Таблица 7.1. Пользовательские дескрипторы, предназначенные для перехвата повторной активизации чувствительных форм

Дескриптор Описание

                                Создает маркеры и сохраняет их в области видимости запроса и в области видимости сеанса

token Помещает маркер в форму в виде скрытого поля

check-tokens Проверяет корректность маркера

Дескрипторы, приведенные в табл. 7.1, просты в применении; например, в листинге 7.6,а представлен JSP-документ, который использует первый и третий из перечисленных выше дескрипторов.

Повторная активизация форм      191

Листинг 7.6,а. Применение пользовательских дескрипторов create-tokens И

<html><head><title>New Account</title>

  <%@ taglib uri='/WEB-INF/tlds/utilities.tld' prefix='util' %>

  <%@ taglib uri='/WEB-INF/tlds/tokens.tld' prefix='tokens' %>

  <tokens:create-tokens/>

</head>

<body>

<font size='5' color='blue'>Open a New Account</font>

<hr>

<form action='createAccount.jsp' method='post'>

  <table>

     <tr>

        <td>User Name:</td>

        <td><input type='text' name='userName'></td>

     </tr>

     <tr>

        <td>Password:</td>

        <td><input type='password' name='password' size='8'></td>

     </tr>

     <tr>

        <td>Confirm Password:</td>

        <td><input type='password' name='confirm-password'

                                   size='8'></td>

     </tr>

     <tr>

        <td>Password Hint:</td>

        <td><input type='text' name='password-hint'></td>

     </tr>

  </table>

  <br>

  <input type='submit' value='create account'>

  <tokens:token/>

</form>

</body>

</html>

В JSP-документе, приведенном выше, дескриптор create-tokens создаст маркеры для сеанса и запроса, а дескриптор token передаст маркер запроса действию, предназначенному для создания новой учетной записи. Действие передается как скрытое поле (скрытое поле необходимо использовать потому, что при активизации формы генерируется новый запрос). Дескриптор token был использован ранее в листинге 7.5.

Дескриптор check-tokens проверяет идентичность дескрипторов запроса и сеанса. Если дескрипторы отличаются друг от друга, генерируется исключение. JSP-до-кумент, код которого представлен в листинге 7.6,6. использует данный дескриптор для того, чтобы исключить возможность дублирования учетных записей.

192  Глава 7. Поддержка событий

Листинг 7.6,6. Использование дескрипторов check-tokens и reraove-seasion-token

<%@ taglib uri='/WEB-INF/tlds/utilities.tld' prefix='util' %>

<%@ taglib uri='/WEB-INF/tlds/tokens.tld' prefix='tokens' %>

<tokens:check-tokens/>

 

<jsp:useBean id='bean' class='beans.CreateAccountBean'/>

<% bean.createAccount(request, application); %>

<font size='4' color='blue'>

  An account has been created for

  <%= request.getParameter("userName") %>

</font>

<tokens:remove-session-token/>

<p><%@ include file='login-form.jsp' %></p>

Код класса поддержки дескриптора create-to kens приведен в листинге 7.6,в.

Листинг 7.6,в. /WEB-INF/classes/tags/CreateTokensTag. Java

package tags;

import javax.servlet.ServletRequest;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.TagSupport;

import beans.Token;

public class CreateTokensTag extends TagSupport {

  private String property;

  public int doEndTag() throws JspException {

     ServletRequest request = pageContext.getRequest();

     try {

        Token token = new Token((HttpServletRequest)request);

 

        pageContext.setAttribute("token", token.toString(),

                                   PageContext.SESSION_SCOPE);

        pageContext.setAttribute("token", token.toString(),

                                   PageContext.REQUEST_SCOPE);

     }

     catch(Exception ex) {

        throw new JspException(ex.getMessage());

     }

     return EVAL_PAGE;

  }

}

Повторная активизация форм  193

Класс поддержки создает маркер и сохраняет его в области видимости запроса и сеанса.

Код класса поддержки дескриптора check-tokens приведен в листинге 7.6,г.

Листинг7.6,Г. /WEB-INF/classes/tags/CheckTokensTag.Java

package tags;

import javax.servlet.ServletRequest;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class CheckTokensTag extends TagSupport {

  private String property;

  public int doEndTag() throws JspException {

     ServletRequest  req = pageContext.getRequest();

     String sessionToken = (String)req.getParameter("token");

     String requestToken = (String)pageContext.getSession().

                           getAttribute("token");

     if(requestToken == null || requestToken == null ||

        !sessionToken.equals(requestToken))

        throw new JspException("Sorry, but this sensitive page" +

                               " can't be resubmitted.");

     return EVAL_PAGE;

  }

}

Класс поддержки данного дескриптора извлекает два маркера и сравнивает их. Если дескрипторы отсутствуют или не совпадают, класс поддержки генерирует исключение.

Резюме

В данной главе рассматривались три основных вопроса.

Обработка событий в базовом наборе классов Model 2.

Использование средств обработки событий для перехвата повторной активи
зации чувствительных форм.

Перехват  повторной   активизации   чувствительных   форм   без   применения
средств обработки событий и архитектуры Model 2.

Рассмотрение первого вопроса сводится к обсуждению модели делегирования событий Java и реализации базового набора классов в соответствии с образом разработки Front Controller. Согласно этому образу разработки, поддержка событий должна осуществляться централизованно, и средства обработки событий реализуются в составе сервлета. В данной главе используется базовый набор классов Model 2, но вы без

194      Глава 7. Поддержка событий

труда сможете адаптировать механизм обработки событий в другом наборе, соответствующем образу разработки Front Controller.

Реализовав поддержку событий в базовом наборе классов, вы можете расширять набор, не модифицируя его код. В данном случае возможность расширения набора классов демонстрируется на примере перехвата повторной активизации чувствительных форм, однако применить механизмы обработки событий можно также для решения других задач, например, для аутентификации пользователей или для установки национальных кодировок.

Независимо от того, используете ли вы базовый набор классов и обработку событий, создавая приложение, вам необходимо позаботиться о правильной обработке повторной активизации чувствительных форм. В данной главе рассматриваются два способа решения этой задачи: с применением базового набора классов и обработки событий и средствами пользовательских дескрипторов.




1. Заявление
2. тематика русский язык литература задания на логику.html
3. Курсовая работа- Учет на валютных счетах в банке
4. . Мар~ївська сільська рада Мочоний О.
5. Подведомственность дел и принципы арбитражного судопроизводств
6. Понятие и основные признаки судебной власти Конституция Российской Федерации предусматривает три вида
7. Эскипулас выходит за рамки центральноамериканского субрегиона
8. Тема 13 ГОРОД В СОЦИАЛЬНОЭКОНОМИЧЕСКОЙ СТРУКТУРЕ ГОСУДАРСТВЕННОГО РАЗВИТИЯ 1
9. Тема- анализ возможностей фирмы при выборе целевого рынкана примере конкретного предприятия рынка исслед
10. Рыночная экономика.html
11. Задание 108 Найдите собственные значения и собственные векторы линейного оператора заданного в базисе сле
12. а prs tuberlis листовидный вырост окружающий ножку гипофиза prs intermedi которую правильнее обозначать как пр
13. Понятие правового регулирования исполнения и отбывания наказания
14. ТЕМА 26 ЗОЛОТОЕ КОЛЬЦО РОССИИ
15. Неандерталец и причины его исчезновени
16. Система запалювання від магнето
17. Вейделевская средняя общеобразовательная школа Вейделевского района Белгородской области Ко
18. Маркетинг и качество
19. Жизнь театра и судьба артиста в пьесах Островского
20. Анализ создания малого бизнеса на примере магазина Школьный возраст