没有 @ViewScoped 的 JSF

JSF without @ViewScoped

多年来我一直在使用 JSF,在下一个项目中,我们的目标是使 Web 层尽可能无状态。我正在探索的一种可能性是删除 @ViewScoped bean 以支持 @RequestScoped(根据需要加上一两个 @SessionScoped bean)。这对于具有 AJAX、数据表和条件呈现的复杂页面来说很麻烦。我的问题是:JSF(和 PrimeFaces)与无状态 Web bean 的配合情况如何?这是我应该继续探索的东西,还是 @ViewScope 现在如此基础以至于不值得付出努力?

当我写这个问题时,我很感激它可能会被关闭为 'primarily opinion based',但我希望它不是,我对 @ViewScope 解决的具体问题很感兴趣以及我必须通过忽略 @ViewScoped.

重新引入哪些历史性解决方法

How well does JSF (and PrimeFaces) work with stateless web beans?

技术上是可行的。

JSF 主要使用视图状态来跟踪 UIInputUICommand 组件的“禁用”、“只读”和“呈现”属性以及“提交的值” 、“本地值”和“是否有效?” EditableValueHolder 组件的状态(由 UIInput 等组件实现)。

在“disabled”、“readonly”和“rendered”属性的情况下,如果它们表示一个 EL 表达式,那么 JSF 将在处理表单提交请求期间重新检查它。下面是一个基本示例:

<h:form>
    <h:commandButton value="toggle" action="#{bean.toggle}">
        <f:ajax render="panel" />
    </h:commandButton>
    <h:panelGroup id="panel">
        <h:commandButton value="submit" action="#{bean.submit}" rendered="#{bean.toggled}">
            <f:ajax />
        </h:commandButton>
    </h:panelGroup>
</h:form>
@Named
@ViewScoped
public class Bean implements Serializable {

    private static final long serialVersionUID = 1L;

    private boolean toggled;

    public void toggle() {
        this.toggled = !toggled;
    }

    public void submit() {
        System.out.println("Submitted");
    }

    public boolean isToggled() {
        return toggled;
    }
}

首先单击“切换”按钮,然后单击“提交”按钮。如果是视图范围的 bean,它会工作得很好。但是,如果您在此处将 @ViewScoped 替换为 @RequestScoped,那么它将失败,因为此时 toggled 默认返回到 false,此时 JSF 需要解码“提交”按钮postback 请求,因此它的 rendered 属性将评估 false 并且最终 JSF 不会对操作事件进行排队。

在这种情况下,您需要自己确保 属性 在 (post) 请求作用域 bean 的构造期间预初始化为预期值。一种方法是在 ajax-updated 组件中为此使用隐藏的输入字段。这是调整后的示例:

<h:form>
    <h:commandButton value="toggle" action="#{bean.toggle}">
        <f:ajax render="panel" />
    </h:commandButton>
    <h:panelGroup id="panel">
        <input type="hidden" name="toggled" value="#{bean.toggled}" />
        <h:commandButton value="submit" action="#{bean.submit}" rendered="#{bean.toggled}">
            <f:ajax />
        </h:commandButton>
    </h:panelGroup>
</h:form>
@Named
@RequestScoped
public class Bean {

    @Inject @ManagedProperty("#{param.toggled}")
    private boolean toggled;

    public void toggle() {
        this.toggled = !toggled;
    }

    public void submit() {
        System.out.println("Submitted");
    }

    public boolean isToggled() {
        return toggled;
    }
}

NOTE: a <h:inputHidden> will unfortunately not work as it updates the model value only after the action event is to be queued. Even not with a immediate="true" on it. This brings me by the way on the idea for a new <o:inputHidden> for OmniFaces.

通过这些更改,它会正常工作。

但是,由于原来是视图范围的状态(toggled 属性)现在变成了一个请求参数,它完全暴露给世界,因此也可以被黑客篡改。想要调用“提交”按钮而不首先调用“切换”按钮的黑客现在可以简单地手动添加请求参数 toggled=true。这是否可取取决于您的应用程序的业务需求,但通常情况下这是完全不可取的。

这就是 JSF 试图通过提供将这些敏感属性放入 @ViewScoped bean 中的可能性来保护您免受的影响。


This is proving troublesome for complex pages with AJAX, datatables and conditional rendering

是的,但在技术上仍然不是不可能的。您只需通过手动填充的隐藏输入字段手动携带分页、排序和过滤状态,如上所示。 <p:dataTable> 支持将这些状态绑定到 bean 属性。例如:

<p:dataTable ...
    first="#{bean.first}"
    sortField="#{bean.sortField}"
    sortOrder="#{bean.sortOrder}"
    filterBy="#{bean.filterBy}">
    ...
</p:dataTable> 

您可以像之前演示的那样将它们复制到 <input type="hidden"> 字段中(您确保已被 <p:ajax update> 覆盖!),最后通过 @ManagedProperty and/or 获取它们@PostConstruct.

实际上,您基本上是通过这种方式重新发明目前已经由 javax.faces.ViewState 隐藏输入字段与 @ViewScoped beans 结合完成的工作。那么为什么不立即使用它呢? :)

如果您主要关心的是内存使用,那么您需要仔细设计 bean,以便 视图范围状态存储在 @ViewScoped bean 并且 只有 请求范围内的状态存储在 @RequestScoped bean 中。例如,将数据模型放在请求范围的 bean 中,将 paginated/sorted/filtered 状态放在视图范围的 bean 中是完全没问题的。您可能还想考虑 OmniFaces @ViewScoped,因为它会在页面卸载时立即销毁视图状态和物理 bean。

也就是说,考虑到这个问题,我已经 just a few hours ago verified and improved the OptimusFaces 库确保它也完全支持 <f:view transient="true"> 的无状态视图,以及一个新的集成测试。 OptimusFaces 的优势之一是您不再需要手动担心携带 paginated/sorted/filtered 状态。 OptimusFaces 会为你操心

另请参阅:

  • Why JSF saves the state of UI components on server?
  • How to choose the right bean scope?
  • What is the usefulness of statelessness in JSF?