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 正在发展以促进开发任务,所以为什么不使用它的有用功能!