Spring Web Flow — интеграция с JSF (JSF Integration)
13. Spring Web Flow — интеграция с JSF (JSF Integration)
1. Введение
Spring Web Flow предоставляет интеграцию JSF, которая упрощает использование JSF и Spring. Она позволяет использовать JSF UI компонентную модель вместе с контроллерами Spring MVC и Spring Web Flow. Также вместе с JSF интеграцией Spring Web Flow предоставляет библиотеку тегов Spring Security для использования с окружением JSF (более подробно в Section 13.11, “Using the Spring Security Facelets Tag Library”).
Начиная с версии Spring Web Flow 2.4 требуется JSF 2.0 и выше. Поддерживаются обе среды выполнения Sun Mojarra и Apache MyFaces.
Spring Web Flow так же поддерживает окружение портлетов. Она описана в главе 14 английского руководства (на этом сайте перевод не планируется).
13.2. JSF интеграция для Spring разработчиков
Spring Web Flow дополняет сильные стороны JSF — его компонентную модель и предоставляет более сложное управление состоянием и навигацией. Кроме того вы имеете возможность использовать контроллеры Spring MVC @Controller или определение потока в качестве контроллера в вэб слое.
Приложения JSF, использующие Spring Web Flow, получат преимущество в следующих областях:
- Возможности управляемых бинов (managed bean facility)
- Управление областью видимости (scope management)
- Обработка событий (event handling)
- Навигация
- Компоновка и упаковка представлений (modularization and packaging of views)
- Чистка URL’ов (cleaner URLs)
- Валидация на уровне модели (model-level validation)
Использование этих функций значительно сокращают требуемую конфигурацию faces-config.xml. Они предлагают понятное разделение между представлением и контроллером с лучшей модульностью функционала приложения. Эти возможности рассматриваются в этой главе ниже. Большинство этих возможностей строятся на языке определения потоков Spring Web Flow. Поэтому мы рекомендуем вам подробно изучить Spring Web Flow – определение потоков (defining flows).
13.3. Обновление из Spring Web Flow 2.3
Если вы апгрейдитесь с Spring Web Flow 2.3 или раньше, то необходимо обновить несколько аспектов вашего проекта. JSF 2.0 теперь минимально необходимое требование и в результате некоторые компоненты прошлых релизов не включены в поддержку.
13.3.1. Компоненты Spring Faces
Предыдущие релизы Spring Web Flow поставлялись с библиотекой компонентов, которая предоставляла AJAX и валидацию на стороне клиента для окружения JSF 1.2. Приложения использующие эти компоненты нуждаются в переключении на сторонние компоненты JSF такие как PrimeFaces или RichFaces. Следующие компоненты были удалены: <sf:clientTextValidator>, <sf:clientNumberValidator>, <sf:clientDateValidator>, <sf:validateAllOnClick>, <sf:resource> и <sf:resourceGroup>. Пример swf-booking-faces Spring Web Flow показывает приложение построенное на компонентах PrimeFaces.
13.3.2. Конфигурация faces-config.xml
Если в вашем приложении определен файл faces-config.xml, то вам необходимо убедиться, что используется корректная версия схемы. Дополнительно вы должны удалить FaceletViewHandler, т.к. Facelets теперь технология рендера по умолчанию для JSF 2.0
1 2 3 4 5 6 7 |
<?xml version='1.0' encoding='UTF-8'?> <faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0"> </faces-config> |
13.3.3. Сторонние библиотеки
Предыдущие выпуски Spring Web Flow часто требуют дополнительной настройки для конфигурации использования сторонних библиотек компонентов для нормальной работы. JSF 2.0 ввел стандартные механизмы загрузки ресурсов, что позволяет избежать индивидуальных настроек. Пока у вас есть элемент <faces:resources> в вашей конфигурации Spring, библиотеки вроде RichFaces или Apache Trinidad должны работать нормально.
13.3.4. Библиотека тегов Spring Security Facelets
Если у вас имеется предыдущий файл конфигурации /WEB-INF/springsecurity.taglib.xml, то вам необходимо обновить контент. Подробнее в Section 13.11, “Using the Spring Security Facelets Tag Library”.
13.4. Конфигурирование web.xml
Первый шаг для обработки запросов в DispatcherServlet находится в файле web.xml. В этом примере в сервлете мапятся все URL’ы, которые начинаются с /spring/. Для сервлета требуются настройки. Элемент init-param используется в сервлете для доступа к contextConfigLocation. Это местонахождение конфигурации Spring вашего веб приложения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/web-application-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <url-pattern>/spring/*</url-pattern> </servlet-mapping> |
Для правильно загрузки JSF необходимо, чтобы FacesServlet был настроен в web.xml как обычно, хотя, в общем, нет необходимости обрабатывать запросы через этот сервлет при использовании JSF совместно с Spring Web Flow.
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- Just here so the JSF implementation can initialize, *not* used at runtime --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Just here so the JSF implementation can initialize --> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> |
При использовании Facelets вместо JSP обычно требуется добавить в web.xml следующее:
1 2 3 4 5 |
!-- Use JSF view templates saved as *.xhtml, for use with Facelets --> <context-param> <param-name>javax.faces.DEFAULT_SUFFIX</param-name> <param-value>.xhtml</param-value> </context-param> |
13.5. Конфигурирование Web Flow для использования с JSF
Этот раздел объясняет как настроить Web Flow вместе с JSF. Поддерживаются оба вида стилей конфигурации: Java и XML. Пример ниже показывает настройку для Web Flow и JSF в XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:webflow="http://www.springframework.org/schema/webflow-config" xmlns:faces="http://www.springframework.org/schema/faces" si:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.4.xsd http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.4.xsd"> <!-- Executes flows: the central entry point into the Spring Web Flow system --> <webflow:flow-executor id="flowExecutor"> <webflow:flow-execution-listeners> <webflow:listener ref="facesContextListener"/> </webflow:flow-execution-listeners> </webflow:flow-executor> <!-- The registry of executable flow definitions --> <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF"> <webflow:flow-location-pattern value="**/*-flow.xml" /> </webflow:flow-registry> <!-- Configures the Spring Web Flow JSF integration --> <faces:flow-builder-services id="flowBuilderServices" /> <!-- A listener maintain one FacesContext instance per Web Flow request. --> <bean id="facesContextListener" class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener" /> </beans> |
Здесь с помощью Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.faces.config.*; @Configuration public class WebFlowConfig extends AbstractFacesFlowConfiguration { @Bean public FlowExecutor flowExecutor() { return getFlowExecutorBuilder(flowRegistry()) .addFlowExecutionListener(new FlowFacesContextLifecycleListener()) .build(); } @Bean public FlowDefinitionRegistry flowRegistry() { return getFlowDefinitionRegistryBuilder() .setBasePath("/WEB-INF") .addFlowLocationPattern("**/*-flow.xml").build(); } |
Основными моментами являются установка FlowFacesContextLifecycleListener, который управляет одним FacesContext на протяжении запроса Web Flow и использование элемента flow-builder-services из faces настроенного пространства имён для настройки рендера среды JSF.
В среде JSF вам так же необходима эта конфигурация, связанная с Spring MVC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:faces="http://www.springframework.org/schema/faces" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/faces http://www.springframework.org/schema/faces/spring-faces-2.4.xsd"> <faces:resources /> <bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter"> <property name="flowExecutor" ref="flowExecutor" /> </bean> </beans> |
Элемент resources из собственного пространства имен делегирует запросы ресурсов JSF к JSF resource API. JsfFlowHandlerAdapter заменяет FlowHandlerAdapter, обычно используемый с Web Flow. Этот адаптер инициализирует свой собственный JsfAjaxHandler взамен theSpringJavaSciprtAjaxHandler.
При использовании Java конфигурации, AbstractFacesFlowConfiguration автоматически регистрирует JsfResourceRequestHandler и таким образом ничего дополнительно делать не требуется.
13.6. Замена JSF Managed Bean
При использовании JSF совместно с Web Flow вы можете полностью заменить управляемые бины JSF на комбинацию управляемых переменных Web Flow и управляемых бинов Spring. Таким образом вы получите гораздо больше контроля над жизненным циклом управляемых объектов с четко определенными точками инициализации и выполнения в вашей модели. Кроме того, поскольку вы, вероятно, уже используете Spring в вашем бизнес слое, это снижает накладные расходы использования двух различных моделей управляемых бинов.
При разработке на чистой JSF вы достаточно быстро обнаружите, что request scope недостаточно долгий для хранения объектов модели, управляющих сложными событиями в представлениях. Обычное решение для JSF это использование session scope, но это добавляет проблем с излишне длительным хранением объектов и необходимости их удаления перед переходам к следующему представлению или функциональной части приложения. Что действительно необходимо, так это managed scope, который находится где-то посередине между request scope и session scope. JSF предоставляет flash scope и view scope, которые могут быть доступны программно с помощью UIViewRoot.getViewMap(). Spring Web Flow предоставляет flash, view, flow и conversation scope. Эти области видимости интегрированы через переменные JSF и будут работать одинаково во всех JSF приложениях.
13.6.1. Использование переменных потока
Самый простой и наиболее правильный способ для объявления и управления моделью является использование переменных потока flow variables. Вы можете объявить эти переменные в начале потока:
1 |
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/> |
и использовать эту переменную в одном из JSF представлений потока с использованием EL:
1 |
<h:inputText id="searchString" value="#{searchCriteria.searchString}"/> |
Отметьте, что нет необходимости использовать префикс переменной в этой области видимости, если обращение идет из шаблона (но вы можете его указать, если требуется быть более конкретным). Как и для стандартных JSF бинов, все доступные области видимости будут просматриваться для поиска совпадающей переменной, таким образом вы можете менять область видимости переменной в вашем описании потока, без необходимости изменять выражения EL.
Также вы можете определить переменные экземпляра представления, которые будут распространяться на текущее представление и будут очищены автоматически при переходе в другое представление. Это весьма полезно для представлений JSF, которые построены для обработки многих событий внутри страницы через множество запросов, прежде чем произойдет переход к другому представлению.
Для определения переменных экземпляра представления вы можете использовать элемент var внути определения view-state:
1 2 3 |
<view-state id="enterSearchCriteria"> <var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/> </view-state> |
13.6.2. Использование Spring бинов с ограниченной областью видимости
Хотя определение автоматически связываемых переменных экземпляра потока обеспечивает хорошую модульность и читаемость, могут возникнуть случаи, когда вам понадобятся другие возможности Spring контейнера, такие как AOP. В таких случаях вы можете определить бин в Spring AplicationContext и предоставить ему определенную область видимости в потоке:
1 |
<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/> |
Главным отличием такого подхода является то, что компонент не будет полностью инициализирован до тех пор, пока к нему не получат доступ с помощью EL выражения. Своего рода, это ленивая инициализация с помощью EL, которая очень похожа на то, как обычно выделяются управляемые бины JSF.
13.6.3. Управление моделью
Довольно часто требуется инициализировать модель перед рендером представления (например при загрузки сущностей персистентности из базы данных), но JSF само по себе не предоставляет удобных средств для такой инициализации. Язык определения потока предоставляет естественный механизм для таких случаев с помощью своих Actions. Spring Web Flow предоставляет дополнительные удобства для преобразования outcome действия (3.6. Actions) в структуру данных JSF. Пример:
1 2 3 4 |
<on-render> <evaluate expression="bookingService.findBookings(currentUser.name)" result="viewScope.bookings" result-type="dataModel" /> </on-render> |
Результат метода bookingService.findBookings будет обернут в JSF DataModel и таким образом этот список может быть использован в стандартном компоненте JSF DataTable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<h:dataTable id="bookings" styleClass="summary" value="#{bookings}" var="booking" rendered="#{bookings.rowCount > 0}"> <h:column> <f:facet name="header">Name</f:facet> #{booking.hotel.name} </h:column> <h:column> <f:facet name="header">Confirmation number</f:facet> #{booking.id} </h:column> <h:column> <f:facet name="header">Action</f:facet> <h:commandLink id="cancel" value="Cancel" action="cancelBooking" /> </h:column> </h:dataTable> |
13.6.4. Data Model Implementations
В примере выше, результат result-type=”dataModel” обертывается в List<Booking> с пользовательским типом DataModel. Пользовательский DataModel предоставляет дополнительные удобства, такие как возможность сериализовать данные для хранения за пределами request scope, а так же позволяет получить прямой доступ к выбранной строке с помощью выражения EL.
Например, при обратной передачи с формы представления, где произошло событие действия после срабатывания компонента внутри таблицы DataTable, вы можете взять действие с выбранной строки таблицы:
1 2 3 |
<transition on="cancelBooking"> <evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" /> </transition> |
Spring Web Flow предоставляет два пользовательских типа DataModel: OneSelectionTrackingListDataModel и ManySelectionTrackingListDataModel. Согласно их названиям они предоставляют возможность следить за одной или несколькими выделенными строками. Это делается с помощью слушателя SelectionTrackingActionListener, который реагирует на события JSF действий и вызывает, согласно имеющимся характеристикам, соответствующие методы в SelectinAware модели данных для записи из строки, в которой вызвали действие.
Чтобы понять как это настроить, имейте в виду FacesConversionService, который регистрирует DataModelConverter с алиасом “dataModel” при запуске. Когда result-type=”dataModel” используется в определении потока, то необходимо использовать DataModelConverter. Конвертер оборачивает предоставленный список List как экземпляр OneSelectionTrackingListDataModel. Для использования ManySelectionTrackingListDataModel вам необходимо зарегистрировать свой собственный конвертер.
13.7. Обработка событий JSF с помощью Spring Web Flow
Spring Web Flow позволяет обрабатывать события действий JSF по отдельности, без необходимости в прямых зависимостях в коде Java как для JSF API. На самом деле, эти события часто могут быть полностью обработаны в описании потока без необходимости вообще в Java коде. Это позволяет получить более гибкую разработку, поскольку артефакты с раздельными событиями (шаблоны представления JSF и SWF описания потока) мгновенно обновляются без необходимости редеплоя всего приложения.
13.7.1. Обработка JSF Action Events внутри страницы
Простым, но распространённым случаем в JSF является необходимость сигнализировать о событии, которое вызывает манипуляции в модели, а потом снова отображает тоже представление с изменившейся частью модели. Язык описания потока имеет специальную поддержку для этого в элементе transition.
Хорошим примером такого случая будет таблица с постраничным отображением результатов. Предположим, что вы хотите иметь возможность загружать и отображать только часть большого списка результатов и предоставлять пользователю возможность листать страницы с списком результатов. Определение view-state для загрузки и отображения списка будет таким:
1 2 3 4 5 6 |
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> </view-state> |
Вы строите таблицу JSF, которая отображает текущий список hotels и затем помещается ссылку “More Results” после таблицы:
1 |
<h:commandLink id="nextPageLink" value="More Results" action="next"/> |
Эта ссылка является сигналом события “next” из атрибута action. Вы можете обработать событие путем добавления к определению view-state следующего:
1 2 3 4 5 6 7 8 9 |
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> </transition> </view-state> |
Здесь вы обрабатываете событие “next” с помощью увеличения счетчика страниц в экземпляре searchCriteria. Далее действие on-render снова вызывается с обновленным критерием, которое содержит результаты с следующей страницы, которые были загружены в DataModel. Тоже самое представление будет перерисовано, т.к. не указан атрибут to в элементе transition и изменения в модели отобразятся в представлении.
13.7.2. Обработка JSF Action Events
Следующим логическим уровнем после рассмотренных событий внутри страницы являются события, которые требуют навигации к другому представлению с некоторой манипуляцией над моделью во время выполнения перехода. Для чистого JSF необходимо добавлять правила в faces-config.xml, а также использовать управляемые бины JSF (обе задачи требуют редеплоя). С использованием языка описания потока вы можете обрабатывать такие случаи лаконично в одном месте, в том же виде, как и обрабатываете события внутри страницы.
Продолжая рассматривать случай с постраничным списком результатов в таблице, предположим, что мы хотим иметь в каждой строчке таблицы ссылку, которая ведет на страницу с деталями объекта из этой строки. Вы можете добавить колонку в таблицу, которая содержит следующий компонент commandLink:
1 |
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/> |
Событие select можно обработать добавив другой элемент transition в существующий view-state:
1 2 3 4 5 6 7 8 9 10 11 12 |
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> </transition> <transition on="select" to="reviewHotel"> <set name="flowScope.hotel" value="hotels.selectedRow" /> </transition> </view-state> |
Здесь событие select обрабатывается по нажатию на выделенный экземпляр отеля из таблицы DataTable в область flow scope, таким образом это можно использовать в другом view-state «reviewHotel«.
13.7.3. Выполнение валидации модели (Model Validation)
JSF предоставляет полезные средства для проверки ввода на уровне полей до того, как изменения будут применены в модели. Однако в случае, когда вам необходима более комплексная проверка на уровне модели уже после принятых обновлений, вам придется добавить много пользовательского кода в методы, обрабатывающие действия JSF, которые находятся в управляемых бинах. Такого рода валидация, как правило, находится в самой модели, однако бывает сложно передать сообщения об ошибках обратно в представление без добавления нежелательных зависимостей из JSF API для уровня модели.
С Web Flow вы можете использовать дженерики и низкоуровневый MessageContext в бизнес коде и сообщения об ошибках будут добавляться в FacesContext во время рендеринга.
Для примера предположим, что имеется представление, где пользователь вводит необходимые детали для бронирования отеля и вам необходимо проверить, что даты въезда и выезда соответствуют необходимым правилам. Вы можете внедрить проверку на уровне модели внутри элемента transition:
1 2 3 4 5 |
<view-state id="enterBookingDetails"> <transition on="proceed" to="reviewBooking"> <evaluate expression="booking.validateEnterBookingDetails(messageContext)" /> </transition> </view-state> |
Событие proceed будет обработано путем внедрения в метод проверки в экземпляре booking, передавая универсальный (дженерик) экземпляр MessageContext и поэтому сообщения смогут быть записаны. Сообщение может быть отображено вместе с любыми другими JSF сообщениями с помощью компонента h:messages.
13.7.4. Обработка событий Ajax в JSF
В JSF поставляется встроенная поддержка для посылки Ajax запросов и выполнение частичной обработки и рендеринга на стороне сервера. Вы можете указать список id’шников для частичного рендеринга с помощью тега <f:ajax>.
В Spring Web Flow так же можно дополнительная возможность указать id для частичного рендеринга на стороне сервера с помощью render action:
1 2 3 4 5 6 7 8 9 10 |
<view-state id="reviewHotels"> <on-render> <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" result-type="dataModel" /> </on-render> <transition on="next"> <evaluate expression="searchCriteria.nextPage()" /> <render fragments="hotels:searchResultsFragment" /> </transition> </view-state> |
13.8. Вложение потока внутри страницы
По умолчанию, когда поток входит в состояние представления view-state, он выполняет на стороне клиента перенаправления при рендеринге представления. Этот подход известен как POST-REDIRECT-GET. Подход имеет преимущество в том, что отделяет обработку выполнения формы для одного представления от рендера другого представления. В результате кнопки Назад и Обновить в браузере работают правильно, не вызывая каких-либо предупреждений браузера.
Обычно перенаправление на стороне клиента прозрачно с точки зрения пользователя. Тем не менее, бывают ситуации, когда POST-REDIRECT-GET может не принести какой-либо выгоды. Например, поток может быть встроен на странице и приводится в действие посредством Ajax запросов, обновляющих только область страницы, которая принадлежит к этому потоку. В таком случае не только нет необходимости использовать перенаправление на стороне клиента, но также это является не желательным поведением по отношению к сохранению остального содержимого страницы нетронутым.
Чтобы указать, что поток должен выполниться в режиме «внутри страницы», вам необходимо добавить дополнительный атрибут mode с значением embedded. Ниже приведен пример из контейнера потока верхнего уровня, вызывающего подпоток в режиме вложенного:
1 2 3 |
<subflow-state id="bookHotel" subflow="booking"> <input name="mode" value="'embedded'"/> </subflow-state> |
При запуске в режиме embedded встроенный подпоток не будет производить перенаправление при выполнении потока во время Ajax запросов.
Если вы хотели увидеть примеры встроенного потока, то можете обратиться к проекту webflow-primefaces-shocase. Вы можете посмотреть исходный код локально, построив его как проект Maven, и затем импортировав его в Eclipse:
1 2 3 4 5 |
cd some-directory svn co https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase cd webflow-primefaces-showcase mvn package # import into Eclipse |
Указанный пример нужно смотреть на вкладке «Advanced Ajax» с названием «Top Flow with Embedded Sub-Flow».
13.9. Перенаправление в тоже состояние
По умолчанию Web Flow производит перенаправление на стороне клиента даже если он остается в том же view-state, до тех пор, пока текущий запрос не является запросом Ajax. Например, это очень полезно после ошибок проверки формы. Если пользователь нажимает кнопку Обновить или Назад, то он не будет видеть каких-либо предупреждений браузера. Но они бы были, если бы Web Flow не произвел редирект.
Правда это может привести к проблеме характерной для JSF среды, где определенный компонент Sun Mojarra listener кэширует FacesContext, предполагая, что тот же экземпляр доступен на всем жизненном цикле JSF. Однако в Web Flow фаза рендеринга временно приостановлена и на стороне клиента выполняются перенаправления.
Поведение по умолчанию Web Flow является желательным, и вы вряд ли получите проблемы в приложениях JSF. Всё потому, что Ajax часто включен по умолчанию в библиотеках компонентов JSF и Web Flow не будет перенаправлять во время Ajax запросов. Однако, если вы получите такого рода проблемы, то вы можете отключить перенаправления на стороне клиента в то же представление следующим образом:
1 2 3 4 5 |
<webflow:flow-executor id="flowExecutor"> <webflow:flow-execution-attributes> <webflow:redirect-in-same-state value="false"/> </webflow:flow-execution-attributes> </webflow:flow-executor> |
13.10. Обработка загрузки файлов с JSF
Большинство поставщиков компонентов JSF включают некоторую форму компонента «загрузки файлов». Вообще при работе с этими компонентами, JSF должен получить полный контроль над парсингом составных запросов и, таким образом, Spring MVC MultipartResolver не может быть использован.
Spring Web Flow была протестирована с компонентами загрузки файлов из PrimeFaces и RichFaces. Проверьте документацию для вашей библиотеки компонента JSF для других поставщиков, чтобы узнать, как настроить загрузку файла.
13.10.1. File Uploads с PrimeFaces
PrimeFaces предоставляет компонент <р:fileUpload> для загрузки файлов. Для того, чтобы использовать компонент необходимо настроить org.primefaces.webapp.filter.FileUploadFilter фильтр сервлета. Фильтр должен быть настроен взамен DispatcherServlet Spring MVC в вашем web.xml:
1 2 3 4 5 6 7 8 |
<filter> <filter-name>PrimeFaces FileUpload Filter</filter-name> <filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class> </filter> <filter-mapping> <filter-name>PrimeFaces FileUpload Filter</filter-name> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> </filter-mapping> |
Подробнее на сайте primefaces.org.
13.10.2. File Uploads с RichFaces
RichFaces предоставляет компонент <rich:fileUpload> для загрузки файлов. Никаких специальных настроек не требуется, однако, вам нужно будет выполнить несколько дополнительных шагов в fileUploadListener.
Вот некоторая типичная XHTML разметка. В этом примере fileUploadBean ссылается на Spring singleton bean:
1 2 3 4 |
<rich:fileUpload id="upload" fileUploadListener="#{fileUploadBean.listener}" acceptedTypes="jpg, gif, png, bmp"> </rich:fileUpload> |
В вашем fileUploadBean вы должны указать Web Flow, что ответ был обработан и чтобы он не попытался произвести какие-либо редиректы. Интерфейс org.springframework.webflow.context.ExternalContext предоставляет метод recordResponseComplete() как раз для таких целей.
Кроме того, очень важно, чтобы некоторые частичные данные ответа возвращались к клиенту. Если компоненту <rich:fileUpload> не указать атрибут render, то может понадобиться вызвать processPartial(PhaseId.RENDER_RESPONSE) у JSF PartialViewContext.
1 2 3 4 5 6 7 8 9 |
public class FileUploadBean { public void listener(FileUploadEvent event) throws Exception{ FacesContext.getCurrentInstance().getPartialViewContext().processPartial(PhaseId.RENDER_RESPONSE); ExternalContextHolder.getExternalContext().recordResponseComplete(); UploadedFile file = event.getUploadedFile(); // Do something with the file } } |
Более подробно на сайте http://richfaces.jboss.org/
13.11. Использование библиотеки тегов Spring Security Facelets
Чтобы использовать библиотеку, необходимо будет создать файл .taglib.xml и зарегистрировать его в файле web.xml.
Создайте файл в /WEB-INF/springsecurity.taglib.xml следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?xml version="1.0"?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://www.springframework.org/security/tags</namespace> <tag> <tag-name>authorize</tag-name> <handler-class>org.springframework.faces.security.FaceletsAuthorizeTagHandler</handler-class> </tag> <function> <function-name>areAllGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAllGranted(java.lang.String)</function-signature> </function> <function> <function-name>areAnyGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areAnyGranted(java.lang.String)</function-signature> </function> <function> <function-name>areNotGranted</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean areNotGranted(java.lang.String)</function-signature> </function> <function> <function-name>isAllowed</function-name> <function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class> <function-signature>boolean isAllowed(java.lang.String, java.lang.String)</function-signature> </function> </facelet-taglib> |
Далее, зарегистрируйте файл taglib выше в web.xml:
1 2 3 4 |
<context-param> <param-name>javax.faces.FACELETS_LIBRARIES</param-name> <param-value>/WEB-INF/springsecurity.taglib.xml</param-value> </context-param> |
Теперь вы можете использовать библиотеку тегов в ваших представлениях. Вы можете использовать тег authorize для вложения контента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:sec="http://www.springframework.org/security/tags"> <sec:authorize ifAllGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> <sec:authorize ifNotGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> <sec:authorize ifAnyGranted="ROLE_FOO, ROLE_BAR"> Lorem ipsum dolor sit amet </sec:authorize> </ui:composition> |
Вы также можете использовать одну из нескольких функций EL в рендере или другом атрибуте JSF компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:sec="http://www.springframework.org/security/tags"> <!-- Rendered only if user has all of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAllGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user does not have any of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areNotGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user has any of the listed roles --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAnyGranted('ROLE_FOO, ROLE_BAR')}"/> <!-- Rendered only if user has access to given HTTP method/URL as defined in Spring Security configuration --> <h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:isAllowed('/secured/foo', 'POST')}"/> </ui:composition> |
13.12. Интеграция со сторонними библиотеками компонентов
Spring Web Flow JSF интеграция стремится быть совместимой с любыми сторонними библиотеками компонентов JSF. Следующие всем стандартным спецификациям JSF в пределах управляемого SWF жизненного цикла JSF сторонние библиотеки в целом должны «просто работать». Главное помнить, что конфигурацию в web.xml необходимо будет немного изменить, так как запросы Web Flow не проходят через стандартный FacesServlet. Как правило, все, что традиционно отображается на FacesServlet должны быть взамен отображено в Spring DispatcherServlet. (Вы можете также отображать это в обоих сервлетах, если, например, вы переносите действующий проект JSF страницу за страницей.).
3