JSF2中的 JSTL... 有意义吗?

我想有条件地输出一点 Faclets 代码。

为此,JSTL 标记似乎工作得很好:

<c:if test="${lpc.verbose}">
...
</c:if>

然而,我不确定这是否是一个最佳实践? 还有其他方法来实现我的目标吗?

96257 次浏览

使用

<h:panelGroup rendered="#{lpc.verbose}">
...
</h:panelGroup>

简介

JSTL <c:xxx>标记都是 跟屁虫,它们在 查看构建时间期间执行,而 JSF <h:xxx>标记都是 UI 组件,它们在 查看渲染时间期间执行。

请注意,从 JSF 自己的 <f:xxx><ui:xxx>标记中,只有那些从 <ui:xxx>5扩展到 <ui:xxx>4的标记也是标记处理程序,例如 <f:validator><ui:include><ui:define>等。从 UIComponent扩展的也是 JSF UI 组件,例如 <f:param><ui:fragment><ui:repeat>等。在视图构建期间,只对 JSFUI 组件中的 <ui:xxx>0和 <ui:xxx>1属性进行评估。因此,以下关于 JSTL 生命周期的答案也适用于 JSF 组件的 <ui:xxx>0和 <ui:xxx>1属性。

视图构建时间就是解析 XHTML/JSP 文件并将其转换为一个 JSF 组件树的时刻,该组件树随后被存储为 FacesContextUIViewRoot。视图呈现时间是 JSF 组件树即将生成 HTML 的时刻,从 UIViewRoot#encodeAll()开始。因此: JSFUI 组件和 JSTL 标记并不像您期望的那样同步运行。您可以将其可视化如下: JSTL 首先从上到下运行,生成 JSF 组件树,然后轮到 JSF 再次从上到下运行,生成 HTML 输出。

<c:forEach> vs <ui:repeat>

例如,使用 <c:forEach>迭代3个项目的 Facelets 标记:

<c:forEach items="#{bean.items}" var="item">
<h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

... 在视图构建期间在 JSF 组件树中创建三个独立的 <h:outputText>组件,大致表示如下:

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

... 在视图呈现期间,它们依次生成自己的 HTML 输出:

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

请注意,您需要手动确保组件 ID 的唯一性,并且在视图构建期间对这些 ID 进行评估。

这个 Facelets 标记使用 <ui:repeat>迭代3个条目,<ui:repeat>是一个 JSF UI 组件:

<ui:repeat id="items" value="#{bean.items}" var="item">
<h:outputText id="item" value="#{item.value}" />
</ui:repeat>

... 已经在 JSF 组件树中按原样结束了,同一个 <h:outputText>组件在视图呈现时是 再利用,以便基于当前迭代周期生成 HTML 输出:

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

请注意,作为 NamingContainer组件的 <ui:repeat>已经确保了基于迭代索引的客户端 ID 的唯一性; 这也不可能在子组件的 id属性中使用 EL,因为它也是在视图构建期间进行评估的,而 #{item}只在视图呈现期间可用。对于 h:dataTable和类似的组件也是如此。

<c:if>/<c:choose> vs rendered

作为另一个例子,这个 Faclets 标记使用 <c:if>有条件地添加不同的标记(也可以使用 <c:choose><c:when><c:otherwise>) :

<c:if test="#{field.type eq 'TEXT'}">
<h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
<h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
<h:selectOneMenu ... />
</c:if>

... 将在 type = TEXT的情况下只将 <h:inputText>组件添加到 JSF 组件树中:

<h:inputText ... />

而这个 Faclets 标记:

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

... 将在 JSF 组件树中完全一样地结束,而不管条件如何。因此,当您有许多这样的组件并且它们实际上是基于“静态”模型(即,至少在视图范围内,field从未改变)时,这可能会以“臃肿”的组件树结束。此外,当你在2.2.7之前处理 Mojarra 版本中附加属性的子类时,你可能会遇到 EL 麻烦

<c:set> vs <ui:param>

它们是不可互换的。<c:set>在 EL 范围内设置一个变量,在视图构建期间只能访问 之后标记位置,但是在视图呈现期间可以访问视图中的任何位置。<ui:param>将 EL 变量传递给通过 <ui:include><ui:decorate template><ui:composition template>包含的 Facet 模板。旧版本的 JSF 存在 bug,因此 <ui:param>变量也可以在有问题的 Faclet 模板之外使用,这是绝对不能依赖的。

没有 scope属性的 <c:set>将表现得像一个别名。它不在任何作用域中缓存 EL 表达式的结果。因此,它可以很好地在内部使用,例如迭代 JSF 组件。因此,下面的例子就可以很好地解决这个问题:

<ui:repeat value="#{bean.products}" var="product">
<c:set var="price" value="#{product.price}" />
<h:outputText value="#{price}" />
</ui:repeat>

它只是不适用于计算循环中的和,而是使用 EL 3.0流:

<ui:repeat value="#{bean.products}" var="product">
...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

只有在使用允许值 requestviewsessionapplication之一设置 scope属性时,才会在视图构建期间立即计算该属性,并将其存储在指定的作用域中。

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

这将只评估一次,并在整个应用程序中作为 #{dev}提供。

使用 JSTL 控制 JSF 组件树的构建

使用 JSTL 可能只会在 JSF 迭代组件(如 <h:dataTable><ui:repeat>等)中使用时导致意想不到的结果,或者当 JSTL 标记属性依赖于 JSF 事件(如 preRenderView)的结果或者模型中提交的表单值(在视图构建时不可用)时导致意外的结果。因此,只使用 JSTL 标记来控制构建 JSF 组件树的流程。使用 JSFUI 组件控制 HTML 输出生成的流程。不要将迭代 JSF 组件的 var绑定到 JSTL 标记属性。不要依赖 JSTL 标记属性中的 JSF 事件。

任何时候,如果您认为需要通过 binding将一个组件绑定到后台 bean,或者通过 findComponent()获取一个组件,并使用带有 new SomeComponent()的后台 bean 中的 Java 代码创建/操作它的子组件,那么您应该立即停止并考虑使用 JSTL 代替。由于 JSTL 也是基于 XML 的,因此动态创建 JSF 组件所需的代码将变得更具可读性和可维护性。

需要知道的是,当在 jSTL 标记属性中引用视图范围内的 bean 时,超过2.1.18的 Mojarra 版本在部分状态保存中存在 bug。整个视图范围内的 bean 将被重新创建,而不是从视图树中检索(仅仅是因为在 JSTL 运行时完整的视图树还不可用)。如果您通过 JSTL 标记属性在视图范围内的 bean 中期望或存储某些状态,那么它将不会返回您期望的值,或者它将在构建视图树之后恢复的实际视图范围内的 bean 中“丢失”。如果你不能升级到 Mojarra 2.1.18或更高版本,你可以像下面这样关闭 web.xml的部分状态保存:

<context-param>
<param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
<param-value>false</param-value>
</context-param>

参见:

要查看一些 JSTL 标记有用的实际示例(例如,在构建视图期间如何正确使用) ,请参见以下问题/答案:


简而言之

至于具体的功能需求,如果您希望有条件地使用 呈现 JSF 组件,则在 JSF HTML 组件上使用 rendered属性,如果 #{lpc}表示 JSF 迭代组件(如 <h:dataTable><ui:repeat>)的当前迭代项,则使用 尤其是

<h:someComponent rendered="#{lpc.verbose}">
...
</h:someComponent>

或者,如果您希望有条件地创建/添加 建造(create/add) JSF 组件,那么继续使用 JSTL。这比在 java 中冗长地执行 new SomeComponent()要好得多。

<c:if test="#{lpc.verbose}">
<h:someComponent>
...
</h:someComponent>
</c:if>

参见:

对于类似开关的输出,您可以使用 PrimeFaces 扩展中的 开关面。