如何选择正确的Bean作用域?

409

我注意到有不同的Bean作用域,如:

@RequestScoped
@ViewScoped
@FlowScoped
@SessionScoped
@ApplicationScoped

每个作用域的目的是什么?我该如何选择合适的作用域来定义我的 bean?

2个回答

524

介绍

它代表了bean的范围(生命周期)。如果您熟悉基本servlet Web应用程序的“底层”工作方式,则更容易理解:Servlet如何工作?实例化、会话、共享变量和多线程


@Request/View/Flow/Session/ApplicationScoped

一个@RequestScoped bean的生命周期与单个HTTP请求-响应周期相同(注意,Ajax请求也算作单个HTTP请求)。一个@ViewScoped bean的生命周期与您通过调用返回null/void且没有导航/重定向的操作方法与JSF视图进行交互的时间相同。一个@FlowScoped bean的生命周期与您在流配置文件中注册的指定视图集合中导航的时间相同。一个@SessionScoped bean的生命周期与已建立的HTTP会话一样长。一个@ApplicationScoped bean的生命周期与Web应用程序运行的时间相同。请注意,CDI @Model 基本上是@Named @RequestScopedstereotype,因此适用相同的规则。

选择哪种范围完全取决于bean持有和表示的数据(状态)。对于简单且非ajax表单/演示,请使用@RequestScoped。对于富含ajax的动态视图(基于ajax的验证、渲染、对话框等),请使用@ViewScoped。对于收集分布在多个页面上的输入数据的“向导”(“问卷”)模式,请使用@FlowScoped。对于客户端特定数据,例如已登录用户和用户首选项(语言等),请使用@SessionScoped。对于应用程序范围的数据/常量,例如下拉列表是每个人都相同的,或者只有方法而没有实例变量的托管bean,请使用@ApplicationScoped
滥用@ApplicationScoped bean用于会话/视图/请求范围的数据将使其在所有用户之间共享,因此其他任何人都可以看到彼此的数据,这是不正确的。滥用@SessionScoped bean用于视图/请求范围的数据将使其在单个浏览器会话中的所有选项卡/窗口之间共享,因此最终用户在切换选项卡后与每个视图交互时可能会遇到不一致性,这对用户体验来说是不好的。滥用@RequestScoped bean用于视图范围的数据将使视图范围的数据在每个单独的(ajax)回发上重新初始化为默认值,可能导致表单不起作用(也请参见这里的4和5点)。滥用@ViewScoped bean用于请求、会话或应用程序范围的数据,以及滥用@SessionScoped bean用于应用程序范围的数据不会影响客户端,但它不必要地占用服务器内存并且效率低下。
请注意,除非您真的具有低内存占用并且想完全无状态,否则不应基于性能影响选择范围;您需要仅使用@RequestScoped bean并调整请求参数以维护客户端状态。还要注意,当您有一个单独的JSF页面具有不同范围的数据时,将它们放在匹配数据范围的不同后备bean中是完全有效的。这些bean可以通过@ManagedProperty(JSF托管bean)或@Inject(CDI托管bean)相互访问。
另请参见:
- 管理bean中视图作用域和请求作用域之间的区别 - 使用JSF Faces Flow而不是常规导航系统的优势 - JSF2中的通信-托管bean范围

@CustomScoped/NoneScoped/Dependent

在您的问题中没有提到,但(传统的)JSF也支持@CustomScoped@NoneScoped, 它们在实际世界中很少使用。 @CustomScoped必须引用自定义的Map<K, Bean>实现,该实现存在于某个更广泛的范围内,并已重写Map#put()和/或Map#get(),以便对bean创建和/或销毁进行更细粒度的控制。

JSF @NoneScoped和CDI @Dependent基本上只会在bean的单个EL评估的生命周期内存在。想象一个登录表单,其中有两个输入字段引用了bean属性,一个命令按钮引用了bean操作,因此总共有三个EL表达式,那么将有效地创建三个实例。一个设置了用户名,一个设置了密码,另一个是执行操作的实例。通常您只想在应该与其注入的bean一样长寿的bean上使用此范围。因此,如果在@SessionScoped中注入@NoneScoped@Dependent,则它将与@SessionScoped bean一样长寿。

参见:


Flash作用域

最后,JSF 还支持 Flash 作用域。它由一个与会话作用域中的数据条目关联的寿命短暂的 cookie 支持。在重定向之前,将在 HTTP 响应上设置一个 cookie,该 cookie 的值与会话作用域中的数据条目唯一关联。重定向后,将检查 Flash 作用域 cookie 的存在,并将与 cookie 关联的数据条目从会话作用域中删除并放入重定向请求的请求作用域中。最后,将从 HTTP 响应中删除 cookie。这样,重定向请求就可以访问在初始请求中准备好的请求作用域数据。

实际上,Flash 作用域不能作为托管 Bean 作用域使用,即没有像 @FlashScoped 这样的东西。通过 ExternalContext#getFlash() 在托管 Bean 中仅可将 Flash 作用域作为映射访问,而在 EL 中使用 #{flash}

另请参阅:


4
我认为在这里提到了 你的回答 对于问题“JSF中的视图作用域Bean何时销毁?”是相关的。 - Lii
3
@Cold: 这是一个旧的CDI作用域,在JSF 2.2中被 @FlowScoped 替代(无需手动启动/停止)。 - BalusC
1
DeltaSpike还额外提供了ViewAccesscopedWindowScoped - Kukeltje
@BalusC,我认为在MyFaces 2.2中存在一个问题,与ViewScoped bean有关。我目前正在面临一个ViewScoped bean和Ajax的问题,我已经在这里发布了。在MyFaces JIRA中,也有一个讨论关于这个主题。 - Tapas Bose
CDI定义了四个内置作用域:`@RequestScoped` `@SessionScoped` `@ApplicationScoped` `@ConversationScoped` 为什么你所描述的这些作用域是不同的呢? - Hosein Aqajani

128

自从JSF 2.3版本以后,javax.faces.bean包中定义的所有bean作用域都已被弃用,以使作用域与CDI对齐。此外,它们仅适用于使用@ManagedBean注释的bean。如果您使用的是低于2.3版本的JSF,请参考末尾的传统答案。


从JSF 2.3开始,可以在JSF Backing Beans上使用以下范围:

1. @javax.enterprise.context.ApplicationScoped:应用程序作用域持续整个Web应用程序的生命周期。该范围在所有请求和所有会话之间共享。当您拥有整个应用程序的数据时,这很有用。

2. @javax.enterprise.context.SessionScoped:会话范围从建立会话到会话终止为止持续存在。会话上下文在同一HTTP会话中发生的所有请求之间共享。当您想要为特定客户端保存特定会话的数据时,这很有用。

3. @javax.enterprise.context.ConversationScoped:对话范围与bean的生存期一样长。该范围提供两种方法:Conversation.begin()Conversation.end()。这些方法应明确调用,以启动或结束bean的生命周期。

4. @javax.enterprise.context.RequestScoped: 请求范围是短暂的。它在HTTP请求提交时开始,在响应发送回客户端后结束。如果您将托管bean放入请求范围,则每个请求都会创建一个新实例。如果您担心会话范围存储的成本,值得考虑请求范围。

5. @javax.faces.flow.FlowScoped: 流程范围持续时间与流程一样长。流程可以定义为一组包含的页面(或视图),用于定义工作单元。只要用户在流程中导航,流程范围的bean就处于活动状态。

6. @javax.faces.view.ViewScoped: 视图范围内的bean在重新显示相同的JSF页面时持久存在。一旦用户导航到不同的页面,bean就会失去作用范围。


以下传统答案适用于2.3版本之前的JSF。
自 JSF 2.x 开始,有四种 Bean Scopes:
- @SessionScoped - @RequestScoped - @ApplicationScoped - @ViewScoped Session Scope: Session Scope 可以从建立会话开始一直持续到会话结束。如果 Web 应用程序调用 HttpSession 对象的 invalidate 方法或会话超时,会话将终止。 RequestScope: Request Scope 生命周期较短。它在提交 HTTP 请求时开始,在响应发送回客户端后结束。如果将托管的 bean 放入请求范围,则每个请求都会创建一个新实例。如果您关注会话范围存储的成本,可以考虑使用请求范围。 ApplicationScope: Application Scope 持续整个 Web 应用程序的生命周期。该范围在所有请求和所有会话之间共享。如果希望单个 bean 在 Web 应用程序的所有实例之间共享,可以将管理的 bean 放入应用程序范围。当任何用户首次请求时构造该 bean,并且在从应用程序服务器中删除 Web 应用程序之前保持活动状态。 ViewScope: View Scope 添加于 JSF 2.0。视图范围中的 bean 在重新显示相同的 JSF 页面时持久存在。 (JSF 规范使用 view 表示 JSF 页面。) 一旦用户导航到不同的页面,bean 就会失去作用。
根据您的需求选择范围。

来源:Core Java Server Faces第3版,David Geary和Cay Horstmann著[第51-54页] enter image description here

(注:此处为格式要求,无需翻译)

请问您所说的“HttpSession对象上的invalidate方法”是指invalidate()方法还是无效方法? - Alexander Pozdneev
1
可能有点晚了,但是为了澄清一下:他的意思是在你的“注销bean”中调用FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); - Roland
1
它已经成为遗留答案,目前有8个作用域。 - Ewoks
@KishorPrakash:一段时间过去了,现在已经是6个月前的事情了。;-) - Kukeltje
@Kukeltje:抱歉,我正在处理。 - Kishor Prakash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接