Продолжается подписка на наши издания! Вы не забыли подписаться?

Взгляд на JSF

Brian Pontarelli

Хотя многие и заявляют, что Java Server Faces (JSF) 1.0 Specification Early Access Draft (EAD) просто непригодна для Web-разработки, есть и такие, включая компании, голосовавшие за эту спецификацию, кто считают это её достоинством. Я не буду вдаваться в детали, говорить, в каком лагере я нахожусь, и так далее, но скажу, что JSF – это определенно шаг в нужном направлении для разработки приложений "HTML через HTTP".

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

Дублирование модели данных

Секция JSF 2.1 говорит о процессе преобразования параметров HTTP-запроса в UIComponent Tree и использовании значений параметров HTTP-запроса для задания локальных значений UIComponent Tree. Секция 2.5 продолжает тему, обсуждая (не вдаваясь в детали) процесс, с помощью которого реализация JSF переводит локальные значения UIComponent Tree в значения Application Model. Эти шаги в технологическом процессе JSF называются "reconstitute request phase" и "update model values phase" (фаза обновления значений модели), соответственно.

Большинство реализаций JSF будут работать с запросами по следующей схеме: HTML->HTTP->FacesServlet->reconstitute request phase->…->update model values phase.

Это ведёт к огромному количеству дублирования и накладных расходов. Значения каждого UIComponent будут храниться в HttpServletRequest, разворачиваться в UIComponent Tree и, в конце концов, передаваться в Application's Model. Это кажется небольшой работой, пока речь идет о небольших формах из двух-трёх полей, но для форм из 20-30 полей это уже не так. Рост накладных расходов будет заметнее, если рассматривать интенсивно работающее Web-приложение (более 20 запросов в минуту). Вдобавок расходы на сами классы UIComponent и Application's Model могут ещё увеличить количество памяти, потребляемой этим тройным дублированием, так как они могут включать другие переменные члены, увеличивающие размеры каждого экземпляра.

Кроме расходов памяти, большинство реализаций JSF скорее всего будут использовать отражение для задания локальных значений UIComponent Tree. Хотя эталонные реализации модели и не были включены в этот драфт, я предполагаю, что они тоже будут использовать отражение. Это означает два или более отражений для каждого значения (UIComponent) в каждом запросе.

Допустим, запрос прошел весь путь до фазы Invoke Application, а затем неожиданно столкнулся с ошибкой. Пользователя отправили обратно к форме, чтобы он исправил введенные значения. После исправления и повторной отправки данных формы, этот двойная передача повторяется во всей полноте.

Альтернативным дизайном могло бы быть одно дублирование данных – напрямую из HttpServletRequest в Application Model. Тем самым полностью удаляются локальные значения UIComponent. UIComponent Tree по прежнему может быть создано из HTTPServletRequest, но оно не будет хранить данные из запроса. Более того, сами UIComponent'ы могут опираться на классы Application's Model, как в случае с MVC-дизайном Java Swing API.

Обработка событий запроса
(Request Event Handling)

Секция JSF 2.3 говорит о модели обработки событий запроса. Эта модель относится к событиям, порождаемым изменениями UI или запросами на изменение UI. Например, пользователь помечает переключатель, что изменяет значения в списке.

Это пробовали сделать много раз, да так и не сделали. Модели работы с запросами на серверной стороне плохо масштабируются из-за накладных расходов на маршалинг/демаршалинг всей формы в и из HttpServletRequest, а в случае JSF ещё и копирования в UIComponent Tree. И все это для того, чтобы переключатель менял значения в выпадающем списке (например).

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

Я думаю, что будет совсем немного нужды в RequestEventHandlers, и что большая часть работы ляжет на ApplicationEventHandlers.

Процесс декодирования при передаче данных из HTTPServletRequest в UIComponent Tree будет работать так же, но сохранять данные будет в Application's Model. Аналогично, процесс кодирования при передаче значений обратно в броузер будет получать данные из Application's Model.

Responses и сохранение состояния

Секция JSF 2.7 говорит о фазе вывода ответа, где UIComponent Tree используется для создания UI, выводимого пользователю. В этой фазе по умолчанию ViewHandler перенаправляет HttpServletRequest в UIComponent Tree. Это делается через id UIComponent Tree, то есть URL ресурса, к которому производится перенаправление.

Как смогут приложения выполнять перенаправление на HTML-страницы? Это не представляется возможным в текущей ситуации без создания UIComponent Tree для страниц, не содержащих JSF-кода, и создания ViewHandler, который будет перенаправлять запрос на это UIComponent Tree. Кроме того, очень неприятным кажется то, что UIComponent Trees диктуют страницу ответа.

Для соблюдения концептуальной корректности каждая страница приложения должна содержать JSF-код, неважно, нужен он или нет. Это выглядит как большое ограничение для предприятий с существующей информационной структурой, или для тех, кто нуждается в переадресации, например, с J2EE-сервера на ASP-сервер. Конечно, это никому не нравится, но это реальность. Процесс должен быть достаточно гибок, чтобы поддерживать пересылки и перенаправления на любой ресурс как внутри, так и вне контейнера.

Требование сохранения UIComponent Tree в ответе или в сессии также очень неоднозначно и вызывает вопросы типа: как UIComponent Tree записывается в ответ или сессию?Это то же самое, что и простая визуализация ответа с помощью UIComponent Tree, результатом которой является запись текущего состояния в страницу?

И ещё, очень может быть, что при визуализации UI количество UIComponent'ов на странице будет различным (например, если JSF-тег содержит цикл). Это делает невозможным сохранение UIComponent Tree в ответе или сессии, если секция JSF 2.1 не рассчитана на то, что сохраненное и описанное параметрами запроса деревья имеют одинаковый размер.

Еще большая проблема, чем сохранение состояния – определение, откуда извлекаются значения для UIComponent в фазе визуализации ответа. Это кажется тривиальным, можно сказать, что значения просто берутся из локальных значений UIComponent при кодировании. Но вполне вероятно, что логика приложения запросто может изменить значение в Application's Model. Это потребует замены значения в UIComponent на значение из Application's Model.

Спецификация не освещает эту тему, хотя и должна бы, поскольку это определяет, кто отвечает за это изменение – логика приложения или реализация JSF.

Лучшим решением этой проблемы будет добавление ещё одной фазы к циклу, "Update Local Values", отвечающей за обновление локальных значений UIComponent по Application's Model в случае необходимости. JSF могла бы поступиться локальными значениями UIComponent в пользу более MVC-ориентированной системы, где отображение стоится прямо по Application's Model, как уже говорилось выше.

Проверки и сообщения об ошибках

В секции JSF 3.5 речь идет о классах Validator, о том, как они вызываются в процессе работы JSF, а также о том, как они генерируют сообщения об ошибках и добавляют их в FacesContext. Сообщения об ошибках были большой заботой во всех проектах, в которых я участвовал, и в обоих написанных мной framework'ах.

Многократно используемый Validator хорош тем, что уменьшает дублирование кода. Однако при использовании многократно используемого Validator очень трудно создать сообщения, подходящие для разных UIComponent'ов.

Например, на одной странице мне нужно текстовое поле для возраста, а на другой – для дохода. Я не хочу, чтобы сообщения об ошибках были обобщенными, типа "Это значение должно быть больше 0, но меньше Х". Я хочу, чтобы пользователь знал, что должно быть внутри этого интервала: "Ваш возраст должен был больше 0, но меньше 110".

Одно из решений – использовать в сообщении имя поля ввода. Это вынуждает разработчика давать полям ввода осмысленные имена, что не всегда возможно. Например, у меня есть поле для ежемесячных накладных расходов, и я называю его monthlyOverhead, нормальное имя для переменной. Но сообщение не может гласить "monthlyOverhead должны быть больше 0". Это не пройдет. Сообщение должно звучать по-человечески, например, "Ваши накладные расходы должны быть больше нуля". Но назвать UIComponent "Ваши накладные расходы" тоже нельзя, особенно, если на странице используется JavaScript. Наконец, это просто плохой стиль.

Другое решение требует отдельного подкласса для каждого сообщения. Это заваливает package тоннами Validator-ов, а также требует слишком большого объема кодирования. Еще одно решение – атрибут JSP-тега, сообщающий JSF-реализации ключ сообщения об ошибке в ResourceBundle. Например:

<jsf:text id="age" errorMessage="ageError"/>

Это не позволяет использовать параметризованные сообщения (то есть параметры java.text.MessageFormat) и не забить JSP-страницу значениями каждого параметра. Предположим, что есть ResourceBundle с сообщениями, и в нем есть сообщение "ageError=Ваш возраст должен быть больше {0} и меньше {1}". Теперь JSP-тег будет выглядеть, например, так:

<jsf:text id="age" errorMessage="ageError" params="0,110"/>

Да, кстати, ни одно из этих решений не принимает во внимание интернационализацию.

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

Я, надо сказать, не вижу в JSF никаких достойных способов решить эту проблему. Многократно используемые Validator'ы не блещут гибкостью, и, скорее всего, для полного управления сообщениями об ошибках, все проверки придется производить где-нибудь в другом месте.

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

Секция JSF 5.1.2 говорит об использовании локалей (Locales) в FacesContext для поддержки локализации UIComponent'ов (и, должно быть, сообщений об ошибках). Это имеет большое значение для интернационализации. Похоже, что нельзя отображать разные UIComponents с разными локалями. Многоязычные приложения нуждаются в такой функциональности.

Предположим, что вы можете локализовать каждый UIComponent. Если разные компоненты используют разные локали, а тип контента HttpServletResponse поддерживает только одну, при визуализации в броузере на клиентской стороне возникнут проблемы.

В секции JSF 5.1.5 говорится о классах Message и о том, как сообщения об ошибках добавляются в FacesContext. Но эта секция забывает о большом шаге к интернационализации. Сообщения, помещаемые в очередь сообщений (при обработке или проверках), содержат Unicode String Objects, и могут быть написаны на любом языке. Message Object не содержит информации о локали сообщения – а это нужно для интернационализации.

Всё выглядит так, будто спецификация хочет втиснуть всю локализацию в набор локалей в FacesContext, что, как я уже говорил, может иметь пагубные последствия. JSF в современном состоянии не поддерживает сообщений об ошибках на разных языках. К использованию разных языков нужно подходить тщательнее из-за проблем, связанных с этим вопросом (таких, как определение нужной кодировки, если две локали нуждаются в разной кодировке).

Хорошо бы

Секция 3.1.2 представляет несколько интерфейсов и методов для определения id UIComponent'а.

Интерфейсы JSF должны предоставлять способ определения индивидуального и абсолютного идентификатора компонента (bar и /foo/bar). Это будет полезно как в средствах разработки, так и при отладке.

Читаемость

Некоторые части EAD очень трудно читаемы и сбивают с толку. Секции JSF 2.6 и другие секции пускаются в обсуждение процесса управления жизненным циклом до того, как пользователю скажут, что это такое.

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

Секция JSF 8.1 описывает пользовательские действия при интеграции с JSP. Это, наверное, наиболее запутанная и плохо написанная секция во всём документе. Там имеют место быть ни к чему не относящиеся термины, старые имена классов и неподписанные таблицы. Все это нужно переписать по-человечески.

Я не понимал, что такое "пользовательские действия", пока не дошел до секции 8.2.6, и не выяснил, что под действием понимается всего лишь тег custom JSP. Это плохой выбор слова, поскольку далеко не все теги подразумевают действия. Каково действие тега input? Я еще могу понять, когда говорят о тегах for-loop.

Выводы

Ну вот, это большая часть моих комментариев к JSF 1.0 Specification Early Access Draft.

Одно последнее замечание к миру в целом. JSF и все другие среды стараются облегчить программирование в мире, не предназначенном для приложений. HTML через HTTP не имеет состояния, что усложняет создание приложений, требующих обработки событий запроса (о чем говорилось выше), управления сессиями, клиентской логики и реального взаимодействия клиента и сервера.

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


Copyright © 1994-2016 ООО "К-Пресс"