Технологии Java
для разработки веб-приложений
Евгений Соколов ака nightd,
Линукс Онлайн

|
Студент: Вот эта область неплоха. Теперь гораздо ближе мне вы.
Мефистофель: Теория, мой друг, суха, Но зеленеет жизни древо.
Гете, "Фауст" |
Предисловие
Старые добрые времена, когда веб-сайты состояли из статического контента и
нескольких cgi-скриптов для обработки форм на Perl, канули в Лету, и похоже, безвозвратно.
В наше новое и недоброе время от веб-приложений требуется гораздо больше интерактивности.
Количество разнообразных технологий и подходов для веб-разработок в настоящее время кажется просто гигантским,
а направление их развития - слабопредсказуемым. В мутных потоках корпоративного пиара,
болтовни маркетологов ("singing-and-dancing marketing crap") и прочего информационного bullshit'а можно захлебнуться.
Как выбрать перспективную рабочую платформу для веб-разработок, действительно удовлетворяющую современным
требованиям?
Для такого выбора необходимо сравнивать множество подходов и реализаций,
нужно иметь о них современное представление.
Но это современное представление часто отсутствует даже у многих авторов подходов -
очень часто в их статьях даются устаревшие примеры технологий-соперников. По-видимому, они живут в
своих вселенных, в которых развитие всех других подходов и технологий, кроме их
собственных, остановилось во времени :-)
В данной статье описывается эволюция подходов к разработке веб-приложений на базе технологии Java
с учетом современных потребностей разработчиков, разрабатываемых в настоящее время спецификаций и программного
обеспечения. Чтобы статья получилась относительно небольшой и легкочитаемой, рассматриваемые
подходы представлены в основных идеях без второстепенных деталей (но не без
конкретных примеров!), а язык
максимально избавлен от тяжелых канцеляризмов, которых и так хватает в
ежедневном меню программиста, читающего документацию. Общая цель, которую
преследовал автор - дать существующим и будущим разработчикам веб-проектов
ретроспективный обзор рабочих инструментов и подходов с примерами, чтобы каждый не открывал
Америку в одиночку и, представляя общую картину, мог быстрее начать пользоваться этими инструментами и
подходами.
Статья рассчитана на программистов, в том числе только знакомящихся с
веб-программированием на Java, может пригодиться и архитекторам веб-проектов.
Автор статьи далек от статуса "гуру", так что заранее приглашает читателей
к конструктивному обсуждению с последующей переработкой материала.
1. Веб-приложения сегодня и завтра.
B2B, bla-bla-bla & stuff.
Интернет-бум продолжается, появляются новые и растут в объеме старые
сайты и интранет-системы,
сильно увеличилось число веб-программистов. Конкуренция между
веб-программистами по всем законам
рынка привела к снижению цен на производимые ими услуги и в каком-то
смысле - к
повышению качества этих услуг. У заказчиков резко повысились запросы.
Больше
возможностей, хороших и разных! Долой гостевые книги, подавай
контент-систему,
долой общую форму для заказа, подавай "системы поддержки клиентов",
дилерские, админ-режимы
и т.п. Мысли вслух: скоро из sales-менеджеров интернет-агентств только
ленивые не будут жонглировать на каждом шагу терминами вроде B2C, B2B,
CLR,
CMS, ERP, даже если все разработки ведутся одним студентом ночами по
модему. (Еще для для солидности некоторые прибавляют про ISO900X).
Причины возрастания роли
веб-приложений понятны - они не требуют установки у пользователя и их гораздо проще "подстраивать под
этого самого пользователя", такие приложения более управляемы с обеих
сторон, меньше требуют от клиентского устройства, в конечном
итоге за веб-приложениями будущее (здесь все должны дружно сказать: "Ага!"). Многие "пока настольные" приложения
уже используют для взаимодействия с пользователем веб-интерфейс (характерный
пример - Microsoft Money 2002).
То есть в веб-приложения начинают закладывать такую функциональность,
какая раньше закладывалась в традиционные "настольные" приложения,
а кое-где (в интранет-системах крупных организаций) веб-функциональность может
быть весьма внушительной. Ввиду того, что веб-интерфейс применяется не только в Интернет,
серверные программы, работающие через веб-интерфейс, совершенно неправильно
называть "сайтами": это - именно веб-приложения. И заодно рядовой пользователь приучается к
веб-интерфейсу.
Ситуация такова, что заказчикам требуются все более сложные и функциональные
веб-приложения, желательно очень быстро и по низким ценам. Все это предполагает
возможность разработки приложения с высокой скоростью из готовых блоков, а
значит, достижение высоких показателей code reuse.
Каковы в конечном итоге "требования времени" к разработке веб-приложений?
Иными словами, что требуется сегодня-завтра от современной системы (скажем, коммерческого сайта) в разработке и эксплуатации?
Получается что-то вроде следующего списка:
- Расширяемость, быстрая разработка. Быстрое и несложное добавление нового функционала. Компонентная структура front- и back-office.
- Высокая готовность к частым изменениям представления и обработки по желанию заказчика.
- Масштабируемость, желательна совместимость кода с промышленными серверами приложений.
- Безопасность системы - в частности, поддержка транзакций, криптозащиты, работа по защищенным каналам.
- Поддержка нескольких языков (i18n & l10n).
- Поддержка нескольких выходных форматов. Например, система может
выдавать результат в XML-формате (стилизация может производиться как у клиента,
так и на сервере, последнее пока надежнее), HTML, WML, предоставлять пользователю
несколько переключаемых цветовых стилей или частные настройки, иметь версию для печати,
генерировать на лету документы PDF и т.п.
- Поддержка тонкого разграничения доступа к функционалу веб-приложения и
обеспечение возможности персонализации пользователей (customization).
Язык Java отлично подходит для быстрой разработки масштабируемых, надежных
сетевых бизнес-приложений, он универсален, переносим, обладает синтаксисом,
сразу понятным для большинства программистов, выросших на C/C++, имеет мощную корпоративную
поддержку и удобные средства локализации, доступа к базам данных и обработки
XML-документов. Java - это даже не орудие, это целый
арсенал. Вдобавок в Java изначально поддерживается Unicode (черт бы побрал всю
эту свистопляску кодировок из прошлого века). В общем, есть мнение, и не только
мое ;-), что Java для означенных задач - самое оно. Перефразируя Пелевина, можно
сказать: "Серьезный язык для серьезных задач" ;-)
Если Вы из тех, кто упорно верит (а в это можно только верить ;-)), что Java - "тормозной",
"монстр", "жрет память" и т.п., не читайте эту статью, она не для Вас, а для
людей с другими задачами. И еще, в XXI веке P4 и 1Gb памяти на рабочей станции -
обычное явление.
А задачи требуется решать все более серьезные. Мы живем во время начала широкого
распространения разнообразных тонких клиентов. Что характерно, разработчики
стандартов тоже не стоят на месте. HTML безнадежно устарел, сегодня он - не более
чем пережиток прошлого века, всего лишь один из частных выходных форматов для
мощного современного веб-приложения на основе XML. Для приема данных от клиента
пока еще приходится пользоваться HTML-формами, но уже почти разработан новый продвинутый
стандарт от
W3C -
XForms (в ноябре 2002 получил статус
"Candidate Recomendation"). В связи с постоянным увеличением роли
тонких клиентов в нашей жизни нам будет требоваться все больше возможностей от
веб-приложений. Как можно ожидать, такая ситуация приведет к постепенному стиранию различий
между "традиционными" и веб-приложениями, а с точки зрения программиста - к
структурной конвергенции подходов и средств для их разработки. А пока можно ожидать
архитектурных заимствований из области "традиционных" приложений. В данной статье
эта тенденция прослеживается для веб-приложений на Java.
2. Server-side Java.
Повесть о спасении утопающих.
2.1. Сервлеты: с чего все началось
Когда-то для веб-программистов на Java были доступны только сервлеты. Для тех,
кто не знает: это такие объекты, которые живут в сервлет-контейнере (из обычного
-
Tomcat,
Jetty или
Resin), доступны по
URI и обрабатывают запросы. Обрабатывают быстро, так как живут в памяти.
Одним словом, работнички на все руки. Первоначально весь динамический вывод запихивался внутрь конструкций печати в сервлетах.
Я эти времена не застал, но несколько таких проектов видел (среди них был даже магазин),
и взгляд постоянно останавливался на чем-то вроде такого (пример из
статьи Вячеслава Педака на Javable.Com):
out.println("<form name=inform METHOD=POST
ACTION="+req.getServletPath()+"
target=frame2>");
if (doc.getAction() != null) tmp = " ( "+doc.getAction()+" )";
if (FoldType == 1) out.println("<b>От:
</b>"+doc.getFrom()+tmp+"<br>");
else out.println("<b>Кому: </b>"+doc.getTo()+tmp+"<br>");
Темное было тогда время, можно сказать, мрачное средневековье. Как жили тогда
программисты, если клиент хотел что-то по быстрому переделать в выводе сайта, сказать не берусь, не
был, не знаю. Думаю, что им было непросто. Возможно, такие проекты
стоили тогда дорого для частой смены дизайна и структурных перестроек, и частично из-за этого тоже :-)
Одним словом, время было мрачное (но зарабатывали, видимо, неплохо).
2.2. Великий Бум Серверных Страниц. JSP
Потом зловредные Sun-овцы малину испортили. А именно, разработали JSP -
Java Server Pages. Видимо,
рассудили, что если уж у Microsoft есть
Active Server
Pages, то Sun'у это точно не помешает. Вообще, server pages тогда взялись делать все кому не лень
- сделали
Python Server Pages,
Perl Server Pages,
TCL Server Pages,
Haskell Server Pages,
Lisp Server Pages,
даже для своей СУБД Cache парни из
InterSystems и то смастерили
Cache Server
Pages, о как! Видать, время тогда было такое ("Oops! I did it again, baby!").
Каждый хотел чем мог помочь любимому языку, а что может быть лучше, чем затащить его в
веб? Ленин, кажется, говорил о "детской болезни левизны в коммунизме".
Наконец появился
Самый Правильный Язык Из Всех (продолжительная овация в
зале, крики "Ура!"). "Счастье найдено нами" - воскликнули многие, подмигнули и занялись делом.
Однако вернемся к нашим баранам. Опять же для тех, кто не в курсе, Server Pages
- это когда внутри будущей веб-страницы пишется код на каком-то языке:
объявляются переменные, крутятся циклы, делаются выборки из БД и т.п. Когда к
странице обращаются, веб-сервер не посылает ее сразу, а отдает на предмет достройки
специальному обработчику (часто - модуль веб-сервера), который выполняет то, что велено, заполняет данными, достраивает до
готовой страницы и отдает назад веб-серверу для отправки. Таким образом,
подобная страница является на самом деле шаблоном для целого семейства страниц.
Страница JSP - это сервлет навыворот : код разметки в ней можно
писать безо всяких затей, а настоящий java-код - в специальных тегах (<% ...
%>). При первом
обращении страница превращается в нормальный сервлет, который (как и полагается
сервлету) компилируется, ложится в память и потом молотит запросы. Есть еще в
JSP ряд полезных тегов и страницы JSP можно подставлять одну в другую (вроде
достопамятного "#include stdio.h"), что здорово. В один файл можно
поместить шапку, в другой - меню, в третью - контент и т.п. Самое ценное, что
JSP-файлы (шаблоны страниц или их фрагментов) можно править в WYSIWYG-редакторе, соответственно изменять дизайн стало
проще.
JSP - далеко не единственный template engine для Java. Если Вы не в восторге от синтаксиса
JSP, но хотите использовать шаблоны, попробуйте
Velocity,
Castor. Но учтите: JSP - это мэйнстрим,
который всерьез и надолго, так что будьте бдительны!
2.3. Первые рацпредложения от передовиков: JSP Model1
Все правильные веб-программисты должны дружно соглашаться с мыслью, что для создания действительно
управляемой системы, причем быстрого, просто-таки необходимо добиться четкого разделения
труда между участниками процесса разработки. Для тех, кто не соглашается,
пишутся подробные статьи и книги (см. в Списке литературы), где стратежно доказывается разными способами, что
представление (шаблоны) должно быть отделено от содержания (код). Что интересно,
у западных авторов главной причиной бед выступает этакий мифический
дизайнер, которому тяжело видеть код, он не переносит кода как явления (у наших гуманитариев, которые
говорят по ящику про культуру, похожие симптомы), а если и переносит,
то обязательно что-нибудь там испортит. Выход - не давать кода
супостату-вредителю! Пусть
занимается только выводом значений из переменных/файлов/БД. А собственно код,
который что-то делает, разместить в другом месте.
На самом деле дело не в каких-то мифических дурных дизайнерах.
Здесь нет ничего нового - все тот же старый принцип "разделяй и властвуй".
С обособлением представления и кода проект действительно становится проще
развивать. При разумном дизайне с каждой из этих, в общем-то, довольно разных частей легче справиться (почему-то
лезет в голову старая притча о дедушке, ломавшем связку прутиков вместе и по
отдельности в целях морально-боевой подготовки молодежи; нам же как раз требуется
сломать эти прутики).
Схема построения веб-приложения, когда за доступ к данным отвечает разработанный
под задачу набор javabeans, а все остальное берут на себя JSP-страницы, в
литературе (изначально термин появился в
J2EE
Blueprints) называется JSP Model1. Вышеупомянутые javabeans являются "моделью"
данных, с которыми работает приложение (любители паттернов скажут, что здесь
используется паттерн facade), а JSP-страницы, работающие с экземплярами этих
javabeans - "представлением", но при этом еще и производят всю
обработку.
Скажем, работаете Вы с покупателями - создаете класс customer с методами getXXXX
и setXXXX, работаете с продуктами - создаете класс product, и так далее. Эти
объекты пусть сами себя в начале работы прочитывают, а в конце - сохраняют.
Метода довольно очевидная, ничем веб-специфичным тут даже и не пахнет :-)
Рис.1. Иллюстрация JSP Model1
2.4. Стоматологи рекомендуют: JSP Model2
JSP Model1 действительно является шагом вперед и избавляет разработчика от
применения "русского народного метода copy/paste" (по меткому выражению
А.Лебедева) для получения и
сохранения данных, теперь этим занимаются javabean'ы, с которыми мы работаем.
Но внутри страниц все равно остается масса
кода (при виде которого наш нервный дизайнер придет в ужас, как пишут люди с
Запада), отвечающего за обработку данных. А это и есть бОльшая часть кода
приложения.
Этот код предложили размещать в специального рода классах (в литературе
используется подходящий по смыслу термин "действие" - action), заточенных под конкретные виды
обработки. Действия эти подгружаются в специальный сервлет ("сервлет действий"),
который становится неотъемлемой частью приложения. Подведем итог:
Javabeans отвечают за связь с данными ("model"), JSP-страницы - за представление
данных ("view"), а сервлет действий - за обработку ("controller"). Осталось
только добавить, что мотив (паттерн) MVC уже давно и успешно применяется
в разработке традиционных приложений с развитым пользовательским интерфейсом (в руководствах по Swing он, мягко говоря,
проходит красной нитью по любой странице). Итак, эта улучшенная схема получила название JSP
Model2.
Рис.2. Иллюстрация JSP Model2
2.5. Борьба с формами во имя формализма
Подход в стиле JSP Model2 неплохо решает проблему структуры приложения, но
остается ряд других проблем. Главная из них - организация работы с формами -
способна создать неслабую головную боль программисту при большом их количестве. Для каждой формы
(на примере редактирования данных) нужно:
- Получить от модели значения и напечатать в форме
- Получить ответ и проверить допустимость значений (валидация)
- Если допущены ошибки, снова напечатать форму с сообщениями, что и как надо
перезаполнить
- Если все заполнено правильно - записать значения в модель.
- Плюс еще необходимо не давать повторно заполнить форму, а то есть много
охотников нажимать на кнопки.
Формы - это по сути основной инструмент веб-программиста, так что задачи
валидации довольно важные, а единой общепризнанной методы не разработано, каждый
спасается как может (к сожалению, до внедрения Xforms пока далеко, а
существующие HTML-формы таковы, какими их задумали в давние времена). Обычно валидация
поручается либо специально созданному сервлету, либо применяется комбинация
специального класса и JSP-страницы. Об этом поподробнее.
Например, есть JSP-файл passport_form.jsp с формой для паспортных данных
(в action для формы задается имя jsp-файла валидации, например,
validate_passport_form.jsp), для формы создается javabean, каждая из переменных которого соответствует какому-либо
паспортному данному из формы, имеет то же имя; для указания типа этой переменной
используется один из заранее подготовленных классов (TextElement, RadioElement,
etc), сводящихся к строкам (String) и представляющих разного рода элементы формы:
// Фрагмент файла passportForm.java
public class passportForm {
TextElement firstname = new TextElement();
TextElement lastname = new TextElement();
RadioElement gender = new RadioElement();
...
public String getFirstname() { return firstname.getValue(); }
public void setFirstname(String s) { firstname.setValue(s); }
public String getLastname() { return lastname.getValue(); }
public void setLastname(String s) { lastname.setValue(s); }
public String getGender() { return gender.getValue(); }
public void setGender(String s) { gender.setValue(s); }
...
}
Далее, в JSP-файле validate_passport_form.jsp, обрабатывающем форму, применяется примерно такая
конструкция:
// Фрагмент файла validate_passport_form.jsp
...
<jsp:useBean id="passportFormValidator" class="my.package.name.passportForm"
scope="request">
<jsp:setProperty name="passportFormValidator" property="*"/>
<jsp:useBean/>
...
<%
if(!passportFormValidator.validate()) {
errorMsg += passportFormValidator.getValidationError();
errorDetected = true;
}
if(errorDetected) {
// Выводим сообщение об ошибке и потом снова форму
%>
<jsp:include page="passport_form.jsp" flush="true"/>
<% } else { %>
// Перенаправляем на следующую страницу
<% } %>
Результатом выполнения этой конструкции является установка свойств
класса passportForm по всем значениям формы при помощи интроспекции (немного
сокращает работу по переносу данных).
Для проверки набора полученных значений в классе обычно реализуется метод
validate(), который и вызывается в нашем примере на странице валидации.
Фактически, класс passportForm - это модель для формы, включающая возможность
валидации, а JSP-страница валидации выступает в данной ситуации как контроллер.
(Oops! I did it again, baby! :-)) А мораль получается такая: от паттерна MVC при
создании пользовательского интерфейса никуда не скроешься, отовсюду вылезает.
Еще один "больной" вопрос после валидации - это обработка повторной отправки
форм. В нескольких статьях для решения этой проблемы предложена техника использования
сеансовых маркеров, которые проверяются на повторность перед обработкой формы.
Подробнее данная техника описана в статьях (см. Список литературы).
2.6. Маленькие радости JSP custom tags. JSTL/EL.
В спецификацию JSP и Servlet API практически сразу была заложена возможность создания
программистами своих частных тегов, в переводах важно называемых "пользовательскими
дескрипторами". Например, если Вы любите делать свои счетчики, то хорошим
средством для этого будет создать свой класс дескриптора от
javax.servlet.jsp.tagext.TagSupport и затем подгружать его на странице, тем
самым максимально сократив на ней присутствие кода. Кроме создания класса, программист еще
должен описать свой тег со всеми аттрибутами и т.п. в XML-файле с рекомендуемым расширением .tld (tag
library definition) и подключить это описание в JSP-страницу (обратите
внимание, что dtd для определений дескрипторов для версий JSP 1.1 и 1.2
различаются). Подстановка счетчика в самом примитивном случае выглядит так:
<mytags:counter/>.
К сожалению, по-настоящему "большие" теги, рисующие значительные фрагменты страниц, делать
весьма неприятно, т.к. нет возможности использовать JSP внутри таких тегов
и весь вывод оказывается внутри конструкций печати класса поддержки (то, с чего мы начинали в
сервлетах). Для таких целей приходится просто подставлять JSP-файлы с указанием
их местоположения (в новых спецификациях JSP-файлу при подключении можно передавать
параметры).
Тем не менее, жить в JSP со своими тегами некоторым так понравилось, что началось строительство
целых библиотек тегов (tag libraries), наиболее известна из них библиотека
Jakarta Taglibs. Наконец для JSP появилась стандартная
библиотека -
JSTL. Она
содержит теги условных конструкций (да здравствует программирование на XML!), форматирования, работы с XML и базами
данных. Для работы с собственно значениями данных в JSTL используется язык
выражений (EL - Expression Language), представляющий собой компиляцию из
JavaScript и XPath. Еще в JSTL есть теги для определения и использования простеньких шаблонов
JSP-страниц, в каждую позицию которых можно подключать значения или содержимое
файлов (честно - эти шаблоны ничего особенно не облегчают и улицы чище не
становятся).
Так что если вам позарез понадобится что-то подобное - проверьте, нет ли этого
тега в JSTL. И не теряйте чувства меры - если внутри вашего JSP-файла много
java-кода, его будет значительно проще читать в чистом виде, не
замутненном никакими тегами:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
...
<h4>Customers living in the USA</h4>
<c:forEach var="customer" items="${customers}">
<c:if test="${customer.address.country == 'USA'}">
<c:out value="${customer}"/><br>
</c:if>
</c:forEach>
Это - пример использования некоторых тегов JSTL, блестяще демонстрирующий
открывшиеся возможности и т.д. и т.п. Спросите Вашего знакомого дизайнера, что
он об этом думает :-)
2.7. XML - семь бед, один ответ. Model 2X.
Тем временем прогрессивное человечество XML-лило, причем чем дальше, тем больше.
Некоторых западных авторов на этой почве вообще заклинило, и они принялись за
толстенные талмуды о том, как использование XML поможет
общению с друзьями, накормит голодных и вообще спасет всех и вся - создавать, по
выражению некоторых здравомыслящих людей, "marketing hype". Ну, где не бывает
перегибов. Когда-то я пошутил, что уж программировать-то на XML не станут, через
полгода понял, что жестоко ошибался (правда, в большинстве случаев, когда
говорят о программировании на XML, имеют в виду работу с XML-файлами через DOM
или SAX). Сейчас меня тревожит только одно: почему медлит Bruce Eckel с "Thinking in XML"? Возмущению масс по
этому поводу просто нет пределов.
Важность XML видна невооруженным глазом. Процитирую строчку из документации к
Apache Cocoon: "Об XML говорят решительно все. XML здесь, XML там. Все сервера
приложений поддерживают XML, каждый хочет сделать B2B с XML,
веб-сервисы с XML, даже базы данных с XML."
Однако от применения XML там, где он подходит, польза действительно большая. В
частности, там, где требуется иметь несколько альтернативных выводов - HTML,
WML, PDF etc. По идее, для генерации XML можно было бы использовать JSP, но
встает проблема "правильной оформленности". А именно, средний JSP-файл у
среднего разработчика изобилует всякими одиночными <hr>, <br> и т.д.
Такие элементы нужно "закрывать" - блюсти трудовую дисциплину. Но это еще
полдела - если Вы пользуетесь custom tags, нужно убедиться, что они выдают
правильно оформленный код.
А еще было бы неплохо иметь правильно оформленными сами JSP-файлы, их можно было
бы генерировать XSLT-преобразованиями или стилизовать при помощи XSL. Поэтому в Sun в последних спецификациях
ввели альтернативный (XML-)синтаксис для JSP, где "нехорошие" элементы <% ...
%> и <%@ ... %> заменяются конструкциями соответственно
<jsp:directive...> и <jsp:scriplet>. Для правильно оформленных
(well-formed) JSP-страниц Sun-овцы предложили термин "JSP-документ". А саму
схему, когда вывод сервлета/JSP в виде well-formed XML передается
XML-трансформатору, называют Model 2X.
2.8. "Advanced JSP": в космосе тоже проблемы
Как известно, без труда не выловишь и рыбку из пруда.
Для создания приложения в духе JSP Model2 от программиста потребуется не только
разработать собственно компоненты приложения (модельные классы, классы действий,
JSP-файлы), но и код поддержки, связывающий эти компоненты - код
сервлета действий и базовые классы действий и обработчиков событий и др., иногда
используют термин "каркас веб-приложения". Все это
можно написать и самому, но зачем в данном случае изобретать велосипед?
Уже давно существуют готовые к использованию пакеты поддержки JSP Model2
(довольно близко к этому понятие "MVC frameworks"), наиболее известным из
которых является Apache
Struts.
2.8.1. Struts: хозяйке на заметку
Struts предоставляет возможность связывать действия и URI в конфигурационном
файле struts-config.xml (для него написаны даже редакторы на Swing, например,
Struts Console). Когда происходит обращение по URI, сервлет действий проверяет
наличие кэшированного экземпляра действия, если его не оказывается, создает
новый. Действия предполагаются thread-safe, так что не стоит злоупотреблять
переменными.
Struts наиболее удобен при совместном использовании с JSP, хотя возможны и
другие варианты (например, Velocity). Для JSP в Struts имеется несколько библиотек тегов - серверная
логика, вывод свойств, шаблонные и т.п., часть пересекается с JSTL. В частности,
JSP-файл с формой может выглядеть вот так (причем фокус ввода будет на элементе
"username"):
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html:html>
...
<html:errors/>
<html:form action="/logon" focus="username">
...
<bean:message key="prompt.username"/>
<html:text property="username" size="16"/>
<bean:message key="prompt.password"/>
<html:password property="password" size="16"/>
...
<html:submit>
<bean:message key="button.submit"/>
</html:submit>
<html:reset>
<bean:message key="button.reset"/>
</html:reset>
</html:form>
...
Для работы с формами в Struts предлагается создавать javabeans с интерфейсом ActionForm,
которые регистрируются в конфигурации Struts и в этом же файле связываются с действиями -
для действия опционально указывается, какую форму (javabean) оно обрабатывает, так что при
выполнении в действии основного метода perform() уже имеется заполненный javabean
с данными, полученными из формы. Для проверки данных при написании form-bean
пишется метод validate(), предусмотренный в интерфейсе ActionForm.
Рис.3. Так работает веб-приложение на основе Struts
Судя по количеству статей (см. Список литературы), Struts пользуется на Западе невероятной популярностью
- практически каждая разработка класса "MVC/Model2 framework" содержит файл с
подробным объяснением, "чем-же-она-немного-лучше-Struts".
В пользу Struts говорит простая архитектура, внятная документация и участие в его разработке лидера команды Tomcat
- Craig McClanahan.
Про применение Struts за последние три года написано несколько десятков статей
(правда, практически одинаковых, см. Список литературы) и с полдесятка книг,
есть примеры, в том числе административный режим самого популярного
сервлет-контейнера
Tomcat, который дает вам
возможность настраивать сервлет-контейнер через веб-интерфейс без ручной правки server.xml.
Мало того, предприимчивые граждане из компании
ScioWorks решили срубить на
Struts бабла и сделали для него настоящую специализированную IDE под названием
Camino (годовая лицензия в районе
$200), о как! Случаи - они всякие бывают. Кстати, приятная программка.
2.8.2. Про Tiles и шаблонное мышление.
Как известно, JSP-страницы можно подключать друг в друга с помощью директив <%@ include
file="myfile.jsp" %>, что позволяет компоновать из фрагментов. Для
подстановки требуется указывать имя файла, что не есть хорошо, т.к. оно может
измениться и его придется переправлять везде, где подключали (всего сразу не
предусмотришь? ;-)). Один умелец по имени
Cedric Dumolin сделал по этому поводу небольшой
tool под названием
Tiles, который
позволяет зарегистрировать имена страниц в специальном XML-файле
(tiles-def.xml), привязав к каждой из них строковый идентификатор и определив
для нее ряд параметров с именами и значениями для подстановки:
<definition name=".products.index" extends=".basic.layout">
<put name="site_title" value="Our Products"/>
<put name="site_left_menu" value="/products/left_menu.jsp"/>
<put name="site_content" value="/products/content.jsp"/>
</definition>
Обратите внимание на аттрибут "extend". Глянем теперь, что такое
".basic.layout":
<definition name=".basic.layout" path="/index.jsp">
<put name="site_title" value="Our company site"/>
<put name="site_header" value="/site_header.jsp"/>
<put name="site_menu" value="/site_menu.jsp"/>
<put name="site_left_menu" value="/left_menu.jsp"/>
<put name="site_content" value="/content.jsp"/>
<put name="site_footer" value="/site_footer.jsp"/>
</definition>
В общем, параметры эти, определенные в definition для .basic.layout,
могут наследоваться и переопределяться в другом definition - .products.index,
причем вместо конкретных URL вроде "/site_menu.jsp" в значениях
параметров может быть опять же идентификатор вроде ".site.menu". В самом же
файле index.jsp все параметры подставляются при помощи Tiles taglib:
<html>
<header><title><tiles:getAsString name="site_title"/></title>
<body>
<tiles:get name="site_header"/><br/>
<tiles:get name="site_menu"/>
<table>
<tr>
<td width="30%"><tiles:get name="site_left_menu"/><br/><td>
<td width="70%"><tiles:get name="site_content"/><br/><td>
<tr>
</table>
<tiles:get name="site_footer"/>
</body>
</html>
Штука состоит в том, что при помощи маскировки дефинициями можно уменьшить число
файлов! В приведенном примере, в частности, отсутствует файл /products/index.jsp
- значения параметров .products.index применяются к файлу /index.jsp. Для того,
чтобы этим воспользоваться, нужно завести Action в конфигурации Struts и указать
ему имя дефиниции, плюс подключить и настроить сервлет Tiles.
Технику Tiles немного напоминает применение шаблонов JSTL, но никакой
маскировки, наследования значений и передачи вставляемой странице параметров в
этих шаблонах получить нельзя.
2.8.3. MVC-реализации: ракообразие многообразных.
Реализаций MVC на базе сервлетов существует довольно много. Некоторые из них
чуть-чуть отличаются от Struts (
Maverick), другие - побольше
(
TurboM2). Нет особого смысла перечислять их все. Более интересна часть
MVC-реализаций, отошедшая от JSP в качестве средства представления - такие, как
StrutsCX
и
OXF.
В StrutsCX представлением занимается специальный сервлет, который получает из
сессии, запроса и контекста приложения разнообразные данные -
бизнес-данные, данные форм, ресурсные свойства, сообщения об ошибках и т.д, создает на их основе
первичный XML-файл - "сборную солянку" и доводит его до "готового" вида набором
заданных пользователем XSLT-преобразований. По сути, это реализация подхода Model
2X.

Кроме всего прочего, у Struts есть двойник и на Apache под именем Turbine.
Похоже, он применяется в основном в проекте
JetSpeed (см. далее) и особо не себя не рекламирует.
2.8.4. По ту сторону: что же дальше?
Теперь - внимание: если Вы отважились на использование JSP Model2 - на основе
своего или готового каркаса (Struts), сделали несколько своих
jsp-тегов и разобрались с валидацией форм и ресурс-пулами (в том числе с
интернационализацией приложения) - поздравляю, Вы используете "Advanced JSP"
(так, по крайней мере, называется одна из книг, описывающая данные вопросы).
Хоть ваши JSP-файлы все равно содержат кучу серверного кода, пусть даже и в виде
нагромождения custom tags (в вышеприведенном примере формы этот код будет совершенно
непонятен WYSIWYG-редакторам), пусть с ними все равно приходится работать больше
программисту, нежели дизайнеру/верстальщику. Как бы там ни было, Ваше приложение можно
считать прилично построенным и относительно управляемым. Если Вам
придется где-то что-то менять, Вы легко найдете нужные места в одном-двух
небольших файлах.
Однако эта сельская идиллия закончится аккурат тогда, когда Вам надо будет
делать несколько следующих проектов. Под каждый из них
придется создавать и обслуживать тучу JSP-файлов, действий, валидаторов форм,
обработчиков событий и модельных классов. Если области применения приложений перекрываются (а это
имеет место сплошь и рядом), то резко обостряется проблема code reuse. Дело уже
не в концептуальной правильности проекта, а в том, что если эту общую часть
функциональности (которая может включать как модели, так и представление с
контроллером) придется заменять, эту замену хотелось бы делать в одном месте.
Если обратиться за аналогиями к строительству, то станет ясно, что дома не
строят из отдельных песчинок, для постройки используются кирпичи или блоки
(пример с монолитными домами отбрасываем как подлую демагогию).
Разумному человеку в наше время не придет мысль снова писать
обычную электронную таблицу целиком на ассемблере, как это было когда-то с Lotus
1-2-3. При увеличении масштаба,
связанном с потребностью во все более сложных приложениях, просто необходима
более скоростная техника их создания, основанная на более "крупномерной", адекватной к задаче
терминологии, повышающей code reuse.
2.9. Специальные подходы:
"Мы пойдем другим путем!"
Пока мэйнстрим-технологии от Sun все совершенствовались, в обычном мире
веб-приложений на Java приходилось решать задачи, для которых вышеупомянутых усовершенствований было
явно недостаточно. Появились специализированные технологии, получившие признание
и зажившие самостоятельной жизнью в популярных открытых проектах. Здесь мы рассмотрим только два, но очень важных
проекта такого рода. Причем рассмотрим весьма поверхностно, ибо
подробное рассмотрение их в данной статье просто невозможно.
2.9.1. Apache Cocoon
Проект Apache
Cocoon позиционируется как
XML publishing framework (интернет-среда публикации, основанная на XML),
используется для генерирования проектной документации в Apache, а
исторически вырос из небольшого сервлета, производящего на сервере
XSLT-преобразования. Cocoon запускается как веб-приложение (cocoon.war) в
сервлет-контейнере.

Cocoon удовлетворит запросы самых неистовых XML-льщиков.
Основная архитектурная концепция Cocoon заключается в генерации больших
структурированных наборов XML-файлов на основе самых разнообразных исходных данных
(другие XML-файлы, реляционные БД, объекты
Java и др., даже файлы PHP) при помощи специальных java-классов - генераторов.
Полученные XML-файлы далее проходят ряд XSLT-преобразований в соответствии с
требованиями логики веб-приложения, и на выходе конечный пользователь, сделавший
ранее запрос, получает ответ в нужном формате - HTML, WML, SVG, RSS, PDF и т.п.
Все инструкции, что и как делать, если пользователь обратился по определенному URI,
записываются в большом XML-файле, называемом "карта
сайта" (sitemap). Фактически, это системный реестр: там регистрируются все
инструменты - генераторы, трансформеры, рендереры и т.п., которые используются
в системе, а далее следует формальное описание самого сайта в таком
стиле:
<map:pipelines>
<map:pipeline>
<map:match pattern="">
<map:redirect-to uri="wizard.html"/>
</map:match>
<map:match pattern="howto-wizard.html">
<map:act type="HowtoWizardAction">
<!-- XMLForm parameters for the HowtoWizardAction -->
<map:parameter name="xmlform-validator-schema-ns"
value="http://www.ascc.net/xml/schematron"/>
<map:parameter name="xmlform-validator-schema"
value="howto/schematron/howto-xmlform-sch-report.xml"/>
<map:parameter name="xmlform-id" value="form-feedback"/>
<map:parameter name="xmlform-scope" value="session"/>
<map:parameter name="xmlform-model"
value="org.apache.cocoon.samples.xmlform.howto.HowToBean"/>
<!-- Content transformation logic -->
<map:generate src="howto/{page}.xml"/>
<map:transform type="xmlform" label="xml"/>
<map:transform src="stylesheets/wizard2html.xsl"/>
<map:transform src="context://stylesheets/xmlform/xmlform2html.xsl"/>
<map:serialize type="html"/>
</map:act>
</map:match>
...
Cocoon предусматривает свои серверные страницы (а как же без них? :-)) -
eXtensible Server Pages (XSP), которые являются XML-файлами с возможностью внедрения
кода на нескольких языках (например, Java). При обработке соответствующим
генератором XSP-страницы преобразуются в XML-файлы, которые включаются в процесс.
Для работы с формами предлагается использовать технику, которая, как кажется
разработчикам, навеяна Struts. Для модельного представления формы создается
javabean, сама форма делается в XML-файле из элементов специального namespace - xmlform,
еще в одном XML-файле можно устроить элементарную валидацию (выглядит
тяжеловато), еще нужно спрограммировать на Java класс контроллера, содержащий
методы prepare, reset, perform (туда запихиваются все редиректы для сложных
форм), плюс записать это все в sitemap. И "спи-отдыхай".
Судя по текущей документации и работоспособности примеров, отвечающих за формы,
сессии и вообще все связанное с интерактивностью системы, этими вопросами
команда Cocoon занимается только сейчас. На мой взгляд, лучшим применением для
Cocoon на настоящий момент будет генерация больших, но в основном статических
сайтов с небольшим числом форм - документация, новостные сайты; делать на Cocoon какие-либо административные
режимы и/или интранет-порталы не стоит. Только если Вы очень любите
"программирование на XML".
2.9.2. Lutris Enhydra/XMLC
Открытый проект
Enhydra создан компанией
Lutris. Enhydra - это сервер
java-приложений, веб-сервер, сервлет-контейнер и ряд специализированных утилит.
Проект стартовал в 1998 году еще до появления JSP, так что в Enhydra
быстро сделали (это было определенно поветрие!) очередные server pages, а
назывались они JDDI (Jolt). Там все было как надо - в HTML
вставлялся при помощи специальных тегов java-код, далее на основе этого файла
системой создавался и компилировался презентационный java-класс (живет внутри
презентационного сервлета в Enhydra-приложениях, а расширение для файлов - .po).
Но с Jolt-страницами товарищи из Lutris не преуспели, да и Sun насчет JSP недолго
медлила. В результате в Enhydra применили действительно интересный подход
для презентационного слоя веб-приложений - XMLC. Технология XMLC (XML Compiler)
интересна тем, что позволяет максимально, насколько это вообще возможно,
отделить оформление веб-приложения от программного кода.
Способ прост, как все гениальное. В шаблоне страницы или
фрагмента есть несколько точек, куда будет подставляться информация. Обычно
дизайнер заполняет такие места условным текстом прикольного содержания, а
полученный файл у них называется "рыба" (примеры см. у Темы Лебедева). Чтобы
сделать из такой страницы рабочий шаблон, дизайнер подставляет в нужный тег,
скажем, блок <SPAN>, аттрибут ID с уникальным значением, так что
получается <SPAN ID="AboutUs">. От дизайнера на этой
странице больше ничего не требуется (и заодно уменьшаются опасения программиста,
что дизайнер испортит программный код). Причем с этим файлом, который
по-прежнему содержит приколы и при этом уже является рабочим шаблоном,
дизайнер может работать в каком угодно редакторе.
Кроме уникального значения аттрибута ID для идентификация элемента,
возможно включение элемента в одну или несколько формальных групп (для совместной
обработки) при помощи аттрибута CLASS (в данном случае элемент SPAN относится к двум
классам):
<SPAN ID="AboutUs" CLASS="class1
class2">Bla-bla-bla</SPAN>
Подготовленная дизайнером страница (about_us.html) скармливается "XML-компилятору". Эта
программа создает на основе анализа HTML/XML-кода java-класс (в нашем случае -
about_us.java), содержащий DOM-объект для данной страницы и набор методов, в том числе
методы для получения помеченных нами элементов (в нашем случае -
getElementAboutUs()). Полученный java-файл компилируется.

Создав экземпляр класса about_us в своей программе, программист получает
полное дерево DOM с возможностью очень быстрого изменения содержимого помеченных
элементов (поскольку для их получения в классе about_us уже существуют специальные методы).
Часть элементов можно удалить (помеченную специальным классом типа "remove_me"), можно
удалить, и т.п. После проведения всех нужных действий над рабочим экземпляром
класса about_us из него одной строчкой создается обратно HTML/XML-файл:
System.out.print( about_us.toDocument() );
При помещении на сервер новой версии шаблона соответствующий класс
автоматически перестраивается (легко менять дизайн).
Подготовка форм к использованию - частный случай: все элементы
ввода в форме метятся
уникальными идентификаторами, так что при подготовке можно удобно выставить
значения и правильный URL для action. Правда, вся обработка ложится на сервлет
валидации, написанный специально под форму, но работать с ним будет просто.
Разумно будет предусмотреть в шаблоне формы
места для вывода ошибок, так что при возникновении оных сервлет валидации
опять-таки создаст экземпляр класса формы, проставит там все полученные значения, где надо -
выведет сообщения об ошибках (что-то вроде setCreditCardError("Липовый номер!")) и, получив из
DOM строку, отправит ее пользователю.
В отличие от рассмотренного выше "Advanced JSP", содержащего мешанину дополнительных тегов
- условий, итераций, форматирования, а в случае плохого дизайна веб-приложения - даже
транзакции (есть в JSTL и такие дескрипторы), XML/HTML-шаблоны в случае XMLC не содержат практически ничего
чужеродного и могут редактироваться дизайнером "на лету". В "Advanced JSP" программисту придется реализовывать custom tags,
создавать файлы определений (.tld - tag library definitions), создавать form
beans и писать в JSP-файлы много всяких страшилищ вроде
<myns:dosmth-iterate bla1="Bla-1" bla2="<% request.getParameter("bla3") %>" ...>
В общем, по части облегчения жизни программиста на стороне XMLC-шаблонов -
однозначное преимущество. Это, пожалуй, единственный случай, когда работает
дурацкий рекламный оборот "Больше шампуня за меньшие деньги". И заметьте,
никакой рекламной помпы - об XMLC знает не так уж много разработчиков, а зря.
Между прочим, XMLC можно "завернуть" и в Cocoon,
например, через соответствующие классы-генераторы. И дизайнер будет работать с
разметкой, а программист - с программой, и все окончится в каком-нибудь SVG, да
еще и на корейском языке. Вот такая загогулина получается.
2.10. Паттерн "Винегрет"®: портлеты
Рассмотренные выше технологии улучшают структуру веб-приложений, повышают их
управляемость, но в целом процесс пока напоминает ручную сборку автомобиля при
помощи в основном напильника (дорого и медленно). Поскольку среди заказчиков тех, кому хочется иметь "Кобру",
да еще и постоять за ней в очереди, немного, этих улучшений нам все равно
недостаточно. Иными словами, нам нужна упоминавшаяся в конце п.2.8 "крупномерная" технология,
технология сборки веб-приложения из довольно больших блоков.
Больше всего "ручной сборки" сосредоточено на уровне "представления". Шаблоны
фрагментов и страниц могут вкладываться один в другой сложным образом, причем если пользователь
находится в одной роли, ему могут показать одно, а в другой - совершенно другое.
Как автоматировать построение "представления"? Разговор продолжится потом, а
сейчас об одном частном (но важном) случае.
Понятие "портлет" используется разработчиками java-порталов. Для создания больших порталов на Java
давно уже существуют корпоративные решения, поставляющиеся вместе с "большими"
веб-серверами - IBM WebSphere, Sun IPlanet, Bea WebLogic и др.
Спецификация для портлетов находится сейчас в стадии разработки, поэтому вместо
точного определения используем интуитивное. Портлет - это
небольшое веб-приложеньице, функционирующее в общей сетке (layout) портала,
выполняющее свою функцию - показывать последние 10
новостей, взятые в RSS с сайта NNN, проводить опрос, рекламировать товар
и т.п. Пользователь в определенном разделе портала работает с доступными ему
портлетами. Выстраивать систему из таких кусочков проще и приятнее. Особенно
приятно было узнать, что существует свободная реализация контейнера портлетов от
Apache -
Jetspeed, в котором,
по словам людей, работавших с WebSphere Portal, портлеты реализованы практически так же.

JetSpeed работает внутри сервлет-контейнера. Это набор классов и интерфейсов
(Portlet API) и базовая программа для
портала, позволяющая создать структуру разделов сайта и наполнить ее Вашими портлетами.
Портлет в JetSpeed - это класс, удовлетворяющий Portlet API (интерфейсы Portlet,
PortletState, Cacheable, Refreshable). Основными методами в Portlet API являются
методы инициализации (init), конфигурирования (set/getPortletConfig),
получения/сохранения аттрибутов (set/getAttribute), получения экземпляра
(getInstance), возврата кода HTML/XML (getContent).
Пользователь работает с экземпляром портлета. Каждый такой экземпляр
дополнительно к основному состоянию (когда он просто показывается)
может быть 1) закрытым, 2) максимальным и 3) минимальным по размеру (интерфейс
PortletState); соответствующие значки показываются на панели портлета, если
таковая выводится. Портлеты могут иметь настраиваемые под пользователя параметры
(customization). Каждый портлет может иметь "версию для печати".
На сайте портлеты содержатся в контейнерах PortletControl,
которые могут вкладываться друг в друга.
Рис.6. Так может выглядеть портлет в Jetspeed (панель включена)
Для долговременного хранения экземпляров портлетов (пользователь ушел на обед
;-)) используется PersistentManager, где можно задать, куда и как Вы будете
складывать данные неактивных объектов. Можно создавать портлеты в стиле MVC, где
отдельно задается view (JSP, Velocity, XSLT, ...). JetSpeed
построен на основе MVC-реализации Apache Turbine, которую мы уже упоминали, так что практически
вся функциональность реализована через действия.
Портлеты показываются пользователю на странице в сетке (layout), причем можно
переключаться между несколькими такими сетками (можно пользоваться стандартными,
можно задать свою) и задавать в них расположение портлетов.
Для сайта предусмотрен набор расцветок (skins), который также можно расширить.
Для навигации верхнего уровня можно создавать разделы (pans). Для разных типов
клиентов (например, HTML- и WAP-броузеров), языков, стран, групп пользователей,
даже для отдельных пользователей в Jetspeed можно
построить совершенно разные подсайты со своей страничной структурой.
Jetspeed хранит все настройки в
едином реестре, который может читаться как из XML-файла, так и из реляционной
базы данных.
Jetspeed Portlet API включает средства для работы с наборами прав доступа,
пользовательских ролей, групп, различного
кэширования и т.д. Рассказывать обо всем нет смысла, смотрите Jetspeed javadocs
и наслаждайтесь - это грандиозная система, летать на которой нужно долго учиться.
А Portlet API подобно приборной панели МИГ-31 :-)
Аналогами JetSpeed среди Open-Source портлет-контейнеров являются
Liferay (построен над Struts!) и
jPorta.
3. Играем в кубики:
компонентное программирование.
3.1. Спаси мир с компонентами!
Попробуем суммировать основные тенденции, прослеживающиеся в развитии подходов
для построения веб-приложений из приведенных примеров:
- Везде, где требуется обмениваться данными с пользователем (главным образом, посредством
форм), прямо-таки напрашивается необходимость применения MVC;
- Дизайнеры/верстальщики по-прежнему хотят работать не с java-кодом, а со
страницами, фрагментами, стилями (в т.ч. в WYSIWYG-редакторах);
- В сложных веб-приложениях происходит выделение такого понятия как ресурсы
(resource-bundles, properties-files - особенно для i18n);
- На уровне "контроллера" в веб-приложениях напрашивается поддержка событий;
Симптомы очень напоминают другой случай, который имел место для "традиционных"
приложений, или "rich clients".
Рис.7. Насколько сложно делать "rich"- и "web"-clients сначала и
потом.
Под "web-clients" понимаем JSP-приложения, хотя бы и Model2.
Похоже, здесь мы добрались до главной идеи данной статьи. Раз возникает
необходимость быстрой разработки приложений, взаимодействующих с пользователем
(в нашем случае - через веб-интерфейс), значит, рано или поздно дело кончится
разработкой компонент и созданием программ из этих готовых компонент визуально в
этаком Delphi. Я говорю о серверных компонентах.
Этот путь уже прошли "традиционные" приложения; раз столько
функционала переносится в веб, неудивительно, что этим же путем могут пойти (и
уже идут) веб-приложения, этот путь уже обкатан.
Будущее веб-приложений - за серверными компонентами пользовательского интерфейса.
Каждый компонент отвечает за формирование своего элемента интерфейса и его
обслуживание при работе системы с пользователем. Например, этот элемент интерфейса может
быть блоком голосований, может быть рамкой вокруг блока голосований или строчкой
с вариантом выбора в блоке голосований. Главное, что компонент сосредотачивает в
себе строго определенную переносимую часть интерфейса веб-приложения ("разделяй и властвуй").
Компонент - это "черный ящик", экземпляр которого хранит информацию о
состоянии (т.е. связанные с ним данные) и способен реагировать на события в
зависимости от своего "состояния".
Для клиента все пройдет незаметно - он будет по-прежнему получать документ,
но на сервере вместо прихотливых комбинаций "серверных страниц", сервлетов,
javabeans, сделанных под конкретный проект, документу этому будет
соответствовать четко выстраиваемое дерево из стандартных (переносимых их проекта в проект) компонент.
Старое понятие "страница" (исходно означавшее статический HTML-документ) наполняется новым смыслом: это "форма",
аналогичная "формам" в системах быстрой разработки приложений с "традиционным"
интерфейсом вроде JBuilder. "Форма", на которой размещаются стандартные интерфейсные элементы
с готовой функциональностью, которую прикладной программист, создающий
веб-приложение, использует в своих целях.
Все компоненты, кто и когда бы их не
создал, будут обязаны единым образом поддерживать look & feel (читайте -
site skins), реакцию на события, MVC, встроенную локализацию и др. Прикладник,
использующий компоненты, не обязан знать их досконально на уровне кода, ему
достаточно использовать всего несколько public-методов.
Из такого набора можно в самом деле сделать функциональную систему, отвечающую
современным требованиям. Разумеется, сторонникам неограниченной демократии это
не понравится, но им, как правило, не нравится ничего, а погоды в исторических
процессах они не делают :-)
Далее в пп. 3.2 и 3.3 будут рассмотрены самые современные компонентные подходы,
представленные в больших популярных проектах. Поскольку русскоязычной
документации или статей по этим проектам на сегодняшний момент
нет вообще (cutting edge!), к ним придется подойти поконкретнее, в то же время пытаясь сохранить сжатость
и выбранный формат изложения.
3.2. Кто первый встал - того и тапки.
"Неофициальные" реализации компонентного подхода.
Пока для компонентного подхода к разработке веб-приложений в Sun вот уже года
два-три готовится "официальная" спецификация (см. п. 3.3), ретивые товарищи уже
пролезли "поперек батьки в пекло" и их web-component frameworks уже живут и
побеждают. Мне известны по крайней мере два примера, один из которых будет
рассмотрен ниже.
Интересно, что для разделения кода и представления в обоих из них использована
техника, взятая из рассмотренной выше Enhydra/XMLC.
Для понимания
излагаемого материала Вам необходимо прочитать предыдущий пункт касательно того,
что такое компоненты с точки зрения архитектуры веб-приложения.
Tapestry
Проект
Tapestry (с июня 2003 -
Apache Tapestry) на сегодняшний день - это наиболее известная среда веб-компонентов для Java.
Несмотря на использование Java, создатели Tapestry (как видно, нежно полюбившие
"программирование на XML") устроили все так, что большая часть свойств объектов
и отношений между ними задается формально, в XML-файлах, так что вместо изучения
API более важным для пользователя представляется изучение DTD XML-спецификаций.
Человеку, до сих пор работавшему с разными Server Pages, Tapestry покажется
другой планетой.
3.2.1. Компоненты Tapestry
С формальной точки зрения компонент в Tapestry - это объект с набором параметров
(помните в Delphi/C++ Builder параметры компонента в "Object Inspector"?),
использующий ряд внешних ресурсов - картинки, звуковые и видео-файлы, flash-ролики
и т.п., объединяемое в Tapestry понятием "assets". Компонент может включать в
себя набор более мелких компонент. Описание всех перечисленных составляющих
(параметров, ресурсов, субкомпонент и др.)
дается разработчиком компонента в специально созданном
XML-файле спецификации компонента, обычно имеющем расширение ".jwc".
Компоненты вкладываются друг в друга, а самые верхние компоненты вкладываются в
"страницу" (компонент высшего уровня). Как правило, каждому компоненту
сопоставляется HTML-шаблон, где с помощью помеченных элементов
(см. выше описание шаблонов Enhydra/XMLC)
задаются позиции включения компонент более низкого уровня.
Для меток используется аттрибут "jwcid" (Java web component id), а внутри
помеченного элемента может располагаться дизайнерский текст, элемент "рыбы",
который будет заменен при обработке шаблона. Например, для компонента,
выводящего список ссылок на страницы (почти как в учебнике по Tapestry),
HTML-шаблон pagesList.html может выглядеть так:
<tr jwcid="insertPageData">
<td jwcid="insertPageName">Главная</td>
</tr>
<tr jwcid="$remove$">
<td>Продукты</td>
</tr>
<tr jwcid="$remove$">
<td>О компании</td>
</tr>
Данный пример является неплохой иллюстрацией, как в шаблонах Tapestry
"затирается" дизайнерский код: все строки таблицы, следующие за строкой "foreach", будут
затерты при анализе шаблона. Все значения аттрибута jwcid, кроме "$remove$", являются
идентификаторами субкомпонент, определенных в XML-спецификации (в HTML-шаблоне
задается только порядок их вывода). Компоненту pagesList будет соответствовать
вот такая спецификация (pagesList.jwc):
<component-specification class="my.package.pagesList"
allow-informal-parameters="no">
<parameter name="title" java-type="java.lang.String" required="yes"/>
<parameter name="pages" required="yes"/>
<component id="insertPageData" type="Foreach">
<inherited-binding name="source" parameter-name="pages"/>
<binding name="value" expression="pageName"/>
</component>
<component id="insertPageName" type="Insert">
>binding name="value" expression="pageName"/>
</component>
<component id="link" type="PageLink">
>binding name="page" expression="pageName"/>
>binding name="disabled" expression="disablePageLink"/>
</component>
</component-specification>
В данной спецификации:
- java-type - это класс реализации компонента;
- informal-parameters - это аттрибуты элемента в HTML-шаблоне более
высокого уровня (например, страница), в который включается компонент pageList.
Говоря "разрешить informal parameters", мы делаем возможным доступ к значениям
этих аттрибутов внутри pageList;
- parameter - формальное объявление параметра. Для параметра
pages в данном случае тип не указывается, т.к. возможно несколько вариантов,
например, List и Iterator;
- component обозначает использование субкомпонента с
идентификатором id (в шаблоне pageList.html);
- type - это тип для субкомпонента. Тип может быть как
определенный пользователем, так и стандартный (Insert, Foreach и др.)
Стандартному типу не требуется спецификация, она уже есть в системе, так что остается
только передать стандартному компоненту значения параметров;
- binding позволяет данному субкомпоненту получить у
компонента верхнего уровня (контейнер, в нашем случае - pageList)
значение своего параметра name с помощью
выражения (expression) на языке OGNL;
- inherited-binding позволяет данному субкомпоненту получить
у верхнего компонента значение его параметра parameter-name как
значение своего параметра name;
Отметим, что с помощью тега <bean> спецификации можно привязывать к
компоненту javabean с настройкой особенностей его жизненного цикла.
Наконец, за конкретную программную реализацию компонента (при данной схеме подменять реализации ну
очень просто) отвечает java-класс, расширяющий базовый класс компонентов. Если
наш компонент слишком простой и ему не нужен HTML-шаблон, базовым классом будет
AbstractComponent и в классе реализации требуется определить метод отрисовки
render(). Если компоненту требуется HTML-шаблон, базовым классом будет
BaseComponent и метод render не переопределяется.
Если от класса реализации компонента не требуется хранить никакие переменные
(или эта задача решается через helper beans), можно вообще не создавать
собственный класс, использовав вместо него базовый. От класса реализации не
требуется хранить какие-то представления параметров: параметры компонент Tapestry
- это "трансцендентные сущности", которыми пользователь оперирует в
XML-спецификациях.
В нашем случае в спецификации компонента pageList в выражениях фигурирует
переменная pageName, с которой несколько субкомпонент связываются для
получения значений. Наш класс компонента должен содержать переменную pageName
типа String и bean-методы для работы с ней getPageName/setPageName.
package my.package.pageList;
import org.apache.tapestry.BaseComponent;
public class pageList extends BaseComponent {
private String pageName;
public void setPageName(String value) {
pageName = value;
}
public String getPageName() {
return pageName;
}
public boolean getDisablePageLink() {
return pageName.equals(getPage().getName());
}
}
3.2.2. Страницы Tapestry
Построенный таким образом компонент pageList можно использовать, например,
непосредственно внутри страницы, определив для него один или несколько
идентификаторов. Так может выглядеть спецификация этой страницы:
<page-specification class="org.apache.tapestry.html.BasePage">
<component id="insertNavigationBar" type="pageList">
<static-binding name="title">Главная</static-binding>
<binding name="pages" expression="Engine.PageNames"/>
</component>
...
</page-specification>
Страницы - это компоненты, с классами реализации, HTML-шаблонами и
XML-спецификациями, но без параметров, т.к. являются самыми верхними в деревьях
компонент. Зато страницы обычно имеют много свойств (переменные в классе
реализации, сопровождающиеся методами get/set).
Экземпляр страницы возникает в ответ на первый запрос этой страницы
пользователем, по обработке его обнуляет свои свойства в методе detach()
и размещается в специальном пуле до следующего (повторного) запроса любого пользователя.
На время повторного запроза экземпляр извлекается из пула, восстанавливает переменные,
участвует в обработке запроса, а потом опять "спрыгивает" в пул, так что при
малой частоте обращений к странице всех пользователей обслуживает один ее
экземпляр. Если в какой-то момент возникает приходят дополнительные запросы, а
первый экземпляр страницы временно занят, программное окружение создает
дополнительные экземпляры страниц на время обработки этих запросов. Если к
экземпляру в пуле долго не обращались, он уничтожается.
3.2.3. Валидация форм и i18n
В Tapestry нажатие на ссылку (стандартные компоненты Action и Direct) и submit для формы
(стандартный компонент Form) одинаковым образом инициируют
событие в приложении. Событие обрабатывает соответствующий слушатель,
реализованный на Java (например, в классе страницы) и зарегистрированный в
качестве слушателя в блоке использования компонента <component> ... </component>
в XML-спецификации (например, страницы).
Для валидации форм в страницах/компонентах создаются специальные объекты для
проверки (validation delegates). Спецификация страницы, содержащей форму, выглядит примерно в таком роде:
<component id="form" type="Form">
<binding name="listener" property-path="listeners.attemptLogin"/>
<binding name="delegate" property-path="validationDelegate"/>
<field-binding name="stateful" field-name="Boolean.FALSE"/>
</component>
<component id="showError" type="ShowError"/>
<component id="showValidationError" type="ShowValidationError">
<binding name="delegate" property-path="validationDelegate"/>
</component>
<component id="labelEmail" type="FieldLabel">
<binding name="field" property-path="components.inputEmail"/>
</component>
<component id="inputEmail" type="ValidField">
<static-binding name="displayWidth">30</static-binding>
<static-binding name="maximumLength">60</static-binding>
<static-binding name="displayName">Email Address</static-binding>
<binding name="value" property-path="email"/>
<field-binding name="validator" field-name="com.primix.tapestry.valid.StringValidator.REQUIRED"/>
</component>
...
Локализация в шаблонах Tapestry выполняется 1) посредством навешивания к тегам
(чаще всего используется <span>) аттрибута key для привязки идентификатора
локализованной строки из набора:
<span key="cart">Здесь должно быть написано "Корзина:".</span>
и 2) созданием локализованных версий самих шаблонов (распознаваемых анализом
имени файла, включающего символы страны и языка, как для properties-файлов).
Для ресурсов (assets) также могут быть локализованные версии.
Важной чертой Tapestry является его четко выраженная не-мэйнстримовость.
БольшАя часть работы переносится на специальные XML-спецификации, использующие
для доступа к параметрам выражения специального языка OGNL. Для хранения
сеансовых данных используется специальный объект Visit, а в качестве контекста
приложения - специальный объект Engine. При знакомстве
с Tapestry мне пришла мысль, что использование Servlet API, а в принципе - и
Java вообще - для него не так уж критично.
Если Вас заинтересовал подход, примененный в Tapestry - смотрите примеры от
разработчиков. Сейчас Tapestry интенсивно развивается, так что их документация
становится все лучше и лучше, может быть, даже скоро будет содержать корректные
имена методов :-)
Еще один web component framework, под названием "
Barracuda", в недалеком прошлом отпочковался от проекта
Enhydra. На мой взгляд, у них несколько бестолковая документация, но система
серьезная и заслуживает внимания, т.к. сочетает в себе лучшие черты XMLC, MVC и
компонентного подхода. Пользователей Enhydra-related технологий в России, видимо,
всегда было немного, а евангелистов - и подавно, и нет особых оснований ожидать,
что эта ситуация изменится. Возможно, следующая версия обзора будет содержать материал по Barracud'е.
3.3. Тяжелая артиллерия: Java Server Faces, или
Как железный конь придет на смену
крестьянской лошадке.
Технология JSF призвана решить проблему построения компонентной структуры
веб-приложения на Java на "высшем официальном уровне" - от самих разработчиков
Java, в рамках промышленного стандарта от корпорации Sun, утвержденного JCP.
Поскольку, по всей видимости, эта технология, несомненно, будет
позиционироваться как мэйнстрим для следующего
поколения веб-приложений на Java, к ней разумно присмотреться повнимательнее.
На настоящий момент спецификация и реализация (разрабатываемая параллельно с ней)
еще не являются окончательными - это Early Access 3, датированный маем 2003, так что какие-то детали
функционирования JSF-приложений могут измениться. Лучший способ познакомиться с
JSF - смотреть актуальные примеры. Кстати, пока Sun доделывает свою, официальную,
реализацию JSF, на
SourceForge некая команда уже развивает собственную open-source реализацию
под названием
MyFaces. Вот
она, волшебная сила спецификаций!
Еще до просмотра примеров и чтения спецификации я знал, что Sun не сможет
позволить себе шоковую терапию, предложив совершенно новую технологию и поэтому
JSF будет логическим продолжением стратегии Servlets/JSP + Model2 + custom tags.
Не революция, но эволюция - вот их девиз, и было бы странно, если бы оказалось иначе.
JSF-приложения и являются комбинацией перечисленного, запускаются в
сервлет-контейнере через контрольный сервлет FacesServlet и в них можно делать
все, что не запрещает Servlet API, в т.ч. работать с имеющейся функциональностью
"в старом стиле".
3.3.1. Java Server Faces - компоненты по версии Sun
С формальной точки зрения, от компонента требуется выполнений следующих функций:
- Преобразовывать данные в своем внутреннем представлении в готовые фрагменты
XML/HTML-кода ("encoding")
- Преобразовывать данные получаемых запросов в свойства и атрибуты внутреннего
представления ("decoding")
- Обрабатывать события, поступившие во время запроса, с перезагрузкой страницы
и изменением своего визуального состояния
- Осуществлять валидацию относящихся к себе данных запросов
Функции "encoding" & "decoding" могут быть делегированы специальным
программным рендер-слоям, формирующим код, оптимизированный под клиентское
устройство (конкретный броузер).
3.3.2. Цикл обработки запроса в JSF
Для понимания JSF следует разобраться с циклом обработки запросов (request proccessing lifecycle), тщательно
регламентированным в Спецификации. Цикл состоит из нескольких фаз (приведено из
Спецификации EA #3):
- Построение дерева компонент для запрошенной страницы (Reconstitute Request Tree). Если
страница уже запрашивалась в данной сессии, компонентам возвращаются их
состояния, установленные после прошлого запроса. Как частный случай сюда входит
заполнение форм.
- Получение параметров запроса (Apply Request Values). В течение данной фазы осуществляется обход
дерева компонент с передачей им (или делегатам) на декодирование параметров запроса. По
окончании фазы дерево компонент содержит всю полезную информацию из запроса и
при необходимости - всю информацию об ошибках при вводе и преобразовании типов.
Компоненты, у которых, судя по полученным значениям, "что-то произошло",
генерируют события (request events) и посылают их в контекст приложения.
- Запуск обработки событий, связанных с параметрами запроса (Handle Request
Events). В дереве компонент проводится последовательная обработка событий, сгенерированных на предыдущей фазе.
Обработка сопровождается изменением состояния ("подстройкой")
данной компоненты и возможно других ("соседних") компонент с необходимостью
далее отразить эти изменения визуально. Результатом обработки может также быть и
генерация выходного дерева компонент ответа (responce tree) с совершенно другим
набором компонент.
- Валидация значений (Process Validations). Последовательная проверка (self-validation) состояния компонент
с подстройкой request tree и responce tree. Если обнаружены ошибки, осуществляется
переход в фазу отрисовки ответа.
- Изменение модельных значений (Update Model). Применение набора значений, установленных в
исходном дереве компонент, к данным модели как корректных. Каждый компонент обновляет
свою модель; при обнаружении ошибок преобразования осуществляется переход в фазу
отрисовки ответа. По успешном завершении в исходном дереве компонент устанавливаются
нулевые значения.
- Вызовы приложения (Invoke Application). Осуществляется обработка событий уровня приложения
посредством контекста FacesContext. Результатом может быть генерация нового выходного
дерева или подстройка существующего.
- Отрисовка ответа (Render Responce). На этой фазе осуществляется генерация кода на основе
дерева компонент, возможно, с использованием оптимизирующих слоев.
3.3.3. JSF API
Компонент JSF с точки зрения программиста - это класс, реализующий интерфейс
javax.faces.component.UIComponent. В этом интерфейсе всего-навсего около
полусотни методов. Лентяи могут воспользоваться абстрактным классом
javax.faces.component.UIComponentBase, в котором определены почти все эти методы
кроме getComponentType().
В общем класс компонента UIComponentBase содержит:
- список (ArrayList) для субкомпонент - children, плюс методы для добавления,
извлечения, удаления;
- хэш (HashMap) для аттрибутов (название => значение) -
attributes, плюс методы для их установки и получения;
- список (ArrayList) - validators, плюс методы для добавления, удаления,
получения объектов-валидаторов;
- Общие методы - для связи с моделью, для decoding/encoding, метод validate() и т.п.;
- Методы для отрисовки (встроенной и сторонней - через рендер-слои)
Для специальных случаев (которых - большинство) в базовом наборе существуют специальные классы
компонент - UIGraphic, UICommand, UIForm и т.п.
Допустим, мы создаем компонент, выводящий меню, отрисовываемое в виде дерева или
в виде традиционного двумерного меню (один из примеров для JSF EA3).
- Для начала нам
потребуется определить модель. Модель представляется двумя классами - узлом
(Node) и деревом (Graph), хранящим иерархию узлов. Node имеет свойство Action
для привязки URL.
- Определим класс GraphComponent как производный от UICommand. В классе
определим метод getComponentType(), возвращающий строку "GraphComponent", и
конструктор, в котором будет происходить вызов метода AddActionListener из
класса UICommand (т.е., добавление "слушателя" событий, связанных с этим
компонентом).
- В классе "слушателя", образованном на основе javax.faces.event.ActionListener,
определяется метод processAction, обрабатывающий события javax.faces.event.ActionEvent.
В методе processAction получаем String-идентификатор узла (path), на который нажал пользователь
- вызовом getActionCommand() для полученного события, получаем экземпляр модели
(graph) и определяем нажатый узел:
Graph graph = null;
GraphComponent component = (GraphComponent)event.getSource();
String path= (String) event.getActionCommand();
FacesContext context = FacesContext.getCurrentInstance();
graph = (Graph) component.currentValue(context);
...
Node node = graph.findNode(path);
Нажатый узел надо "раскрыть", а текущий "раскрытый" - "закрыть" (изменение состояния
компонента).
- Наследованием от javax.faces.render.Renderer определяем отдельный класс отрисовщика
двумерного меню MenuBarRenderer.
Его задача на стадии Render Responce - выдать на основе модельных значений компонента
локализованный клиентский код (encoding), а на стадии Apply Request Values -
запросить компонент о нажатиях, и если path непустой, создать экземпляр события
и передать его в контекст приложения (decoding), пометив компонент как валидный
- "проверка закончена".
- Для отрисовки компонента GraphComponent в стиле дерева аналогично определяем
отрисовщик MenuTreeRenderer.
- Создаем класс myAppServletContextListener, реализующий стандартный интерфейс
javax.servlet.ServletContextListener, и в его методе contextInitialized() с помощью javax.faces.render.RenderKitFactory
создаем рендер-слой по умолчанию (default RenderKit), в который и добавляем отрисовщики
MenuBarRenderer и MenuTreeRenderer:
defaultRenderKit.addRenderer("MenuBar", new MenuBarRenderer());
defaultRenderKit.addRenderer("MenuTree", new MenuTreeRenderer());
В этом же методе можно зарегистрировать свой обработчик событий, образованный от
javax.faces.lifecycle.ApplicationHandler, который будет обрабатывать события, не обработанные в
компонентах; в этом же методе можно зарегистрировать наборы ресурсов, с которыми
будет работать приложение.
В web.xml для приложения регистрируем класс myAppServletContextListener.
- Создаем класс поддержки тегов GraphMenuBarTag на основе
javax.faces.webapp.FacesTag, в котором метод getRendererType() возвращает
String-идентификатор "MenuBar". В методе overrideProperties, проверяется,
передано ли в тег значение аттрибута modelReference (ссылка на модель), и если
не передано, создается новый объект Graph в области сессии. А метод
createComponent создает экземпляр компонента, с которым будет проводиться работа.
- Создается класс поддержки тега "узла" для работы со структурой дерева прямо
внутри JSP-страницы - GraphMenuNodeTag на основе
javax.faces.webapp.FacesTag.
- Для созданных тегов пишется XML-спецификация в tld-файле.
В принципе, компонент может отрисовывать себя и сам, непосредственно, а также
осуществлять decoding, но тогда из проекта исчезает гибкость, необходимая для
подержки разных выходных форматов.
3.3.4. Использование компонент: JSF taglibs
В JSF основным инструментом для создания презентационного слоя предлагается
считать JSP-файлы (а чего Вы еще ожидали?) Компоненты подключаются туда довольно
просто при помощи tag libraries. Разработчик
библиотеки компонент должен предоставить еще и библиотеку тегов со
спецификацией в tld-файле. Все, что относится к компонентам, должно быть внутри
конструкции use_faces (базовая JSF taglib). А в остальном в JSP-файлы можно
писать то же, что и обычно, в т.ч. скриптлеты и JSTL. Чтобы вывести компонент
GraphComponent в виде меню, нам потребуется примерно такой JSP-код:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="URI_спецификации_наших_тегов" prefix="d" %>
<f:use_faces>
<html>
<head>
<title>Пример использования "собственного" компонента GraphComponent</title>
<d:stylesheet path="/tree-control-test.css"/>
</head>
<body bgcolor="white">
<h:form formName="menuForm">
Рисуем GraphComponent в виде меню (с установкой структуры через JSP):<br>
<d:graph_menubar id="menu3" selectedClass="tree-control-selected"
unselectedClass="tree-control-unselected">
<d:graph_menunode name="Menu" label="Menu">
<d:graph_menunode name="File" label="File" expanded="true">
<d:graph_menunode name="File-New" label="New" action="/faces/demo-test.jsp"/>
<d:graph_menunode name="File-Open" label="Open" action="/faces/demo-test.jsp"/>
<d:graph_menunode name="File-Close" label="Close" enabled="false"/>
<d:graph_menunode name="File-Exit" label="Exit" action="/faces/demo-test.jsp"/>
</d:graph_menunode>
<d:graph_menunode name="Edit" label="Edit">
<d:graph_menunode name="Edit-Cut" label="Cut" action="demo-test.jsp"/>
<d:graph_menunode name="Edit-Copy" label="Copy" action="demo-test.jsp"/>
<d:graph_menunode name="Edit-Paste" label="Paste" enabled="false"/>
</d:graph_menunode>
</d:graph_menunode>
</d:graph_menubar>
</h:form>
</body>
</html>
</f:use_faces>
Структуру меню можно задать не только непосредственно "на месте":
если имеется заполненный объект типа Graph (модель), можно просто передать его
компоненту в качестве modelReference:
Рисуем GraphComponent в виде меню (с установкой структуры из модели):<br>
<d:graph_menubar id="menu2" modelReference="sessionScope.graph"
selectedClass="tree-control-selected"
unselectedClass="tree-control-unselected" />
С такими JSP-файлами, конечно, довольно сложно работать в Dreamveawer и др.,
так что в Sun наверное рассчитывают на разработчиков IDEs, которые быстро
напишут визуальные редакторы для JSF-приложений :-)

Строго говоря, JSP - это основной, но не единственный способ для создания
presentation layer. Из всех server-side
java-технологий JSF требуются только сервлеты, поскольку JSF ориентирован на
использование совместно с Servlet API. Схема слева демонстрирует
отношения между компонентами JSF-приложения. Для JSF EA3 есть
несколько примеров с использованием XUL - языка для пользовательских
интерфейсов, применяемого в Mozilla.
3.3.5. Валидация форм и i18n в JSF
Для обслуживания HTML-форм в JSP/JSF-файлах удобно пользоваться стандартным
набором HTML-компонентов для JSF, сопровождаемым библиотекой тегов.
В теге формы непосредственно указывается modelReference, т.е. соответствующий
javabean, а у каждого элемента ввода формы также задается modelReference, но уже
в виде имени конкретного поля в javabean. К каждому элементу ввода можно
применить валидаторы из стандартного набора:
- DoubleRangeValidator
- LengthValidator
- LongRangeValidator
- RequiredValidator
- StringRangeValidator
В приведенном примере иллюстрируется интернационализация через наборы ресурсов
(resource-bundles), связывание элементов формы с полями модели и использование
стандартной (validate_length) и частной, т.е. специально разработанной
пользователем (creditcard_validator) валидации для номера кредитной карты:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="/WEB-INF/mytags.tld" prefix="cd" %>
...
<h:form formName="CustomerForm" modelReference="CustomerBean">
...
<tr>
<td><h:output_text key="ccNumberLabel" bundle="myResourceBundle"/></td>
<td><h:input_text id="ccno" size="16" converter="creditcard">
<f:validate_required/>
<f:validate_length minimum="16" maximum="16"/>
<cd:creditcard_validator maximumChar="9" minimumChar="0"/>
</h:input_text></td>
<td><h:output_errors clientId="ccno"/></td>
</tr>
Пример выводит форму в таблице из трех колонок: в первой выводится название
поля формы в выбранной локали по ключу в наборе ресурсов; во второй выводится
поле ввода для номера кредитки с указанием правил валидации и последующего преобразования
(converter) с группировкой по 4 символа; в третьей выводятся сообщения об
ошибках, связанных с валидацией значения.
Для использования стандартного валидатора длины поля требуется только
подключение базовой JSF taglib. Для реализации и использования "нестандартного",
т.е., "собственного", валидатора creditcard_validator требуется ряд действий:
- Разработать собственный класс валидации (CreditCardValidator) в виде
реализации javax.faces.validator.Validator с bean-методами get/set для
параметров (minimumChar, maximumChar), методом getMessageResources() для получения надлежащего набора
ресурсов с сообщениями об ошибках и главным методом validate(). Для
нашего случая в этом методе проверяется, сколько букв содержится в полученном значении, и при
выпадении этого числа из диапазона параметров (minimumChar, maximumChar)
компоненту-полю ввода выставляется setValid(false) с выводом сообщения об ошибке
по ключу (ключи заложены в классе валидатора как public static final String).
- Разработать класс поддержки тега валидатора CreditCardValidatorTag
(предлагается расширить javax.faces.webapp.ValidatorTag), содержащий
get/set-методы для параметров (minimumChar, maximumChar), конструктор, в котором
устанавливается ранее разработанный класс валидации
(super.setType("myapp.CreditCardValidator")), и метод createValidator() для
создания экземпляра зарегистрированного класса.
- Описать параметры тега в XML-спецификации (tld-файл);
- Создать локализованные записи сообщений об ошибках в национальных наборах
сообщений myResourceBundle.
В качестве ресурсов можно использовать как традиционные properties-файлы, так и
собственные классы на основе java.util.ResourceBundle (возможно, с хранением в базе
данных), как в обычном варианте Servlets/JSP и "Advanced JSP". Плюс в JSF появились
XML-файлы с сообщениями, с которыми можно работать через объекты
com.sun.faces.context.MessageResourcesImpl. В таких сообщениях можно
использовать тему (summary) и параметризованный текст, куда пойдет список
параметров из валидатора:
<messages>
...
<message messageId="myapp.CreditCardValidator.Invalid_Characters"
severity="2" summary="Invalid Credit Card Number">
<detail>Credit Card can only accept characters between {0} and {1}</detail>
</message>
...
</messages>
4. Заключение.
В этой статье я постарался показать, в каком направлении происходила и происходит эволюция
подходов к разработке веб-приложений на примере Java. Последним результатом этой
эволюции являются компонентные подходы, такие как Java Server Faces для
Servlets/JSP и WebForms для ASP.Net (к сожалению, WebForms появились уже давно,
а JSF до сих пор в Early Access). Повышение сложности решаемых
задач привело к сближению, конвергенции подходов, применяемых для "традиционных" и веб-приложений
- сегодня их можно делать по-одинаковому, а завтра, в эру тонких клиентов, эти понятия,
видимо, перестанут существовать как отдельные, слившись в единое целое.
Пока же нам приходится иметь дело с множеством технологий и подходов, стоящих на
разных ступенях этой эволюции. Возможно, в не таком уж далеком будущем мы позабудем о большинстве
из них так же, как сейчас позабыты Borland OWL и Turbo Vision для C++. Эти
изменения не будут очень быстрыми, да и сами компонентные реализации не совсем
зрелые - JSF пока не "выпущен", в Tapestry готовится версия 3.0, и поэтому нет
нормальной документации, а кто и как что-то делает на Barracuda, мне вообще не
известно.
Несмотря на разнообразие ("разноуровневость") перечисленных в обзоре
технологий, эффективность их применения можно сравнить довольно просто - насколько
полно для технологии реализован прицип "разделяй и властвуй". Для существующих
веб-приложений ответ на этот вопрос складывается из двух характеристик:
- Показатель разделения представления и кода (DCS - design/code separation);
- Показатель повторного использования кода (CR - code reuse)
Каждый из этих показателей условно оценим по трехбальной системе (1 -
удовлетворительно, 2 - нормально, 3 - отлично). В результате у меня получился
такой вот результат:

Разумеется, это сугубо личные оценки автора. Кроме того, для "MVCs" - "MVC Frameworks"
существуют различные варианты, так что оценка может существенно плавать около
значения.
Наивысшие оценки по DCS получили технологии, основанные на XMLC, к ним
приближаются Cocoon и системы XML+MVC. Мэйнстрим от Sun хорошими показателями DCS
никогда не блистал вследствие завязанности на JSP. Вообще, Бум
Серверных Страниц можно сравнить с бумом монотеистических религий - сначала
большие толпы кричат "Ура!", а потом наступает темное средневековье.
Показатель CR становится важен только сейчас, когда веб-приложения укрупняются и
можно говорить о больших объемах кода. Переход к компонентной структуре
веб-приложений поднимет этот показатель примерно до одинакового уровня.
Будущее всем все покажет :-)
5. Ссылки по теме:
Model1, Model2
- Перевод статьи с JavaWorld об архитектуре MVC
(Источник - Govind Seshadri, JavaWorld, декабрь 1999)
- Старая статья о
дескрипторах шаблонов в JSP, давно уже входящих в Struts/JSTL (David Geary,
JavaWorld, ноябрь 2000)
- Статья: Layering Applications - о "правильной" структуре веб-приложения (Scott Stanchfield, JavaDude)
- Очень рекомендую книгу "Advanced Java Server
Pages" Дэвида Гери - одного из евангелистов Struts, taglibs и теперь JSF.
Struts - введения
- Статья: Struts, an open-source MVC implementation - начинающим (Malcolm Davis, DeveloperWorks, Feb 2001)
- Сайт с PowerPoint-презентацией и разобранным примером - начинающим
- Статья: Create Better Web Apps with Struts (Kevin Jones, Java Pro) - начинающим
- Статья: Quick Struts Introduction For Evaluators (Harry Rusli, ScioWorks, Jun 2002)
Struts в действии
- Статья: Stepping through Jakarta Struts (Keld H. Hansen, JavaBoutique)
- Статья: Coding your second Jakarta Struts Application (Keld H. Hansen, JavaBoutique)
- Статья: Struts meets Swing (1) (Keld H. Hansen, JavaBoutique)
- Tutorial: An easy step by step introduction to Struts (Stephan Wiesner, Sep 2002)
- Страница по i18n с примером кириллизации (Aaron Rustad)
- Сайт с несколькими полезными примерами, в т.ч. Sonic Store
- Статья: Fast Track to Struts (Nadir Gulzar, TheServerSide, Nov 2002)
- Статья: Jakarta Struts: Seven Lessons from the Trenches (Chuck Cavaness, ONJava, Oct 2002)
- Глава 14: Programming Jakarta Struts: Using Tiles, Part 1 (Chuck Cavaness, ONJava)
- Part 1,
Part 2: Learning the New Jakarta Struts 1.1 (Sue Spielman, ONJava, Nov 2002)
- Статья: Struts 1.1: Should I upgrade? (John Yu, TheServerSide, Aug 2002)
- Статья: Using JAAS for Authorization and Authentication - с использованием Struts (Dan Moore)
- FAQ: Issues In Struts Adoption (вопросы общего плана) - для менеджеров (Harry Rusli and John Yu, ScioWorks, Jul 2002)
- Статья: Struts and Tiles aid component-based development (Wellie Chao, DeveloperWorks, Jun 2002)
- Статья: Struts: a Solid Web-App Framework - продолжающим (Tim Holloway, Java Pro)
- Manual: Using Struts - PDF - продолжающим (Larry Maturo)
- Статья: Boost Struts with XSLT and XML - продолжающим (Julien Mercay and Gilbert Bouzeid, JavaWorld, Feb 2002)
- Статья: UI design with Tiles and Struts (Prakash Malani, JavaWorld, Jan 2002)
- FAQ: Struts Q&A Distilled - набор вопросов и ответов, сгруппированных по тематике (на ScioWorks)
- BasicPortal.com - Struts portal
Struts & IDEs
- HowTo: Struts & Oracle9i JDeveloper (Oracle Technology Network)
- HowTo: Struts & Forte
- Tutorial: JBuilder 5, Struts 1.0 & WebLogic 6.0, a Tutorial (Markus Colombo)
Cocoon
Enhydra/XMLC
Разное
JetSpeed
Java Server Faces