Скачиваний:
88
Добавлен:
09.05.2014
Размер:
823.27 Кб
Скачать

Объект calculatorController будет помещен в область видимости request. При этом ссылка на calculator будет передана в calculatorController. Это делается с помощью выражения #{calculator} в теге <managed-property>. Таким образом, JSF создаст экземпляр класса Calculator и передаст его в метод setCalculator классаCalculatorController, как показано в листинге 28:

Листинг 28. Объявление объекта calculator в области видимости request и связывание с помощью тега <managedproperty>

<managed-bean> <managed-bean-name>calculatorController</managed-bean-name> <managed-bean-class>

com.arcmind.jsfquickstart.controller.CalculatorController </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property>

<property-name>calculator</property-name> <value>#{calculator}</value>

</managed-property> </managed-bean>

Объект calculator используется классом CalculatorController, но при этом сам остается ―чистым‖, т.е. никоим образом не привязанным от JSF. Классы модели всегда желательно держать независимыми от библиотек, подобных JSF, изолируя весь JSF-зависимый код внутри контроллера, в данном случае – классе CalculatorController. Это значительно облегчает тестирование и повторное использование модели.

Привязка компонентов JSF к классу CalculatorController

Класс CalculatorController тесно связан с JSF – это особенность архитектуры приложения. К нему привязаны три компонента JSF. Один из них – resultsPanel, представляющий собой секцию для вывода результатов арифметических операций, покзан в листинге 29:

Листинг 29. Компонент resultsPanel в классе CalculatorController

private UIPanel resultsPanel;

...

public UIPanel getResultsPanel() { return resultsPanel;

}

public void setResultsPanel(UIPanel resultPanel) { this.resultsPanel = resultPanel;

}

За привязку resultsPanel к CalculatorController отвечает JSF. Обратите внимание на атрибут binding в листинге 30:

Листинг 30. Привязка компонентов к контроллеру

<h:panelGroup binding="#{calculatorController.resultsPanel}" rendered="false"> <h4>Results</h4>

<h:panelGrid columns="1" rowClasses="oddRow, evenRow" styleClass="resultGrid">

<h:outputText value="First Number #{calculatorController.calculator.firstNumber}"/> <h:outputText value="Second Number #{calculatorController.calculator.secondNumber}"/> <h:outputText value="Result #{calculatorController.calculator.result}"/>

</h:panelGrid>

/h:panelGroup>

Значением это атрибута является выражение "#{calculatorController.resultsPanel}", связывающее свойствоresultsPanel с компонентом. JSF сохраняет ссылку на компонент в этом свойстве, вызывая методcalculateController.setResultsPanel при загрузке страницы. Таким образом, JSF позволяет легко манипулировать элементами интерфейса, не требуя явного обхода или поиска в дереве компонентов.

На самом деле, сначала JSF вызывает метод calculateController.getResultsPanel. Если компонент уже создан, то он будет использован в представлении. Если же метод вернул null, то JSF создаст новый экземпляр компонента, а затем передаст его в метод calculateController.setResultPanel, привязав таким образом к объекту контроллера.

В листинге 31 показано, как компонент resultsPanel используется внутри метода CalculateController.add():

Навигация в JSF

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

Правила перехода

Как правило, диаграмму переходов Web-приложения удобнее создавать с помощью специальных средств. В частности, многие IDE предоставляют такие возможности для JSF-приложений. Eclipse JEE включает в себя Navigation Rule Layout Tool, пример использования которого показан на рисунке 12:

Рисунок 12. Редактирование правил переходов в Eclipse

Рисунки 11 и 12 должны стать намного понятнее после прочтения данного раздела.

В некоторых случаях навигация не нужна

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

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

Для начала добавим домашнюю страницу со ссылкой на главную страницу Калькулятора, которую мы разделим на две части : на представление самого калькулятора и результатов выполнения операции. В дополнение к этому нам понадобятся правила переходов между представлением калькулятора, страницей результатов и домашней страницей приложения.

Добавление ссылки с домашней страницы на главную страницу Калькулятора

Ссылку можно добавить тремя различными способами:

С помощью commandLink и обычного правила перехода

С помощью commandLink и правила перехода, использующего элемент <redirect>

С помощью outputLink

Первый способ реализуется путем добавления правила перехода в файл faces-config.xml, как показано в листинге 38:

Листинг 38. Правило перехода, определенное в файле faces-config.xml

<navigation-rule> <navigation-case>

<from-outcome>CALCULATOR</from-outcome> <to-view-id>/pages/calculator.jsp</to-view-id>

</navigation-case> </navigation-rule>

Правило в листинге 38 указывает JSF, что после любого действия (action), чьим результатом является логический признакCALCULATOR, необходимо загрузить /pages/calculator.jsp в качестве представления. Данное правило является динамическим, т.е. оно применимо к любому действию, возвращающему CALCULATOR. Исключением является случай, когда можно применить более конкретное правило, например, связанное с конкретным представлением с помощью

элемента <from-view-id> (на этом мы остановимся позже). Таким образом, правило приведенное в листинге 38

аналогично глобальным переходам (global forwards) в Struts.

На странице необходимо добавить элемент <h:commandLink> в качестве дочернего к <h:form>, как показано в листинге 39:

Листинг 39. Использование <h:commandLink> на домашней странице

<h:form>

<h:panelGrid columns="1">

<h:commandLink action="CALCULATOR" value="Calculator Application"/>

...

Все работает, как и должно: по данной ссылке загружается главная страница Калькулятора. Однако пользователя может смутить то, что в адресной строке браузера по-прежнему фигурирует http://localhost:8080/calculator3/home.jsf. Для кого-то это может показаться нормальным, особенно для людей, часто работающих с Web-приложениями. Но это может помешать сохранить страницу Калькулятора в закладках браузера, т.к. ее подлинный адрес неизвестен.

Это можно поправить с помощью элемента <redirect> в правиле перехода, описанном в faces-config.xml (см. листинг 40):

Листинг 40. Правило перехода и элемент redirect

<navigation-rule> <navigation-case>

<from-outcome>CALCULATOR_REDIRECT</from-outcome> <to-view-id>/pages/calculator.jsp</to-view-id> <redirect/> <!-- LOOK HERE -->

</navigation-case> </navigation-rule>

<h:commandLink> по-прежнему использует правило перехода, используя атрибут action. Теперь при нажатии на ссылку браузер будет перенаправлен на страницу Калькулятора.

Листинг 41. Правило перехода с использованием <redirect>

<h:commandLink action="CALCULATOR_REDIRECT" value="Calculator Application (redirect)"/>

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

Этого дополнительного запроса можно избежать, если вы не против разместить прямую ссылку на требуемую страницу, как показано в листинге 42:

Листинг 42. Связывание с помощью прямой ссылки (элемента <h:outputLink>)

<h:outputLink value="pages/calculator.jsf">

<h:outputText value="Calculator Application (outputlink)"/> </h:outputLink>

В листинге 42 показана прямая ссылка на следующее представление, что, как правило, считается признаком плохого стиля при использовании Model 2 и JSF. Желательно, чтобы у контроллера была возможность инициализировать классы

модели для следующего представления, поэтому лучше делать не прямой вызов, а через какой-либо action-метод контроллера. Но если нужна просто ссылка и правильный адрес в браузере, то в листинге 42 показано, как это сделать. Долгое время сохранять ссылки на JSF-приложения в закладках было достаточно проблематично. Некоторые библиотеки, например, JBoss Seam, предлагали свои решения этой проблемы. Ей также будет уделено внимание в следующей версии

JSF – JSF 2.

Переход на страницу результата

После выполнения арифметической операции, пользователь должен быть перенаправлен на страницу, отображающую результат. Для этого добавим правило перехода, приведенное в листинге 43:

Листинг 43. Правило для перехода из любого представления Калькулятора на страницу результата

<navigation-rule>

<display-name>Calculator View</display-name> <from-view-id>/pages/calculator.jsp</from-view-id> <navigation-case>

<from-outcome>results</from-outcome> <to-view-id>/pages/results.jsp</to-view-id>

</navigation-case> </navigation-rule>

Согласно данному правилу, если текущим представлением является calculator.jsp, а action-метод возвращает признакresults, то должен быть выполнен переход к странице результатов (results.jsp). JSF автоматически преобразовывает возвращаемые значения action-методов к строковому виду и использует их для выбора следующего представления. Методы могут возвращать значения любых типов, потому как для преобразования используется метода toString. Например, многие используют перечисления (Enum.)

Далее мы изменим выходные значения всех операций на results.

Наше CRUD-приложение состоит из следующих элементов:

ContactController: контроллер JSF

Contact: объект, хранящий данные модели, в данном случае контактную информацию

ContactRepository: объект модели, отвечающий за создание, чтение, модификацию и удаление объектов типаContact.

contacts.jsp: страница JSP (JavaServer Pages), содержащая дерево компонентов JSF, служащих для управления контактами.

faces-config.xml: конфигурационный файл JSF, в котором объявляются управляемые объекты JavaBean, в частности, экземпляры ContactController и ContactRepository. Кроме этого, ссылка

на ContactRepositoryсохраняется в свойстве ContactController через механизм инъекции зависимостей

(dependency injection).

ContactController

ContactController необходим для функционирования страницы contacts.jsp. Его код показан в листинге 1:

Листинг 1. ContactController

public class ContactController {

/** Contact Controller взаимодействует с contactRepository. */ private ContactRepository contactRepository;

/** Ссылка на редактируемый контакт. */ private Contact contact = new Contact();

/** Выбранный контакт. */ private Contact selectedContact;

/** Текущая форма. */ private UIForm form;

/** Команда добавления. */ private UICommand addNewCommand;

/** Команда сохранения контакта в модель. */ private UICommand persistCommand;

/** Свойство для сохранения ссылки на репозиторий. */

public void setContactRepository(ContactRepository contactRepository) { this.contactRepository = contactRepository;

}

public void addNew() { form.setRendered(true); addNewCommand.setRendered(false); persistCommand.setValue("Add");

}

//Большинство get- и set-методов опущено

}

Весь графический интерфейс CRUD занимает в листинге 1 менее 74 строк кода — не так уж плохо, не правда ли? ОбъектContactController находится в области видимости запроса, поэтому при его инстанциировании также создается и объект типа Contact. Три компонента JSF —form (класс UIForm), addNewCommand (класс UICommand)

и persistCommand(класс UICommand) — привязаны к экземпляру ContactController.

Метод addNew() отвечает за:

включение формы для добавления новых контактов — form.setRendered(true)

выключение компонента addNewCommandaddNewCommand.setRendered(false)

установку для компонента persistCommand значения AddpersistCommand.setValue("Add")

Метод persist() служит для добавления новых контактов и редактирования существующих с помощью объектаcontactRepository. Этот метод делает недоступной форму и активирует объект addNewCommand. Для удаления контактов служит метод remove(), тоже использующий contactRepository.

Метод read() копирует объект selectedContact типа Contact в переменную contact. Она также имеет тип Contact и привязана к форме. При этом само значение переменной selectedContact является следствием выбора пользователем одного контакта из списка (мы вернемся к этому моменту в разделе, посвященному JSP). Как и впредыдущей части, сообщения о результатах операций будут храниться в объекте addStatusMessage, а выводиться с помощью

тега <h:messages>.

Слой представления

Как видно из листинга 2, на странице JSP используется компонент <h:dataTable> (мы обошли его вниманием в первой части):

Листинг 2. contacts.jsp

<head>

<title>Contacts</title>

<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/main.css" />

</head>

<body>

<f:view>

<h4>Contacts</h4>

<h:messages infoClass="infoClass" errorClass="errorClass" layout="table" globalOnly="true" />

<h:form>

<h:commandLink binding="#{contactController.addNewCommand}" action="#{contactController.addNew}" value="Add New..." />

</h:form>

<h:form binding="#{contactController.form}" rendered="false" styleClass="form">

<h:inputHidden value="#{contactController.contact.id}" /> <h:panelGrid columns="6">

<%-- Имя --%>

<h:outputLabel value="First Name" for="firstName" accesskey="f" /> <h:inputText id="firstName" label="First Name" required="true"

value="#{contactController.contact.firstName}" size="10" /> <h:message for="firstName" errorClass="errorClass" />

Список контактов выводится из contactController компонентом <h:dataTable> с помощью выражения "#{contactController.contacts}". При выводе ссылка на каждый контакт доступна через переменную contact, объявленную в атрибуте var: var="contact". В теле contacts.jsp используются

стили oddRow и evenRow, описанные в файле CSS: rowClasses="oddRow, evenRow". Благодаря им соседние строки в таблице хорошо визуально различимы. Компонент <h:dataTable> обладает очень широкими возможностями, поэтому за полным описанием лучше обратиться к документации Javadoc, ссылка на которую приведена в разделе Ресурсы.

Также можно установить единый стиль для всей таблицы (styleClass="contactTable") либо для ее заголовка (headerClass="headerTable"). Кроме того, можно назначить чередующиеся стили колонкам с помощью атрибутаcolumnClasses:columnClasses="normal,centered". Сама таблица не будет отображаться в случае, если не существует ни одного контакта - за это отвечает атрибут rendered: rendered="#{not empty contactController.contacts}". Как видите, <h:dataTable> - очень мощный и достаточно простой в использовании компонент.

Элементы <h:column> используются внутри <h:dataTable> для вывода свойств объектов. Для каждой колонки определяется заголовок с помощью тега <f:facet>. facet (грань) – это именованный компонент, используемый другими компонентами. Далее, после <f:facet> внутри <h:column> можно использовать <h:outputText> для вывода значений свойств firstName и lastName текущего контакта.

Каждая строка таблицы содержит ссылки для удаления (remove) и редактирования (edit) контакта. Они представляют собой компоненты <h:commandLink>, связанные c

методами contactController.remove и contactController.readсоответственно. Выбранный в таблице контакт присваивается свойству contactController.selectedContact. Это реализуется

элементом <f:setPropertyActionListener>, который копирует контакт, связанный с выбранной строкой, перед обработкой нажатия на ссылку remove или edit.

Использование компонентов JSF

Вэтом разделе мы будем совершенствовать наше CRUD-приложение с помощью различных компонентов JSF:

<f:subview>

<h:selectOneMenu>

<h:selectOneRadio>

<h:selectBooleanCheckbox>

<h:selectManyCheckbox>

<h:inputTextarea>

Внешний вид этих компонентов в составе интерфейса приложения показан на рисунке 3:

Рисунок 3. Интерфейс приложения с несколькими часто используемыми компонентами JSF

Подпредставления (subviews)

Можно предположить, что такое количество компонентов JSF будет трудно встроить в одну страницу. К счастью, их можно разбить на отдельные подпредставления (subviews), используя элемент <f:subview>, как показано в листинге 6:

Листинг 6. Подпредставления на странице contacts.jsp

<body>

<f:view>

<h3>Contacts (2nd version)</h3>

<h:messages infoClass="infoClass" errorClass="errorClass" layout="table" globalOnly="true" />

<h:form>

<h:commandLink binding="#{contactController.addNewCommand}" action="#{contactController.addNew}" value="Add New..." />

</h:form>

<f:subview id="form"> <jsp:include page="form.jsp" />

</f:subview>

<f:subview id="listing">

<jsp:include page="listing.jsp" /> </f:subview>

</f:view>

</body>

Тег <f:subview> можно использовать на родительской либо на вложенной странице, но не на обеих одновременно. В JSF 1.2 <f:subview>, в отличие от предыдущих версий, не является обязательным элементом. Впрочем, некоторые IDE ожидают его использования вне зависимости от того, с какой версией JSF вы работаете.

Компоненты для выбора из списка

В JSF каждый компонент состоит из двух частей – собственно компонента и рендерера, отвечающего за его отображение. Компонент UISelectOne может иметь несколько рендереров, как и его наследники:

компонентыHtmlSelectOneListbox, HtmlSelectOneMenu и HtmlSelectOneRadio.

В нашем приложении (а точнее, во второй его версии) используется компонент <h:selectOneMenu>. Для его работы потребуются три новых объекта модели: Group (код показан в листинге 7), Tag и ContactType. Кроме этого понадобятся два новых объекта-репозитория: GroupRepository и TagRepository, аналогичных ContactRepository.

ТипуContactType репозиторий не нужен, потому что он представляет собой перечисление (Enum). Теперь наш

класс Contactбудет включать в себя три новых свойства: группу, которой он принадлежит (group), теги, которыми он помечен (tags), и тип (type).

Листинг 7. Использование <h:selectOneMenu> для выбора группы

<%-- Group --%>

<h:outputLabel value="Group" for="group" accesskey="g" /> <h:selectOneMenu id="group" validatorMessage="required"

value="#{contactController.selectedGroupId}"> <f:selectItems value="#{contactController.groups}" /> <f:validateLongRange minimum="1" />

</h:selectOneMenu>

<h:message for="group" errorClass="errorClass" />

Обратите внимание на атрибут value элемента <h:selectOneMenu>. Он используется для связывания со свойствомselectedGroupId. Внутри <h:selectOneMenu> находится элемент <f:selectItems>, значение которого связано со свойством groups контроллера:value=#{contactController.groups}. Список групп создается и хранится в невидимом для пользователей объекте JavaBean. Код свойств selectedGroupId и groups приведен в листинге 8:

Листинг 8. Формирование списка групп

public class ContactController {

...

private GroupRepository groupRepository;

...

private Long selectedGroupId;

...

public List<SelectItem> getGroups() { List<Group> groups = groupRepository.list();

List<SelectItem> list = new ArrayList<SelectItem>(groups.size()+1); list.add(new SelectItem(Long.valueOf(-1L), "select one"));

for (Group group : groups) {

SelectItem selectItem = new SelectItem(group.getId(), group.getName()); list.add(selectItem);

}

return list;

}

//Другие get- и set-методы опущены

...

Свойство groups возвращает список объектов типа SelectItem, который используется для представления отдельных элементов. Этот класс используется компонентами UISelectMany и UISelectOne. Обратите внимание на то, что в методе getGroups используется ссылка на объект-репозиторий, необходимый для управления списком групп (groupRepository). Он связывается с контроллером методом инъекции зависимостей аналогично репозиторию контактов (contactRepository). Каждая группа является объектом типа Group, а контакт - экземпляром классаContact.

Метод getGroups() создает список объектов SelectItem, используя свойства group.id и group.name в качестве значения и метки соответственно.

Обратите внимание на специальный элемент SelectItem со значением -1 – он необходим для проверки того, был ли выбран элемент из списка. Это делается простым сравнением <f:validateLongRange minimum="1" /> внутри компонента <h:selectOneMenu>, как показано выше в листинге 7. Также отметим, что в компоненте включен вывод кратких сообщений об ошибках: validatorMessage="required".

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

При отправке формы на сервер устанавливается значение свойства selectedGroupId. Оно используется в методеpersist(), который связан с кнопками добавления и редактирования контактов, для поиска нужной группы в репозитории. Код приведен в листинге 9:

Листинг 9. Использование свойства selectedGroupId в методе persist()

public class ContactController {

...

public void persist() {

/* Установить значение группы для контакта */ contact.setGroup(groupRepository.lookup(selectedGroupId));

/* Сделать форму недоступной, а ссылку для добавления контакта - доступной */ form.setRendered(false);

addNewCommand.setRendered(true);

/* Добавить сообшение об успешности операции . */ if (contactRepository.persist(contact) == null) {

addStatusMessage("Added " + contact); } else {

addStatusMessage("Updated " + contact);

}

}

...

public void read() {

/* Prepare selected contact. */ contact = selectedContact;

/* Форму сделать доступной, а ссылку - нет. */ form.setRendered(true); addNewCommand.setRendered(false);

/* Сохранить идентификатор группы в свойстве. */ selectedGroupId = contact.getGroup().getId();

...

this.selectedTagIds = tagIds.toArray(new Long[tags.size()]);

addStatusMessage("Read " + contact); persistCommand.setValue("Update");

}

...

Обратите внимание, что класс Contact добавляет свойство group. Таким образом, между классами Contact и Groupсуществует отношение типа ―многие-к-одному‖.

Метод read() в листинге 9 инициализирует свойство this.selectedTagIds, так что он отображается в представлении как выбранный.

Вы уже видели, как можно работать с отношениями типа ― многие-к-одному ‖ с помощью компонента <h:selectOne>. Кроме этого у класса Contact также существует отношение типа ―многие-ко-многим‖ с классом Tag через свойство tags. Для работы с таким типом отношений используется компонент <h:selectManyCheckbox> (см. листинг 10):

Листинг 10. Использование selectManyCheckbox вместе со свойством Contact.tags

<h:selectManyCheckbox id="tags" value="#{contactController.selectedTagIds}">

<f:selectItems value="#{contactController.availableTags}" />

</h:selectManyCheckbox>

Класс UISelectMany является предком не только компонента <h:selectManyCheckbox>, но и <h:selectManyListbox>вместе с <h:selectManyMenu>. Вы можете их использовать вместо <h:selectManyCheckbox>, как показано в листингах 12, 13 и на рисунке 4.

Листинг 12. <selectManyListbox>

<h:selectManyListbox id="tags" value="#{contactController.selectedTagIds}"> <f:selectItems value="#{contactController.availableTags}"/>

</h:selectManyListbox>

Листинг 13. <selectManyMenu>

<h:selectManyMenu id="tags" value="#{contactController.selectedTagIds}"> <f:selectItems value="#{contactController.availableTags}"/>

</h:selectManyMenu>

Рисунок 4: Компоненты <selectManyListbox> и <selectManyMenu>

Как видите, все три компонента организованы одинаково.

JSF 1.2 предоставляет специальные конвертеры для перечислений (Enum), поэтому для них не требуется поиск в репозитории. Перечисления можно использовать напрямую, без промежуточных свойств контроллера, как в случае тегов и групп (см. листинги 8 и 11). Использование перечисления в классе Contact показано в листинге 14:

Другие компоненты демонстрационного приложения

Наше CRUD-приложение также демонстрирует использование

компонентов <h:selectBooleanCheckbox> и<h:inputTextarea> (см. листинг 17):

Листинг 17. Использование компонентов <h:selectBooleanCheckbox> и <h:textArea>

<h:inputHidden value="#{contactController.contact.id}" />

...

<%-- active --%>

<h:outputLabel value="Active" for="active" accesskey="a" /> <h:selectBooleanCheckbox id="active"

value="#{contactController.contact.active}" /> <h:message for="active" errorClass="errorClass" />

<%-- Description --%>

...

<h:outputLabel value="Description" for="description" accesskey="d" style="font: large;" />

<h:inputTextarea id="description" cols="80" rows="5" value="#{contactController.contact.description}" />

<h:message for="description" errorClass="errorClass" />

Элемент <h:inputTextarea> содержит два дополнительных атрибута, задающих размер области: cols="80" rows="5". Связывание компонентов со свойствами объектов работает как прежде.

Код свойств, к которым привязываются компоненты в листинге 17, приведен в листинге 18:

Листинг 18. КлассContact

public class Contact implements Serializable {

...

private String description; private boolean active; protected long id;

...

public String getDescription() { return description;

}

public void setDescription(String description) { this.description = description;

}

public boolean isActive() {

return active;

}

public void setActive(boolean active) { this.active = active;

}

public long getId() { return id;

}

public void setId(long id) { this.id = id;

}

...

}

Свойства id, description, type, firstName и lastName класса Contact напрямую связаны со значениями формы интерфейса приложения. Свойства group и tags не могут связываться напрямую, потому что для их типов данных отсутствует стандартный конвертер JSF. Ниже, в разделе "Конвертеры данных в JSF", мы расскажем о конвертерах и создадим конвертеры для нашего приложения. Но до этого давайте обратимся к вопросу о жизненном цикле обработки запросов в приложениях JSF.

Жизненный цикл обработки запросов в приложениях JSF

Вопреки распространенному мнению, приложения JSF можно создавать, не зная всех деталей этой технологии. Во многих аспектах можно разобраться по ходу разработки вашего первого проекта. Однако знание основополагающих принципов может сделать вашу работу значительно более продуктивной и поможет сэкономить время. В этом разделе мы отвлечемся от нашего демонстрационного приложения и рассмотрим шесть фаз, составляющих жизненный цикл обработки запросов в приложениях JSF. Мы подробно остановимся на каждой фазе и их взаимосвязях. Это облегчит понимание оставшихся разделов руководства.

Фазы жизненного цикла

Жизненный цикл обработки запроса в приложениях JSF состоит из следующих фаз:

1.Восстановление представления

2.Использование параметров запроса; обработка событий

3.Проверка данных; обработка событий

4.Обновление данных модели; обработка событий

5.Вызов приложения; обработка событий

6.Вывод результата

Фазы перечислены в порядке, в котором JSF, как правило, обрабатывает запросы, поступившие через формы GUI. На каждом этапе возможна обработка событий. При этом фазы не обязательно следуют одна за другой в строго установленном порядке. Более того, отдельные фазы можно пропускать или вовсе прерывать цикл. Например, если компоненту было присвоено некорректное значение, переданное в параметре запроса, то можно повторно отобразить текущее представление, отменив последующие этапы обработки.

Также существует возможность делегировать обработку запросов сервлету или другой инфраструктуре Web-приложений. Для этого следует вызвать метод FacesContext.responseComplete, переадресующий пользователя к другой странице или Web-ресурсу, а затем, с помощью диспетчера запросов (ссылку на который можно получить через объект request вFacesContext) передать обработку соответствующему ресурсу. Можно также повторно вывести текущее представление с помощью метода FacesContext.renderResponse.

Такой жизненный цикл обработки помогает структурировать процесс создания приложения, не ограничивая при этом свободу разработчика. Цикл можно менять при необходимости, не опасаясь нарушить логику выполнения. В большинстве случаев, однако, имеет смысл придерживаться данных этапов, потому что цикл JSF хорошо продуман и логичен.

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

Разработчики, использующие JSF, решают разные задачи: одни вообще никогда не расширяют стандартную инфраструктуру и не добавляют компоненты, а другие могут заниматься этим постоянно. Несмотря на то, что сам жизненный цикл JSF остается неизменным практически в любом проекте, разработчикам приходится работать с различными его этапами в соответствии с их ролью в проекте. Например, программисты, занимающиеся разработкой приложения в целом, как правило, имеют дело со срединными фазами цикла обработки запроса, такими как:

Использование параметров запроса; обработка событий

Проверка данных; обработка событий

Использования данных модели; обработка событий

Вызов приложения; обработка событий

Те же, кто специализируется на создании компонентов JSF, в основном работают с первой и последней фазами цикла:

Восстановление представления

Вывод результата

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

Рисунок 5. Жизненный цикл обработки запроса в JSF

Фаза 1: Восстановление представления

В первой фазе обработки —этапе восстановления представления— запрос поступает на вход сервлета FacesServlet. Последний анализирует данные запроса и извлекает идентификатор представления, определяемый именем страницы

JSP.

Идентификатор используется контроллером JSP для поиска компонентов, ассоциированных с данным представлением. Если оно не существует, то контроллер создаст его, если существует - будет использовать существующее. Представление содержит в себе все необходимые компоненты графического интерфейса.

Существуют три типа представлений, с которыми приходится иметь дело на данном этапе: новое, изначальное и повторное (postback). Представление каждого типа обрабатывается по-своему. В случае нового представления JSF создает представление страницы Faces и связывает компоненты с обработчиками событий и валидаторами данных. Затем оно сохраняется в объекте FacesContext.

Этот объект служит для хранения информации, необходимой для управления состоянием компонентов GUI в процессе обработки запросов. FacesContext сохраняет состояние в свойстве viewRoot. Данное свойство содержит все компоненты JSF, ассоциированные с данным идентификатором представления.

Изначальное представление означает, что данная страница вызывается впервые. В этом случае JSF создает пустое представление, которое заполняется по мере обработки JSP-страницы. Поэтому JSF сразу переходит к этапу отрисовки результата.

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

Фаза 2: Использование данных запроса

Главной задачей данного этапа является получение данных о состоянии каждого компонента. Сами компоненты хранятся или создаются внутри объекта FacesContext вместе со своими значениями. Значения компонентов, как правило, приходят в параметрах запроса, хотя могут извлекаться также из cookies и заголовков запроса. Многие компоненты сохраняют значения параметра запроса в свойстве submittedValue.

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

Фаза 3: Проверка данных

Конвертация и валидация данных, как правило, выполняются в фазе проверки данных. Компонент конвертирует и сохраняет значение свойства submittedValue. Например, если данное поле связано со свойством типа Integer, то значение будет преобразовано к типу Integer. Если конвертация проходит неудачно, создается соответствующее сообщение об ошибке, направляемое в очередь FacesContext для последующего вывода в фазе отрисовки результата. После завершения второй фазы происходят первые в жизненном цикле действия по обработке событий. На данном этапе значения каждого компонента проверяются в соответствии с правилами валидации конкретного приложения. Правила могут быть стандартными, входящими в поставку JSF, или определяться разработчиком. Данные, введенные пользователем, проверяются на соответствие этим правилам. В случае, если проверка выявляет несоответствие, компонент помечается как невалидный, а также генерируется сообщение об ошибке, которое помещается вFacesContext. В этом случае JSF сразу переходит к фазе отрисовки результата, игнорируя все остальные фазы. В результате отображается текущее представление, содержащее все сообщения об ошибках валидации. Если ошибок валидации нет, JSF переходит к фазе обновления модели.

Фаза 4: Обновление модели

В четвертой фазе жизненного цикла JSF происходит обновление данных модели путем изменения свойств серверных объектов JavaBean. Обновляются только те свойства, которые привязаны к значениям компонентов. Обратите внимание, что этот этап следует за валидацией, поэтому можно быть уверенным, что значения, сохраняемые в свойствах серверных объектов, будут корректными как минимум на уровне полей форм. Впрочем, они могут быть некорректными с точки зрения бизнес-правил приложения.

Фаза 5: Вызов приложения

В этой фазе контроллер JSF вызывает приложение для обработки данных, полученных через форму. Значения компонентов уже преобразованы к нужным типам, валидированы и сохранены в объектах модели. Теперь их можно использовать для выполнения бизнес-логики приложения.

Именно на данном этапе вызываются такие action-методы, как persist() и read() объекта ContactController нашего демонстрационного приложения.

Кроме того, на данном этапе необходимо задать следующее логическое представление для данной цепочки (или нескольких цепочек) обработчиков. Для этого необходимо задать признак успешной обработки данных формы и возвращать этот признак в конце обработки. Например, "в случае успешной обработки, перенаправить пользователя к следующей странице". Для осуществления такой навигации необходимо создать отображение такого признака успешного завершения на правило перехода в файле faces-config.xml. Срабатывание правила означает переход к заключительной фазе цикла. JSF берет объект, возвращенный action-методом, и вызывает его метод toString(). Далее полученное значение используется в качестве признака результата для поиска нужного правила перехода. Настройка правил перехода подробно рассмотрена в предыдущей части.

Фаза 6: Вывод результата

В заключительной фазе цикла происходит вывод представления вместе со всеми его компонентами и их текущими состояниями.

На рисунке 6 показана диаграмма прохождения всех фаз жизненного цикла JSF, включая валидацию и обработку событий:

Рисунок 6. Шесть фаз цикла обработки запроса в JSF

Конвертеры данных в JSF

Конвертация – это процесс преобразования данных к нужным типам. В процессе конвертации строковые поля форм преобразуются в даты (класс Date), примитивные типы float, в объекты типа Float и т.д. JSF позволяет использовать как встроенные, так и специально созданные для данного приложения конвертеры. Вначале мы расскажем об использовании стандартных конвертеров JSF, а затем подробно рассмотрим создание специализированных конвертеров.

Стандартные конвертеры JSF

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

Таблица 1. Стандартные конвертеры

 

Конвертер

 

Реализующий класс

 

 

 

 

 

 

 

 

 

javax.faces.BigDecimal javax.faces.convert.BigDecimalConverter

javax.faces.BigInteger javax.faces.convert.BigIntegerConverter

javax.faces.Boolean javax.faces.convert.BooleanConverter

javax.faces.Byte

javax.faces.convert.ByteConverter

javax.faces.Character javax.faces.convert.CharacterConverter

javax.faces.DateTime javax.faces.convert.DateTimeConverter

javax.faces.Double javax.faces.convert.DoubleConverter

Соседние файлы в папке лабораторная работа 7 (jsf)