Сервлеты в чистом виде: смена парадигмы Упрощение дизайна приложений с помощью 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 загруженного пакета.