Table of Contents
JSF Request Lifecycle
Big Picture
Описание стадий
Restore View
На стадии Restore View реализация JSF должна выполнить следующие действия:
- Проверить FacesContext для текущего запроса. Если он содержит UIViewRoot, то
- Выставить
UIViewRoot
'у локаль, полученную из метода ExternalContext.html#getRequestLocale() для текущего запроса. - For each component in the component tree, determine if a
ValueBinding
for "binding" is present. If so, call the setValue() method on this ValueBinding, passing the component instance on which it was found. - Не предпринимать дальнейших действий на этой стадии.
- Определить идентификатор вида (view identifier) для текущего запроса:
- Если для
FacesServlet
'а используется маппинг с префиксом (например, "/faces/*
"), то в качествеviewId
использовать то, что на месте*
1). - Если используется маппинг с суффиксом (например, "
*.faces
"), то в качествеviewId
берётся путь, по которому был сделан запрос, в котором суффикс заменяется на указанный в параметре инициализации контекста с именем равным символической константе2)ViewHandler.DEFAULT_SUFFIX_NAME
(если этот параметр не присутствует, использовать значение символической константыViewHandler.DEFAULT_SUFFIX
в качестве суффикса для замены.3) - Если идентификатор вида определить не удаётся, бросить исключение4).
- Вызвать метод ViewHandler#restoreView(), передав ему полученный
FacesContext
и идентификатор вида в качестве аргументов и получив (возможно)UIViewRoot
в качестве результата.- Если
restoreView()
вернулnull
, то вызвать ViewHandler#createView() и затем FacesContext#renderResponse(). - Если запрос не содержит
POST
-данных или параметров в запросе (query parameters), то вызвать FacesContext#renderResponse().
- Сохранить созданный или восстановленный
UIViewRoot
вFacesContext
'е. - For each component in the component tree, determine if a
ValueBinding
for "binding" is present. If so, call the setValue() method on this ValueBinding, passing the component instance on which it was found.
В конце этой стадии свойство viewRoot
у FacesContext
'а для текущего запроса будет содержать сохранённое состояние вида, которое было в предыдущем запросе или новый вид, созданный с помощью ViewHandler#createView() для указанного идентификатора вида.
Apply Request Values
Назначение этой стадии - дать каждому компоненту возможность обновить своё состояние на основании информации в текущем запросе (параметры, заголовки, куки и т.д.).
В течение этой стадии реализация JSF должна вызвать метод UIViewRoot#processDecodes. По-нормальному это повлечёт за собой вызовы метода UIComponent#processDecodes для всех компонентов в дереве компонентов, как описано в явадоке к методу UIComponent.processDecodes()
. Для UIInput
-компонентов, преобразование данных должно быть проведено в соответствии с явадоком к UIInput.
В процессе декодирования данных из запроса, компоненты могут производить хитрые действия, включающие в себя:
- Компоненты, которые реализуют интерфейс [http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/javax/faces/component/ActionSource.html|ActionSource]] (например, UICommand), которые определят, что они были активированы, добавят ActionEvent в очередь событий. Это событие будет вызвано в конце фазы Apply Request Values или в конце фазы Invoke Application, в зависимости от состояния свойства
immediate
у активированного компонента. - Компоненты, которые реализуют интерфейс EditableValueHolder (например, UIInput), у которых свойство
immediate
выставлено вtrue
, вызовут преобразование и валидацию (включая потенциально запуск событий ValueChangeEvent, которые по-нормальному происходят в фазе Process Validations, вместо этого будут вызваны в фазе Apply Request Values.
В конце этой фазы все компоненты, которые реализуют интерфейс EditableValueHolder, будут обновлены в соответствии с данными, переданными в запросе (или с достаточным количеством данных чтобы вопроизвести некорректный ввод, если были ошибки преобразования). В дополнение к этому, для этих компонентов, у которых свойство immediate
выставлено в true
, будут выполнены преобразование и валидация. Преобразования и валидации, которые не удались, с помощью метода FacesContext#addMessage могут сигнализировать об ошибках для соответствующих компонентов и их свойство valid
будет выставлено в false
5).
Если какой-либо из вызванных методов decode()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#responseComplete(), то обработка текущего запроса должна быть немедленно прекращена. Если какой-либо из вызванных методов decode()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#renderResponse(), то управление должно немедленно перейти к фазе Render Response. Иначе управление должно перейти к фазе Process Validations.
Process Validations
В процессе создания вида (view
) для текущего запроса, к компонентам могут быть добавлены валидаторы. Помимо этого, компоненты могут сами реализовывать логику валидации в их методах validate()
6).
На стадии Process Validations жизненного цикла запроса реализация JSF должна вызвать метод UIViewRoot#processValidators(). По-нормальному это повлечёт за собой рекурсивный вызов метода UIComponent#processValidators() для каждого компонента в дереве компонентов, как написано в документации к этому методу. Обратите внимание, что компоненты, реализующие EditableValueHolder, у которых свойство immediate
выставлено в true
, уже выполнили свою валидацию на стадии Apply Request Values.
В процессе валидации, события могут добавляться в очередь компонентами и/или валидаторами, у которых вызывается метод validate()
. После этого они (события) будут распространены заинтересованным слушателям событий.
В конце фазы все преобразования и настроенные валидации будут выполнены. Те преобразования и валидации, которые провалились7), сигнализируют об этом посредством добавления сообщений вызывая метод FacesContext#addMessage() у контекста для текущего запроса, и их свойство valid
выставлено в false
8).
Если какой-либо из вызванных методов validate()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#responseComplete(), то обработка текущего запроса должна быть немедленно прекращена. Если какой-либо из вызванных методов validate()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#renderResponse(), то управление должно немедленно перейти к фазе Render Response. Иначе управление должно перейти к фазе Update Model Values.
Update Model Values
Если обрабока запроса дошла до этой стадии, то предполагается, что запрос синтаксически и семантически9) корректен, что локальное значение каждого компонента в дереве компонентов было обновлено, и что теперь можно обновлять данные в модели приложения для подготовки к выполнению событий приложения, которые находятся в очереди.
В течение фазы Update Model Values, реализация JSF должна вызвать метод UIViewRoot#processUpdates(). По-нормальному это повлечёт за собой вызов метода UIComponent#processUpdates() для каждого компонента в дереве, как описано в документации к этому методу. Обновление модели выполняется в методе updateModel()
10) компонента.
В процессе обновления модели, события могут добавляться в очередь компонентами, у которых вызывается метод updateModel()
. После этого они (события) будут распространены заинтересованным слушателям событий. В конце этой фазы у всех подходящих11) объектов данных будут обновлены их значения до значений, содержащихся в соответствующих компонентах, а локальные значения в компонентах будут очищены.
Если какой-либо из вызванных методов updateModel()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#responseComplete(), то обработка текущего запроса должна быть немедленно прекращена. Если какой-либо из вызванных методов updateModel()
или слушатель событий (event listener), который обрабатывал событие, вызвал метод FacesContext#renderResponse(), то управление должно немедленно перейти к фазе Render Response. Иначе управление должно перейти к фазе Invoke Application.
Invoke Application
Если обрабока запроса дошла до этой стадии, то предполагается, что все обновления модели выполнены и оставшиеся события должны быть переданы приложению. Реализация JSF должна убедиться, что вызван метод UIViewRoot#processApplication(). По умолчанию этот метод распространяет все события, у которых задан идентификатор фазы PhaseId.INVOKE_APPLICATION.
Продвинутые приложения (или фрэймворки) могут заменять ActionListener
, вызвав метод Application#setActionListener() для текущего приложения. Однако реализация JSF должна предоставить ActionListener
по умолчанию, который ведёт себя согласно описанию.
Render Response
Эта фаза производит два действия:
- Выводит результат клиенту
- Сохраняет состояние результата для последующий запросов.
Эти две задачи объединены в одну фазу потому, что в JSP-приложениях вывод результата может вызвать построение вида (view
) по мере того, как выводится страница. Поэтому невозможно сохранить состояние до тех пор пока вид не построен, а состояние необходимо сохранить дотого, как вывод будет отправлен клиенту для того, чтобы сохранить у клиента состояние.
JSF поддерживает различные подходы, которые реализации JSF могут использовать при создании результата, который соответствует содержимому результирующего вида, включая:
- Получение всего вывода напрямую из результатов вызова методов
encode()
(либо компонентов либо соответствующих рендереров). - Чередование результатов энкодинга компонентов с содержимым которое динамически генерируется логикой приложения.
- Чередование результатов энкодинга компонентов с контентом, который копируется из статического "шаблонного" ресурса.
- Использование результатов энкодинга компонентов посредством вызовов методов в динамическом ресурсе (именно так отображаются компоненты в виде custom-тэгов в JSP-страницах).
Из-за большого количества возможных вариантов, механизм реализации фазы Render Response не может быть точно описан. Однако, все реализации этой фазы должны удовлетворять следующим требованиям:
- Реализация JSF должна предоставить реализацию
ViewHandler
'а по умолчанию, которая вызывает метод RequestDispatcher#forward() для ресурса, путь (относительно контекста) которого равен идентификатору вида текущего дерева компонентов. - Если весь вывод получается из энкодирующих методов компонентов или ассоциированных с ними рендереров, то дерево компонентов должно обходиться в таком же порядке, в каком оно обходилось на более ранних фазах.
- Если содержимое вывода получается из энкодирующих методов и дополнительных источников, компоненты могут рендериться (мда) в произвольном порядке12).
- В процессе рендеринга дополнительные компоненты могут быть добавлены в дерево компонентов на основании информации, имеющейся у реализации ViewHandler'а13). Однако, перед добавлением новых компонентов, реализация
ViewHandler
'а должна предварительно проверить, не содержится ли уже указанный компонент в дереве компонентов. Если уже содержится (возможно из-за того, что предыдущие фазы создали его заранее), должны использоваться свойства и атрибуты уже существующего компонента14). - Ни при каких условиях не должен выбираться компонент для рендеринга, если у какого-либо из его родительских компонентов свойство
rendersChildren
установлено вtrue
. В таких случаях тот родительский компонент должен сам рендерить своих детей, когда он будет выбран. - Если метод isRendered() у компонента возвращает
false
, то рендерер для этого компонента не должен производить никакой разметки и никакой из детей или facet'ов этого компонента не должен быть отрендерен.
Для компонентов, которые реализуют интерфейс ValueHolder (например, UIInput
и UIOutput
), должно выполняться преобразование данных в соответствии с тем, как это описано в документации к UIOutput
.
По завершении рендеринга, конечное состояние вида должно быть сохранено посредством методов класса StateManager. Эта информация о состоянии должна быть доступна при последующем запросе, чтобы ей можно было воспользоваться в фазе Restore View. Более подробная информацию о StateManager
'е можно найти здесь.
На практике
Делаем простенький PhaseListener:
import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; public class MyPhaseListener implements PhaseListener { public void afterPhase(PhaseEvent event) { } public void beforePhase(PhaseEvent event) { if (event.getPhaseId().equals(PhaseId.RESTORE_VIEW)) { System.err.println("----"); } System.err.println(event.getPhaseId().toString()); } public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } }
И наблюдаем следующую картину:
- Прямое обращение к странице:
RESTORE_VIEW 1 RENDER_RESPONSE 6
Вот то самое, о чём я говорил чуть выше.
- Сабмит формы с последующим редиректом:
RESTORE_VIEW 1 APPLY_REQUEST_VALUES 2 PROCESS_VALIDATIONS 3 UPDATE_MODEL_VALUES 4 INVOKE_APPLICATION 5
RESTORE_VIEW 1 RENDER_RESPONSE 6
Дамы и господа, обратите внимание. Запроса два. Причём оба какие-то неполноценные. В первом нет фазы RENDER_RESPONSE
, во втором всё как при простом обращении к странице.
Вывод: если вы хотите, чтобы какой-то кусок кода вызывался при каждом обращении к JSF-приложению (например, вы хотите защитить какие-то страницы от просмотра, то необходимо либо писать фильтр, чтобы запросы к JSF-сервлету проходили сначала через него, либо писать PhaseListener, который будет висеть на фазе RESTORE_VIEW
.
Ссылки по теме
web.xml
с помощью <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.jspx</param-value> </context-param>
failed
. Заменить на более русское слово.appropriate
.