没有@ViewScoped 的 JSF [英] JSF without @ViewScoped
问题描述
多年来我一直在使用 JSF,在下一个项目中,我们的目标是使 Web 层尽可能无状态.我正在探索的一种可能性是删除 @ViewScoped
bean 以支持 @RequestScoped
(根据需要加上一两个 @SessionScoped
bean).这对于带有 AJAX、数据表和条件渲染的复杂页面来说很麻烦.我的问题是:JSF(和 PrimeFaces)与无状态 Web bean 的配合情况如何?这是我应该继续探索的东西,还是 @ViewScope
现在如此基础以至于不值得付出努力?
我很感激在我写这个问题时它可能会被关闭为主要基于意见",但是我希望它不是,我对 @ViewScope
解决了我必须通过忽略 @ViewScoped
重新引入哪些历史性变通方法.
JSF(和 PrimeFaces)与无状态 Web bean 的配合如何?
技术上是可行的.
JSF 主要使用视图状态来跟踪禁用"、只读"状态.和渲染"UIInput
和 UICommand
组件的属性以及提交的值"、本地值"和有效吗?"EditableValueHolder
<的状态/a> 组件(由其他UIInput
实现).
在禁用"的情况下,只读"和渲染"属性,如果这些代表一个 EL 表达式,那么 JSF 将在处理表单提交请求时重新检查它.下面是一个基本示例:
<h:commandButton value="toggle";动作=#{bean.toggle}"><f:ajax render="面板"/></h:commandButton><h:panelGroup id=面板"><h:commandButton value="提交"动作=#{bean.submit}";渲染=#{bean.toggled}"><f:ajax/></h:commandButton></h:panelGroup></h:form>
@Named@ViewScoped公共类 Bean 实现了 Serializable {private static final long serialVersionUID = 1L;私有布尔值切换;公共无效切换(){this.toggled = !toggled;}公共无效提交(){System.out.println(提交");}公共布尔 isToggled() {返回切换;}}
首先点击切换"按钮按钮,然后是提交"按钮.如果是视图范围的 bean,它会工作得很好.但是,如果您在此处将 @ViewScoped
替换为 @RequestScoped
,那么它将失败,因为 toggled
默认返回 false
> 目前 JSF 需要解码提交"文件按钮在回发请求期间,因此它的 rendered
属性将评估 false
并且最终 JSF 不会将操作事件排队.
在这种情况下,您需要确保自己在请求范围 bean 的(后)构造期间将属性预初始化为预期值.一种方法是在 ajax 更新的组件中为此使用隐藏的输入字段.这是调整后的示例:
<h:commandButton value="toggle";动作=#{bean.toggle}"><f:ajax render="面板"/></h:commandButton><h:panelGroup id=面板"><输入类型=隐藏"名称=切换"值=#{bean.toggled}"/><h:commandButton value="提交"动作=#{bean.submit}";渲染=#{bean.toggled}"><f:ajax/></h:commandButton></h:panelGroup></h:form>
@Named@RequestScoped公共类豆{@Inject @ManagedProperty("#{param.toggled}")私有布尔值切换;公共无效切换(){this.toggled = !toggled;}公共无效提交(){System.out.println(提交");}公共布尔 isToggled() {返回切换;}}
<块引用>
注意:不幸的是,
将不起作用,因为它仅在 将动作事件排队之后更新模型值.即使没有 immediate="true"
.这让我想到了为 OmniFaces 设计一个新的
.
通过这些更改,它会正常工作.
然而,由于最初是视图范围的状态(toggled
属性)现在已成为请求参数,它完全暴露给世界,因此也可以被黑客篡改.想要调用提交"的黑客按钮而不调用切换"首先按钮现在可以简单地手动添加请求参数toggled=true
.这是否可取取决于您的应用程序的业务需求,但通常情况下它是完全不可取的.
这就是 JSF 试图通过提供将这些敏感属性放在 @ViewScoped
bean 中的可能性来保护您免受伤害.
<块引用>
这对于带有 AJAX、数据表和条件渲染的复杂页面来说很麻烦
没错,但在技术上仍然不是不可能.您只需要通过手动填充的隐藏输入字段手动处理分页、排序和过滤状态,如上所示.
支持将这些状态绑定到 bean 属性.例如:
您可以像之前演示的那样将它们复制到 字段中(您确保它们被
!) 并最终通过 @ManagedProperty
和/或 @PostConstruct
获取它们.
实际上,您通过这种方式基本上重新发明了当前由 javax.faces.ViewState
隐藏输入字段与 @ViewScoped
bean 组合完成的工作.那么为什么不立即使用它呢?:)
如果您主要关心内存使用情况,那么您需要仔细设计 bean,使仅视图范围状态存储在 @ViewScoped
bean 中并且仅请求范围状态存储在 @RequestScoped
bean 中.例如,将数据模型放在请求作用域 bean 中,将分页/排序/过滤状态放在视图作用域 bean 中是完全正确的.您可能还想立即考虑使用 OmniFaces @ViewScoped
页面卸载时销毁视图状态和物理 bean.
也就是说,考虑到这个问题,我几个小时前 验证并改进了 OptimusFaces 库,以确保它也完全支持带有 < 的无状态视图;f:viewtransient="true">
,以及一个新的集成测试.OptimusFaces 的优点之一是您不再需要手动担心携带分页/排序/过滤状态.OptimusFaces 会替你操心.
另见:
- 为什么 JSF 会保存 UI 组件的状态在服务器上?
- JSF:Mojarra vs.OmniFaces @ViewScoped:@PreDestroy 被调用但 bean 不能被垃圾回收
- 如何选择合适的 bean 范围?
- 无状态在 JSF 中有什么用处?
I've been using JSF for many years and in the next project we're aiming to make the web-tier as stateless as possible. One possibility I'm exploring is removing @ViewScoped
beans in favour of @RequestScoped
(plus one or two @SessionScoped
beans as required). This is proving troublesome for complex pages with AJAX, datatables and conditional rendering. My question is: how well does JSF (and PrimeFaces) work with stateless web beans? Is this something that I should continue to explore, or is @ViewScope
now so fundamental that it's not worth the effort?
I appreciate as I write this question that it might be be closed as 'primarily opinion based', however I'm hoping that it isn't, I'm interested in specific problems that @ViewScope
solved and what historic workarounds I'd have to re-introduce by ignoring @ViewScoped
.
How well does JSF (and PrimeFaces) work with stateless web beans?
It's technically possible.
JSF uses the view state primarily to keep track of the "disabled", "readonly" and "rendered" attributes of the UIInput
and UICommand
components as well as the "submitted value", "local value" and "is valid?" states of the EditableValueHolder
components (implemented by among others UIInput
).
In case of "disabled", "readonly" and "rendered" attributes, if these represent an EL expression, then JSF will re-check it during processing the form submit request. Below is a basic example:
<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;
}
}
First click the "toggle" button and then the "submit" button. In case of a view scoped bean, it'll work just fine. If you however replace @ViewScoped
by @RequestScoped
here, then it'll fail, because toggled
defaults back to false
at the moment JSF needs to decode the "submit" button during the postback request, and so its rendered
attribute will evaluate false
and ultimately JSF won't queue the action event.
In such case, you need to make sure yourself that the property is preinitialized to the expected value during (post)construction of the request scoped bean. One way is to use hidden input fields for this within the ajax-updated component. Here's the adjusted example:
<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 aimmediate="true"
on it. This brings me by the way on the idea for a new<o:inputHidden>
for OmniFaces.
With these changes, it'll work fine.
However, as the state which was originally view scoped (the toggled
property) has now become a request parameter, it's fully exposed to the world and therefore also tamperable by hackers. Hackers wanting to invoke the "submit" button without invoking the "toggle" button first can now simply manually add a request parameter toggled=true
. Whether that's desirable depends on the business requirements of your application, but more than often it's totally undesirable.
This is what JSF is trying to protect you from by offering the possibility to put these sensitive properties in a @ViewScoped
bean instead.
This is proving troublesome for complex pages with AJAX, datatables and conditional rendering
True, but still not technically impossible. You only have to manually carry around the paginated, sorted and filtered states via manually populated hidden input fields as demonstrated above. The <p:dataTable>
supports binding these states to bean properties. For example:
<p:dataTable ...
first="#{bean.first}"
sortField="#{bean.sortField}"
sortOrder="#{bean.sortOrder}"
filterBy="#{bean.filterBy}">
...
</p:dataTable>
You can just copy them into <input type="hidden">
fields as demonstrated before (which you make sure are covered by <p:ajax update>
!) and finally grab them via @ManagedProperty
and/or the @PostConstruct
.
In effects, you're this way basically reinventing the job currently already done by javax.faces.ViewState
hidden input field in combination with @ViewScoped
beans. So why not using it right away? :)
If your primary concern is the memory usage, then you need to carefully design your beans in such way that only the view scoped state is stored in a @ViewScoped
bean and that only the request scoped state is stored in a @RequestScoped
bean. For instance, it's perfectly fine to put the data model in the request scoped bean and the paginated/sorted/filtered state in the view scoped bean. You may also want to consider OmniFaces @ViewScoped
instead as that immediately destroys the view state and the physical bean when the page is unloaded.
That said, with this question in mind, I've just a few hours ago verified and improved the OptimusFaces library to ensure that it also fully supports stateless views with <f:view transient="true">
, along with a new integration test. The advantage of OptimusFaces is among others that you don't anymore need to manually worry about carrying around the paginated/sorted/filtered state. OptimusFaces will worry about it for you.
See also:
- Why JSF saves the state of UI components on server?
- JSF: Mojarra vs. OmniFaces @ViewScoped: @PreDestroy called but bean can't be garbage collected
- How to choose the right bean scope?
- What is the usefulness of statelessness in JSF?
这篇关于没有@ViewScoped 的 JSF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!