21. JSF整合
Spring Web Flow 提供了一个 JavaServer Faces(JSF)集成,允许你将 JSF UI 组件模型与 Spring Web Flow 控制器一起使用。 Web Flow 还提供 Spring Security 标签库,供 JSF 环境中使用。 详情请参见使用 Spring Security面贴标签库。
Spring Web Flow 4.0 需要 JSF 4.1 或更高版本。
21.1. 配置web.xml
第一步是将请求路由到调度器服务在web.xml文件。
在下面的例子中,我们将所有以 开头的 URL 映射起来/Spring/给servlet。
servlet 需要配置。
一初始参数在servlet中用于传递contextConfigLocation.
这是你Web应用Spring配置的位置。
以下列表展示了配置详情:
<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正确引导,面圈必须配置于web.xml这通常是这样,尽管使用 JSF 配合 Spring Web Flow 时,通常不需要通过它路由请求。
以下列表展示了配置详情:
<!-- Just here so the JSF implementation can initialize. *Not* used at runtime. -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>jakarta.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>
使用Facelet代替JSP通常需要以下元素web.xml:
!-- Use JSF view templates saved as *.xhtml, for use with Facelets -->
<context-param>
<param-name>jakarta.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
21.2. 配置用于 JSF 的网页流
本节解释如何用 JSF 配置 Web Flow。 支持Java和XML配置。 以下示例配置为 Web Flow 和 JSF 的 XML 格式:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
https://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd
http://www.springframework.org/schema/faces
https://www.springframework.org/schema/faces/spring-faces.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 配置中也做了同样的作:
@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那是一颗面孔上下文在Web流请求期间,以及使用流量-构建器-服务来自面临自定义命名空间,用于配置JSF环境的渲染。
在JSF环境中,你还需要以下与Spring MVC相关的配置:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/faces
https://www.springframework.org/schema/faces/spring-faces.xsd">
<faces:resources />
<bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
</beans>
这资源自定义命名空间元素将 JSF 资源请求委托给 JSF 资源 API。
这JsfFlowHandlerAdapter是流量处理适配器通常与Web Flow一起使用。
该适配器通过JsfAjaxHandler而不是SpringJavaScriptAjaxHandler.
当你使用 Java 配置时,抽象面流配置基类会自动注册JsfResourceRequestHandler,所以没有更多可做的了。
21.3. 取代JSF管理的Beans设施
当你用 JSF 配合 Spring Web Flow 时,你可以完全用 Web Flow 管理变量和 Spring 管理的 Beans 来完全替代 JSF 管理的 Bean 功能。 它通过明确的钩子来初始化和执行你的域模型,给你对托管对象生命周期的控制权大大提升。 此外,既然你大概已经在业务层用 Spring,它减少了维护两个不同托管 Bean 模型的概念负担。
如果你纯粹做JSF开发,你可能会很快发现请求范围的寿命不够长,无法存储驱动复杂事件驱动视图的对话模型对象。
在 JSF 中,通常的选择是先将内容放入会话范围,但额外需要清理对象,才能进入应用的另一个视图或功能区域。
真正需要的是一个介于请求和会话范围之间的托管范围。
JSF提供闪烁和视野示波器,可通过程序访问UIViewRoot.getViewMap().
Spring Web Flow 提供闪回、视图、流和对话等功能。
这些示波器通过JSF变量解析器无缝集成,并在所有JSF应用中工作相同。
21.3.1. 使用流变量
声明和管理模型最简单、最自然的方式是使用流变量。 你可以在流程开始时声明这些变量,具体如下:
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
然后你可以通过EL在流程的JSF视图模板中引用该变量,具体如下:
<h:inputText id="searchString" value="#{searchCriteria.searchString}"/>
注意,引用模板时不需要在变量前加上作用域(不过如果需要更具体也可以)。 和标准的 JSF 豆子一样,所有可用的作用域都会被搜索匹配的变量,所以你可以在流量定义中更改变量的范围,而无需修改引用它的 EL 表达式。
你还可以定义视图实例变量,这些变量的作用域对应当前视图,切换到另一个视图时会自动清理。 这在 JSF 中非常有用,因为视图通常设计为处理多个页面内事件,跨多个请求后再切换到其他视图。
要定义视图实例变量,可以使用VAR元素在视图状态定义如下:
<view-state id="enterSearchCriteria">
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
</view-state>
21.3.2. 使用带瞄准镜的春豆
虽然定义自动连接的流程实例变量提供了良好的模块化和可读性,但有时你可能会想使用Spring容器的其他功能,比如面向切面编程(AOP)。
在这种情况下,你可以在春季中定义豆子应用上下文并赋予其特定的网页流域,具体如下:
<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/>
这种方法的主要区别在于,豆子直到首次通过EL表达式访问时才会被完全初始化。 这种通过EL进行的懒惰实例化,与JSF管理豆子通常的分配方式非常相似。
21.3.3.作模型
在视图渲染前先初始化模型(例如从数据库加载持久实体)的需求相当常见,但JSF本身并未提供任何方便的初始化钩子。 流定义语言通过其动作为 提供了自然的便利性。 Spring Web Flow 为将动作结果转换为 JSF 专用数据结构提供了一些额外的便利。 以下示例展示了如何实现:
<on-render>
<evaluate expression="bookingService.findBookings(currentUser.name)"
result="viewScope.bookings" result-type="dataModel" />
</on-render>
前述例子取了bookingService.findBookings并用自定义的JSF数据模型包裹,使该列表可以在标准的JSF DataTable组件中使用,具体如下:
<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>
21.3.4. 数据模型实现
在前文示例中,result-type=“dataModel”结果是预订名单<>有个习俗数据模型类型。
习俗数据模型提供了额外的便利,比如可序列化以超出请求范围的存储,以及访问EL表达式中当前选择的行。
例如,在后期回发时,从一个视图中,动作事件是由组件在数据表你可以对所选行的模型实例采取以下作:
<transition on="cancelBooking">
<evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" />
</transition>
Spring Web Flow 提供了两种自定义数据模型类型:OneSelectionTrackingListDataModel和ManySelectionTrackingListDataModel.
顾名思义,它们会跟踪一行或多行选中的行。
这是在SelectionTrackingActionListener监听器,响应JSF动作事件并调用相应的方法SelectionAware数据模型用于记录当前点击的行。
要理解这是如何配置的,请记住FacesConversionService寄存器A数据模型转换器针对该别名dataModel启动时。
什么时候result-type=“dataModel”在流量定义中使用时,它会导致数据模型转换器被使用。
转换器随后对给定的 进行包裹列表其中一个实例为OneSelectionTrackingListDataModel.
使用ManySelectionTrackingListDataModel你需要注册自己的自定义转换器。
21.4. 利用春季网页流处理JSF活动
Spring Web Flow 允许你以解耦的方式处理 JSF 动作事件,无需在 Java 代码中直接依赖 JSF API。 事实上,这些事件通常可以在流定义语言中完全处理,无需任何自定义的Java动作代码。 这使得开发过程更加敏捷,因为在布线事件中作的工件(JSF 视图模板和 SWF 流程定义)可以立即刷新,无需构建和重新部署整个应用程序。
21.4.1. 处理JSF页面内动作事件
JSF中一个简单但常见的情况是需要发出信号,导致模型作,然后重新显示相同视图以反映模型的变化状态。
流定义语言对此有特别支持,在过渡元素。
一个很好的例子是分页列表结果表。
假设你只想加载并显示大型结果列表中的一部分,并让用户浏览结果。
首字母视图状态加载和显示列表的定义如下:
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
</view-state>
你可以构建一个JSF数据表来显示当前情况酒店列出并放置更多结果表格下方有链接,如下:
<h:commandLink id="nextPageLink" value="More Results" action="next"/>
这commandLink信号 a下一个其事件行动属性。
然后你可以通过在视图状态定义如下:
<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>
来,你来处理下一个通过增加页数来事件检索标准实例。
这渲染时随后再次调用动作,更新后的条件,使下一页结果加载到数据模型.
同样的视图被重新渲染,因为没有自属性过渡元素,模型的变化会反映在视图中。
21.4.2. 处理JSF行动事件
页面内事件之外的下一个逻辑层级是需要导航到另一个视图的事件,并在过程中对模型进行一定的作。
用纯JSF实现这一点需要在faces-config.xml以及可能在JSF管理的豆中加入一些中间的Java代码(这两项任务都需要重新部署)。使用流定义语言,你可以在一个地方简洁地处理此类情况,类似于处理页面内事件的方式。
继续我们作分页结果列表的用例,假设我们希望每一行都显示在数据表包含该行实例详细页面的链接。
你可以在表格中添加一列,包含以下内容commandLink分量如下:
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>
这会提升选择事件,你可以通过添加另一个事件来处理过渡元素视图状态如下:
<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>
这里,选择事件通过将当前选择的酒店实例从数据表将其纳入流域,以便被酒店评论 视图状态.
21.4.3. 执行模型验证
JSF提供了有用的工具,可在模型变更前验证现场层面的输入。 然而,当更新应用后需要在模型层面进行更复杂的验证时,通常需要在托管豆中添加更多自定义代码到JSF动作方法。 此类验证通常是领域模型本身的责任,但很难将错误消息传回视图,否则会对你的领域层的JSF API产生不良依赖。
通过Web Flow,你可以使用通用和底层消息上下文在你的业务代码中,添加的任何消息随后都可以被面孔上下文在渲染时。
例如,假设你有一个视图,用户输入完成酒店预订所需的详细信息,你需要确保登记和退房日期遵循一套特定的商业规则。
你可以从过渡元素如下:
<view-state id="enterBookingDetails">
<transition on="proceed" to="reviewBooking">
<evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
</transition>
</view-state>
这里,进行事件通过在预订实例上调用模型级验证方法来处理,传递通用的消息上下文实例,以便记录消息。
这些消息随后可以与其他任何JSF消息一起显示在h:信息元件。
21.4.4. 处理JSF中的Ajax事件
JSF 内置支持发送 Ajax 请求以及在服务器端执行部分处理和渲染。
你可以通过<f:ajax>facelets 标签。
在 Spring Web Flow 中,你还可以选择通过渲染动作指定用于服务器端部分渲染的 ID,具体如下:
<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>
21.5. 在页面上嵌入流程
默认情况下,当流程进入视图状态时,会在渲染视图前执行客户端重定向。 这种方法被称为“POST-REDIRECT-GET”。 它的优点是将一个视图的表单处理与下一个视图的渲染分离。 因此,浏览器的返回和刷新按钮可以无缝使用,不会触发任何浏览器警告。
通常,客户端重定向从用户视角是透明的。 然而,在某些情况下,“POST-REDIRECT-GET”可能带来同样的好处。 例如,有时在页面上嵌入一个流程,并用 Ajax 请求驱动,只刷新渲染流程的页面区域,可能会很有用。 在这种情况下,不仅不需要使用客户端重定向,而且这也不是保持页面周围内容完整的理想行为。
为了表示流程应以“页面嵌入”模式执行,你可以传递一个额外的流输入属性,称为模式其值为嵌入式. 以下示例展示了顶层容器流调用嵌入模式下的子流:
<subflow-state id="bookHotel" subflow="booking">
<input name="mode" value="'embedded'"/>
</subflow-state>
当以“页面嵌入”模式启动时,子流不会在 Ajax 请求期间发出流执行重定向。
关于嵌入流的示例,请参见Webflow-Primefaces-Showcase项目。 你可以在本地查看源代码,像构建 Maven 项目一样,然后导入到 Eclipse 或其他 IDE,具体如下:
cd some-directory
git clone https://github.com/spring-projects/spring-webflow-samples.git
cd primefaces-showcase
mvn package
# import into Eclipse
你需要查看的具体示例是在“高级Ajax”标签下,名为“带嵌入式子流程的顶层流”。
21.6. 同一州的重定向
默认情况下,只要当前请求不是 Ajax 请求,Web Flow 即使视图状态相同,也会进行客户端重定向。这在表单验证失败后非常有用(例如)。如果用户点击刷新或返回,他们不会看到任何浏览器警告。如果 Web Flow 没有执行重定向,警告会显示。
这可能导致JSF环境特有的问题,即某个特定的Sun Mojarra监听器组件缓存面孔上下文假设同一实例在整个JSF生命周期中均可用。然而,在Web流程中,渲染阶段会暂时暂停,并执行客户端重定向。
Web Flow 的默认行为是理想的,JSF 应用程序不太可能遇到该问题。这是因为 Ajax 通常在 JSF 组件库中被默认启用,且 Web Flow 在 Ajax 请求时不会重定向。不过,如果你遇到这个问题,可以在同一视图内禁用客户端重定向,具体如下:
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-attributes>
<webflow:redirect-in-same-state value="false"/>
</webflow:flow-execution-attributes>
</webflow:flow-executor>
21.7. 使用JSF处理文件上传
大多数 JSF 组件提供商都包含某种形式的文件上传组件。通常,在处理这些组件时,JSF 必须完全控制解析多部分请求和 Spring MVC 的解析MultipartResolver不能使用。
Spring Web Flow 已通过 PrimeFaces 的文件上传组件进行测试。请查看 JSF 组件库的文档,了解其他提供商如何配置文件上传。
通常,你需要在 Servlet 容器中启用多部分支持,可以通过在调度器服务web.xml 中的声明,或通过使用jakarta.servlet.MultipartConfigElement在程序化 Servlet 注册中
21.8. 使用 Spring Security Facelets 标签库
要使用库,你需要创建一个taglib.xml归档并注册web.xml.
你需要创建一个名为/网-INF/springsecurity.taglib.xml内容如下:
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"https://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如下:
<context-param>
<param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/springsecurity.taglib.xml</param-value>
</context-param>
现在你可以在视图中使用标签库了。你可以使用授权标签来有条件地包含嵌套内容,具体如下:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://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>
你也可以在任何JSF组件的渲染或其他属性中使用多个EL函数之一,具体如下:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://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>