在 JSF 2 的 requestScoped bean 中显式创建会话

Posted

技术标签:

【中文标题】在 JSF 2 的 requestScoped bean 中显式创建会话【英文标题】:Creating session explicitly in requestScoped bean in JSF 2 【发布时间】:2013-02-24 08:28:50 【问题描述】:

我对在 JSF 2.x 中的 requestScoped bean 中创建会话有疑问。我知道我们不需要在 JSF 2.x 中创建会话,因为我们可以直接使用 sessionScoped bean 来放置用户所需的数据。但是我最近被介绍到一个 JSF 代码,其中开发人员创建了会话的实例变量并从 facescontext 获取会话,如下所示

 @ManagedBean
 @RequestScoped
 Public class someClass()

 FacesContext facesContext = FacesContext.getCurrentInstance();
 HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);

 public void someFunction()

 //Some code here..

 session.setAttribute("key","value");

 //Some code here..

 

  //rest of the code here...
 

好吧,我告诉他们不应该将“会话”作为实例变量,也不需要在 JSF 2 中显式获取会话,而是使用 sessionScoped bean。

首先我给出的理由是“将会话保留为实例变量不是线程安全的”,而第二个语句我给出的原因是“您正在为已经提供的 JSF 会话做一个解决方法”。

我得到的反驳是, 首先,“我们的应用程序是 Web 应用程序,因此不存在多线程问题”。 第二个我得到“无论如何,一旦处理请求,会话变量将被清除,所以这里没有任何错误

我没有其他强项来纠正它。所以我的问题是, 1)他们正确吗? 2) 是否有任何其他具体原因说明我们应该以应该做的方式做事? 3) 最后,有什么可能促使他们使用这种方式?

有人可以详细说明吗? 请纠正我。 谢谢

【问题讨论】:

一个 webapp 是多线程的,可以同时处理请求。请求可以具有相同的会话 ID。看看Is HttpSession thread safe。 @NicolasLabrot 是的,当从同一台机器上的两个不同浏览器请求时,它可以具有相同的会话 ID,因此应该以多线程方式处理。除了这种情况,如果我们谈论来自不同机器的请求的理想情况,那么多线程就不会出现,对吧?问题与本案有关。 @marceln 为什么这个问题有一个 ejb 标签?? 不仅来自不同的浏览器,而且来自同一浏览器的不同同时请求:AJAX请求,刷新n次的用户...Anyway the session variable will be cleared once the request is processed如果开发人员在请求结束时明确执行,否则会话属性保持会话的时间。但是为什么要在会话映射中存储请求范围的属性呢? @NicolasLabrot 好的,关于刷新。但是,一旦处理了 requestScoped bean,会话变量将被清除。我的意思是对于该 bean 的下一个请求,该会话变量也将被刷新。所以我不认为开发商必须清除自己。至于为什么这样做,这就是我的问题:)我自己问一个具体的点:) 【参考方案1】:

HttpSession 接口,如 Java EE 6 中所定义,提供了一种用户操作分组方式、识别用户执行的连续操作以及在多个页面请求中存储有关该用户的信息。

一般概述

由于会话显然是在许多请求之间共享的,因此会产生线程安全问题及其影响。在 Servlet 3.0 规范的第 7 章关于 Sessions 中,人们可能会发现处理此类问题的重要性:

执行请求线程的多个 servlet 可以同时对同一个会话对象进行主动访问。容器必须确保以线程安全的方式执行表示会话属性的内部数据结构的操作。 开发者有责任对属性对象本身进行线程安全访问。这将保护 HttpSession 对象内的属性集合免受并发访问,从而消除应用程序导致该集合损坏的机会。

Servlet 3.0 Specification (JSR-315), ch. 7.7.1,强调我的。

但是,所有这些混乱的根源是什么?在 AJAX 之前的时代,Web 应用程序开发人员并不太关心同步,因为同一用户同时访问会话的可能性很小。但随着构建支持 AJAX 的 Web 应用程序的趋势不断上升,来自同一用户的两个请求很可能同时出现,因此也很可能同时访问会话。

作为旁注,值得注意的是,使用servlet的SingleThreadModel接口可以稍微降低线程问题,但其应用的必要性是有争议的(Servlet 3.0 Specification (JSR-315), ch. 2.2.1)。此外,它已被弃用,因为 Servlet 3.0 和“一次可访问多个 servlet 实例的对象,例如 HttpSession 的实例,可能在任何特定时间对多个 servlet 可用,包括那些实现 SingleThreadModel 的对象”。

同步问题

Java EE 6 教程明确指出“并发访问可能出现......当多个 Web 组件访问存储在会话中的对象时”(The Java EE 6 Tutorial, ch. II-15)。此外,如果我们仔细观察HttpSession 接口,我们会发现一些方法允许将对象绑定到会话,本质上提供了跨多个用户连接存储用户信息的可能性,从而克服了HTTP 协议的无状态性。这些方法是:

getAttribute(String name)(以及现已弃用的getValue(String name)); setAttribute(String name, Object value)(以及现已弃用的 putValue(String name, Object value)removeAttribute(String name)(以及现已弃用的removeValue(String name)); invalidate() 和 接口的其他方法。

最后一个方法使这个会话无效并解除绑定任何对象,持有用户信息,绑定到它,所以它不是我们害怕的。最重要的方法是从/到会话中读取、写入和删除Objects 的方法,因为这些方法将被不同的线程同时调用来处理/访问数据。

正如 Brian Goetz 在Java theory and practice: Are all stateful Web applications broken? 中提到的那样,并发访问的明显问题是:

原子性失败,其中一个线程正在更新多个数据,而另一个线程在它们处于不一致状态时读取数据并且 读取线程和写入线程之间的可见性失败,其中一个线程修改数据,但另一个线程看到过时的数据或处于不一致状态的数据。

问题的简单解决方案

他后来提出了 5 种减少 Web 应用程序中并发问题的技术,最后指出“在 HttpSession 上序列化请求可以消除许多并发风险”。 Marty Hall 在Session tracking 上的在线教程中提出了以下关于同步的建议:“使用会话或会话中的值作为同步块的标签”。所以,基本设置是:

HttpSession session = request.getSession();
synchronized(session) 
    SomeClass value = (SomeClass)session.getAttribute("someID");
    if (value == null) 
        value = new SomeClass(...);
    
    doSomethingWith(value);
    session.setAttribute("someID", value);

使用此设置,访问会话的重叠请求将被同步。

非线程安全使用示例

HttpSession session = request.getSession();
MyClass myClass = (MyClass)session.getAttribute("myClass");
if(myClass != null) 
    myClass.performOperation();
    session.setAttribute("myClass", myClass);

JSF 中对 session 的显式操作的需要

很明显,在会话对象中操作数据可能会导致并发问题。此外,当您选择在将为您隐式管理会话对象的 JSF 框架内进行开发时,它的适用性是值得怀疑的。

最后,当对象固有属于会话时,您应该将它们放入会话中。开发人员有时倾向于将对象放入会话中作为解决问题的一种方式他们的方式,但通常有更好的方法。 Thomas Asel 的文章JSF Best Practices: Scope management for clean sessions 中涵盖了一些错误。

线程问题缓解的一些方法

HttpSession 使用错误时,大多数线程问题都会出现。从这个角度来看,线程问题作用域问题的结果。

例如,如果您将一个值放入一个应该属于较窄范围(即请求范围或视图范围)的会话中,您的代码将变得容易受到有形概率的并发问题的影响时间>。相反,如果您的所有会话数据都属于正确的范围,则用户遇到并发问题的可能性极低

@SessionScoped bean 线程安全必须由开发人员确保,只要 JSF 最终将这些 bean 存储为 HttpSession 的属性,并以托管 bean 的名称作为键。通过名称访问托管 bean 是 JSF 中的一种便利,据我所知,由 session.getAttribute("managedBeanName") 完成。

在这种情况下,如果用户在会话中放置正确的数据并正确管理它(我的意思是没有解决应该在没有会话干扰的情况下解决的问题),那么我看到的唯一缺点是 BalusC 的回答中提到的,即是引用“紧耦合和糟糕的设计”。否则(如果我们省略了 JSF 管理 bean 的生命周期 正确和糟糕的设计问题),用法类似。

想到的一些导致并发问题的范围问题示例:

在会话中存储除与用户相关的任何信息(用户身份验证详细信息、他的偏好、国际化 Web 应用程序中的区域选择; 视图之间的信息共享不应该使用会话来完成:数据的检索应该由GET请求完成,数据的操作应该由POST请求完成;在页面转发期间传递数据可以通过例如使用<f:setPropertyActionListener>来完成,在页面重定向期间可以通过例如使用#flash属性来传递数据; 在重复的组件设置中传递信息应该在不干扰会话的情况下通过<f:attribute><f:setPropertyActionListener><f:param> 和信息检索 - 通过操作(侦听器)方法、<f:viewParam> 等来完成。李>

总而言之,如果您不严格遵循上述列表,您当然有一天会看到错位的输出,但如果您选择正确的数据属于会话范围,则几乎不会看到任何问题。通常,可以在回答整个用户会话问题期间所需的信息时做出这种数据选择。

话虽如此,一个常见的错误是将信息放入会话中以使其在后续视图中可用,检索该信息然后将其从会话中删除,这显然是错误的。我坚信您的对话者采用了这种方法。

最终,JSF 正在发展以促进开发任务,所以为什么不使用它的有用功能!

【讨论】:

我知道如果我们使用我在问题中提到的会话,我们可以通过使用同步块来避免竞争条件。但我最初的问题是“使用 JSF 给出的单个会话范围的 bean 与使用我在这里提到的 requestScoped bean 中的会话有什么区别”?使用 sessionScoped bean 比这里使用的方式有什么好处吗?另外,当我们使用 sessionScoped 时可能会出现竞争条件,对吗?如何避免? @hemanth 虽然我对您的评论的回复有点晚,但您可以在编辑后的答案中看到它。 感谢您的详细解答。 “例如,如果您将一个值放入应该属于较窄范围的会话中,即请求范围或视图范围..”您的意思是什么? 我的想法是,如果您想使用会话在视图之间进行简单的数据传输(例如数据库 id,或类似向导的设置中的先前选择,或选定的项目等),那将导致问题,应该通过使用其他替代方案来解决,开发人员很可能没有意识到,因为他滥用会话,当他首先将(非托管)数据放入会话中,然后从会话中读取数据,最后从会话中删除数据。跨度> ok.. 这意味着在这种情况下我们需要使用其他方法,如 、【参考方案2】:

虽然这在理论上可行,但这只是紧密耦合和糟糕的设计。从@RequestScoped@ViewScoped 的一个很小的变化就会完全杀死整个Web 应用程序。这绝对是不对的。支持 bean 类的设计方式应使其在技术上对其声明的范围不敏感。

您不应该将FacesContext 或其任何工件分配为支持bean 的属性,而是始终在线程本地范围内访问它,没有任何借口。

【讨论】:

“使用 JSF 提供的单个会话范围的 bean 与使用我在这里提到的 requestScoped bean 中的会话有什么区别”? 如果不是由 JSF 管理, 那么当你在 EL 中执行#sessionBeanName 时它还没有被创建时它不会被自动创建。 @BalusC 将范围从请求更改为视图会杀死,因为我们将无法获得在前一个视图中设置的会话属性?如果我错了,它如何杀死应用程序? 引用的FacesContext 实例在下一个请求中不再有效。 @BalusC 你能详细说明一下吗?您的意思是 FacesContext 实例将在下一个请求时刷新吗?在下一次请求时,将正确创建一个新实例,它将再次包含所有信息。它会如何影响?很抱歉,我只想说清楚。 您的代码已将 FacesContext 分配为请求范围 bean 的属性。如果此 bean 的范围更广,并且任何方法在后续请求期间尝试访问该属性,那么它将抛出 IllegalStateException【参考方案3】:

我不知道session 获得后在代码中发生了什么,实际上一切都取决于它。

对于多线程问题,我必须说,如果您只是从会话中读取内容,则没有问题。写入会话时,可能会出现巨大的问题。当针对单个会话(AJAX 请求、多个选项卡...)运行多个请求时,将会出现竞争条件,因此您无法确定会话中实际写入的内容。

没有 JSF 会话和 HTTP 会话,只有一个会话,那就是 HTTP 会话。 JSF 只是为该会话提供了一个外观,因此从那时起,如何获取会话就没有问题了。在某些情况下向会话添加一些属性是可以的,但应该控制并且必须限制。例如,您可以在此支持 bean 中生成一些数据,并且这些数据应该显示在另一个窗口中(例如一些报告)。您可以将报告数据添加到会话中,并在获得这些数据时在另一个页面上将它们从会话中删除。这是有限的,因为这些页面将在一个流中,并且它们之间没有任何内容。但是如果为了其他目的调用该方法也是一个问题。

【讨论】:

所以基本上你总结的是,我们可以像这样得到会话并使用它,而不需要遵循 JSF 通过 SessionScoped bean 实际提供的内容。另外,您是说一般会话的多线程问题还是将会话声明为实例变量? 我只是认为可以使用 session 来存储一些您将在 session 中保留的数据,或者您只想通过视图进行翻译(例如,当您没有对话范围时) )。在任何情况下,您都不应该使用它来创建一些“会话范围的 bean”,因为这些 bean 不会由 JSF 管理,因此它们不是托管 bean。对于会话范围,这是一般问题,请参阅 this link 尤其是“线程注意事项”部分。 例如,假设您有一个要实现的具有多个页面的向导。您可以在会话中存储对该向导重要的变量,并在向导结束时清除该数据。那就是例子。但是这些数据不是托管 bean,因为它们是由您而不是由 JSF 管理的。现在清楚了吗? 是的,现在很清楚了。我已经知道了。所以,在 requestScope bean 中,如果我们有一个会话实例变量,那么在 ajax 请求中就会有竞争条件,这就是你的意思吧? 是的。如果您有两个 AJAX 调用,您将有两个请求范围的 bean 实例相互竞争。

以上是关于在 JSF 2 的 requestScoped bean 中显式创建会话的主要内容,如果未能解决你的问题,请参考以下文章

JSF 2 - f:selectItems 与日期键映射

从 JSF 页面获取请求和会话参数和属性

在 JSF 中如何以及何时销毁 @ViewScoped bean?

没有@ViewScoped的JSF

Validator JSF 中的 EJB bean

刷新会话jsf2,PrimeFaces中的数据