rus

advertisement
Сервлеты в чистом виде: смена парадигмы
Упрощение дизайна приложений с помощью Servlet API
Уровень сложности: средний
Джейсон Ван Клив, web-разработчик, Else For If
19.12.2007
Считается, что для Web-страниц с динамическим содержимым технология JavaServer Pages (JSP)
позволяет отделить задачи разработчика от задач дизайнера пользовательского интерфейса. К
несчастью, технология JSP слишком сложна для многих дизайнеров, так что Java-программистам
приходится лично заниматься обработкой JSP-страниц, часто с неудовлетворительными
результатами. В этой статье демонстрируются преимущества нестандартного подхода: использование
простых объектов-помощников (helper) для построения Web-интерфейса, основанного только на
сервлетах.
От редактора. Этот прием не рекомендуется для команд, в которых участвуют HTML-кодеры. Он
предназначен для Java Web-разработчиков, которые пишут и поддерживают свой собственный HTMLкод в несложных Web-приложениях.
Технология JSP спроектирована так, чтобы отделить задачи Web-разработчика от персонала, который
занимается проектированием динамических GUI страниц. К несчастью, технология JSP слишком
сложна для многих дизайнеров пользовательских интерфейсов, так как добавляет несколько слоев
для решения различных проблем при генерации динамического содержимого. К примеру, для
интернационализации приложений необходимо хранить текст в определенном месте, отдельно от
страниц пользовательского интерфейса, и обращаться к нему с помощью специальных ключей. Так
что Java-разработчикам приходится работать c JSP-страницами в одиночку, зачастую переделывая
работу дизайнеров пользовательского интерфейса, добавляя taglib'ы и другие элементы, чтобы
избежать усложнения страницы из-за добавления Java-кода.
Вопреки устоявшейся точке зрения, можно построить простой и симпатичный Web-интерфейс
приложения, основанный на обычных сервлетах, с помощью простых объектов-помощников. Эта
статья призывает рассмотреть генерацию представления для динамических Web-страниц с помощью
стандартных средств Java. Я объясню преимущества этого подхода и продемонстрирую его на
примере приложения для подсчета очков, которое я разработал для чемпионата NCAA March Madness.
Замечание. Подход, продемонстрированный в этой статье, предназначен для Java Webразработчиков, которые пишут и поддерживают собственный HTML код, а не для команд, в состав
которых входят HTML кодеры.
Динамический HTML
Подход, основанный на чистых сервлетах, позволяет упростить архитектуру. Он включает базовый
класс-сервлет и специализированные объекты-генераторы, которые подклассы сервлета используют
для производства выходных данных. Кода в данном случае получается немного, так как большая
часть HTML инкапсулирована в методах объектов-помощников, которые могут быть переопределены
при необходимости. Повторное использование кода всегда приветствуется, и большинство Webсайтов совместно используют так много HTML между различными страницами, что повторное
использование является крайне важным. Методы генерации HTML приводят к прямолинейному и
компактному коду сервлетов, а, следовательно, и удобному для поддержки, так как стоимость
поддержки кода более или менее пропорциональна размеру кода. Переписав UI интерфейс с JSP на
"чистые" сервлеты, я смог сократить размер кода на две трети.
Например, возьмем эту сложную конструкцию для вывода ссылки в зависимости от прав доступа
пользователя:
<c:if test="${user.permission[ sessionScope.ConstantMap[
EDIT_WIDGET ] ] != 0}">
<c:url var="editUrl" value="/EditWidget.jsp"/>
<div class="navigation"><a href="<c:out
value="${editUrl}"/>">Edit
this widget</a></div>
</c:if>
Она становится значительно понятнее при использовании синтаксиса Java:
if (user.getPermission(Constants.EDIT_WIDGET) != 0)
out.printNavlinkDIV("/EditWidget.jsp", "Edit this widget");
Учтите также количество вспомогательного кода, сэкономленного за счет извлечения и вывода
бизнес-объектов в одном и том же месте, вместо передачи их через запрос. Краткость - сестра
таланта.
Работа с JSP и другими технологиями отображения- возможно, один из самых ненадежных аспектов
Web-разработки. JSP страницы - это не HTML, не XML, не Java-код и не код JavaServer Pages Standard
Tag Library (JSTL), не язык выражений (EL), а беспорядочная смесь этих и других технологий. Однако
не только это нелепая смесь, но и каждый уровень абстракции создает новые препятствия для
разработки. Отладка JSP страниц, к примеру, крайне напоминает поиск источника с помощью лозы.
Вы знаете, что где-то там под поверхностью есть ошибка, но не можете её обнаружить на основании
загадочного сообщения об ошибке, в котором указан номер строки, не соответствующий вашему
коду.
Также JSP-технология не позволяет наследовать базовому классу, так что повторное использование
сводится к bean-компонентам, include-файлам и специфическим taglib'ам. Taglib'ы также слишком
сложны, чтобы быть эффективным средством повторного использования. Поддержка XML-файла для
каждого сделанного изменения в API крайне утомительна, несмотря на то что "проектирование тегов
это проектирование языка" (см. статью Ноэла Бергмана (Noel Bergmann) в Ресурсах). Результат всего
этого - еще один уровень в и без того многоуровневом интерфейсе.
Сейчас мы встречаем новый World Wide Web. Вне зависимости от того, сможет ли AJAX перевернуть
Web-разработку, Web-сайты будут становиться все более и более интеллектуальными. И хотя HTML
сам по себе всегда декларативен, код, который он генерирует, определенно не декларативный.
Технология JSP и другие системы шаблонов неминуемо оказываются слишком сложными, так как они
пытаются выразить декларативно то, что на самом деле является динамическим выводом. Именно по
этой причине разработчики вынуждены добавлять scriplet'ы в исходный код JSP, так как то, что они
пытаются выразить это в такой же степени логика, как и форма.
Инкапсулируя HTML как Java-код, можно выразить логику вывода более сжато. Конструкции if и
циклы for принимают свою хорошо известную естественную форму. А элементы страниц могут быть
разложены по легко поддерживаемым и понимаемым методам. Поддержка JSP-страниц, которые
редко хорошо документируются, в лучшем случае достаточно тяжела и подвержена ошибкам.
Используя "чистые" сервлеты, можно максимизировать повторное использование кода, так как не
нужно писать новый класс для каждой конструируемой страницы.
Безумный дизайн
Чтобы проиллюстрировать концепцию "чистых" сервлетов, я
построю интерфейс для просмотра очков для чемпионата
NCAA March Madness (см. врезку March Madness и материалы для скачивания). Пользователь может
войти в систему, выбрать из 64-х команд чемпионата те 20 команд, которые, по его мнению,
являются фаворитами, и присвоить каждой команде весовой коэффициент. После начала игр данные,
введенные пользователями, можно только просматривать, и администратор сообщает победителей
игр, как только они становятся известны. В зависимости от команд, выбранных пользователем, его
очки аккумулируются, соотносятся с очками других пользователей и выводятся по порядку.
Этот проект занял около трех недель моего свободного времени, большую часть которого я посвятил
возне со стилями и графикой, так как я не художник. Кроме одного HTML-файла и других
статических ресурсов, уровень GUI состоит из 21 Java класса, общим размером в 1334 Javaвыражения, по измерениям JavaNCSS (см. Ресурсы).
Отход от Model - View - Controller (MVC)
Архитектура, основанная только на сервлетах, которую я здесь продемонстрирую, включает только
один уровень представления между клиентом и бизнес-логикой. Шаблон Модель - Вид - Контроллер
(MVC), известный как Model 2, на самом деле не является панацеей, так как страдает серьезными
ограничениями, и Web-среды для разработки приложений, поддерживающие его, имеют тенденцию к
чрезмерному усложнению. Spring MVC и JavaServer Faces (MVC) слишком многословны, как и Struts,
конфигурационные файлы которого являются крайне сложными и объемными и должны
корректироваться каждый раз, чтобы следовать логике управления приложениями. Н. Алекс Рупп (N.
Alex Rupp) в разделе Ресурсы пошел ещё дальше и назвал MVC - анти-шаблоном или "безумным
поиском философского камня Web-технологий".
Например, разработчики часто неверно понимают назначение модулей Action в Struts. Бизнес-логика
имеет тенденцию накапливаться и застревать в них (а то и на всем протяжении пути от них до JSP).
Реализация представления и контроллера как сервлетов побуждает хранить бизнес-логику там, где
она должна быть, так как сервлеты явно сфокусированы на взаимодействии с браузером.
Для данного проекта использовалось несколько классов из моей собственной elseforif-servlet
библиотеки (см. Ресурсы). Именно эта библиотека является ключом к архитектуре проекта, так как
она предоставляет удобный интерфейс для генерации HTML. Но мы не будем фокусироваться на
библиотеке, просто проверим концепцию, чтобы доказать жизненность моего подхода.
На рисунке 1 приведена частичная диаграмма классов, элементы библиотеки elseforif-servlet
выделены зеленым.
Рисунок 1. Фрагмент диаграммы классов
Вверху дерева находится интерфейс, содержащий строковые HTML константы, используемые и
объектами, генерирующими HTML, и сервлетами. Позже вы увидите, как они работают. Затем идут
классы HTMLWriter и HTMLFlexiWriter, реализующие базовые низкоуровневые HTML методы, которые
могут потребоваться любому Web-сайту. Разница между ними в том, что HTMLWriter печатает
информацию напрямую в поток вывода, тогда как HTMLFlexiWriter также может возвращать
выводимую информацию в виде строк. Часто оказывается удобным передать результат работы одного
метода-генератора HTML параметром в другой метод-генератор HTML, как в этом примере.
out.printA(URL_ELSEFORIF, out.IMG("/img/elseforif.gif", 88, 31));
Класс MadnessWriter затем добавляет высокоуровневую функциональность для вывода HTML,
необходимую для данного Web-сайта - общие элементы, такие как верхний и нижний колонтитулы и
меню, все, что специфично для данного сайта и неоднократно встречается на нем. Это легкий, не
приспособленный к многопоточному использованию объект, создаваемый для каждого запроса
абстрактным базовым классом-сервлетом MadnessServlet с помощью метода-фабрики.
Этот базовый класс отвечает за обработку основной управляющей логики сервлета, так что
конкретные подклассы могут сфокусироваться на своих специфических задачах. После установки
стандартных заголовков HTTP и выполнения определенной проверки безопасности на уровне
страницы, он передает объект MadnessWriter в защищенный метод doBoth():
protected void doBoth(HttpServletRequest request, HttpServletResponse response,
HttpSession session, MadnessWriter out) throws ServletException, IOException
Класс MadnessServlet также реализует интерфейс MadnessConstants, предоставляя подклассам удобный
доступ к статическим значениям, определенным в HTMLConstants. Таким образом, применение объекта
MadnessWriter вместе с этими константами позволяет создать для сервлетов компактный Javaподобный синтаксис.
Говоря языком MVC, сервлеты - в данном случае базовые
компоненты пользовательского интерфейса - сочетают
уровни представления и управления. Для протокола, подобного HTTP, не поддерживающего сеанс
взаимодействия с пользователем, это разумно. Запросы на отображение и запросы на обновление
данных принимают одну и туже простую форму, и между ними нет четкого разделения. Я
предпочитаю ради модульности реализовывать страницу с формой в одном сервлете, а обработчик
этой формы в другом. Но как бы вы их ни разделяли, логика для генерации HTML, логика для
обработки параметров сервлета и логика навигации по страницам тесно связаны между собой. Благое
намерение MVC отделить их друг от друга порождает большие проблемы.
Реализация бизнес-уровня должна иметь мало значения для уровня представления. Главное - иметь
разумный бизнес-интерфейс, чтобы код пользовательского интерфейса занимался только своим
делом. Для бизнес-уровня данного проекта я построил довольно простой CRUD (create-read-updatedelete) интерфейс для работы с данными при помощи Apache Derby.
Запуск приложения
Это Web-приложение является практически самодостаточным, но может потребоваться изменить
некоторые переменные для настроек среды в файле web.xml, прежде чем помещать его в каталог
webapps. По крайней мере, встроенному экземпляру СУБД Derby требуется место, чтобы создать и
хранить файлы с данными. По умолчанию в UNIX используется каталог /var/derby/, так что если
используется Linux, то нужно создать такой каталог и обеспечить сервлет-контейнер правом на
запись туда. В приложение можно войти, используя имя пользователя admin и пароль password.
Дополнительную информацию можно найти в файле README загруженного пакета.
Download