7

我对在 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)最后,有什么可能促使他们使用这种方式?

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

4

3 回答 3

7

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

总体概述

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

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

Servlet 3.0 规范 (JSR-315),ch。7.7.1,强调我的。

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

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

同步问题

Java EE 6 教程明确指出“可能出现并发访问......当多个 Web 组件访问存储在会话中的对象时”(Java EE 6 教程,第 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()
  • 接口的其他方法。

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

正如 Brian Goetz 在Java 理论和实践中提到的,并发访问的明显问题是:所有有状态的 Web 应用程序都被破坏了吗?:

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

问题的简单解决方案

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

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 session中涵盖了一些错误。

缓解线程问题的一些方法

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

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

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

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

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

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

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

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

最后,JSF 正在发展以促进开发任务,所以为什么不使用它的有用特性呢!

于 2013-03-12T18:27:05.640 回答
4

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

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

于 2013-03-13T20:21:43.227 回答
3

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

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

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

于 2013-03-11T21:30:13.113 回答