Servlets 框架 HttpSession 提供的会话状态管理机制简化了有状态应用程序的创建,但也很容易导致误用。在没有足够协作的情况下,许多 Web 应用程序对可变数据(比如 JavaBeans 类)使用了 HttpSession 这个机制,从而使自身面临大量潜在的并发性危险。
虽然 Java? 生态系统中存在许多Web框架,但它们都直接或间接地基于Servlets 基础设施。Servlets API 提供大量有用特性,包括通过 HttpSession 和 ServletContext 机制提供的状态管理,它允许应用程序在跨多个用户请求时保持状态。然而,在 Web 应用程序中使用共享状态受一些微妙的(并且大部分没有进行说明)的规则控制着,因此导致许多应用程序无意中触犯了规则。结果是许多有状态 Web 应用程序存在难以发觉的严重缺陷。
范围容器
在Servlet规范中,ServletContext、HttpSession 和HttpRequest 对象被称为 范围容器(Scoped container)。它们都有 getAttribute() 和 setAttribute() 方法,为应用程序存储数据。这些范围容器的区别在于它们的生命周期不同。对于 HttpRequest,数据只在请求期间有效;对于 HttpSession,数据在用户和应用程序的会话期间有效;而对于 ServletContext,数据在应用程序的整个生命周期中都有效。
由于HTTP 协议是没有状态的,所以范围容器在构造有状态 Web 应用程序时十分有用;servlet 容器负责管理应用程序的状态和数据的生命周期。尽管这个规范没有强调,但需要保证嵌套在会话或应用程序中的容器在某种程度上是线程安全的,因为 getAttribute() 和 setAttribute() 方法随时都可能被不同的线程调用(这个规范没有直接要求这些实现必须是线程安全的,但它们提供的服务实际上提出了这一点)。
范围容器还为 Web 应用程序提供一个潜在的好处:容器可以透明地管理应用程序状态的复制和故障转移。
会话
session 是特定用户和Web应用程序之间的一系列请求-响应交换。用户希望Web站点记住他的身份验证凭证、购物车的物品,以及在前面的请求中输入到 Web 表单的信息。但核心 HTTP 协议是没有状态的,这意味着必须将所有请求信息存储到请求本身。因此,如果要创建比一个请求-响应周期更长的有用交互,就必须存储会话状态。servlet 框架允许将每个请求与一个会话关联起来,并提供充当值存储库的 HttpSession 接口,用于存储与该会话相关的(键,值)数据项。清单 1 是一段典型的 servlet 代码,它用于在 HttpSession 中存储购物车数据:
清单 1. 使用 HttpSession 存储购物车数据
HttpSession session = request.getSession(true); ShoppingCart cart = (ShoppingCart)session.getAttribute("shoppingCart"); if (cart == null) { cart = new ShoppingCart(...); session.setAttribute("shoppingCart"); } doSomethingWith(cart); |
清单 1 提供的是 servlet 的典型用途;这个应用程序查看是否已将一个对象放到会话中,如果还没有,它将创建一个该会话的后续请求可以使用的对象。构建在 servlet 之上的 Web 框架(比如 JSP、JSF 和 SpringMVC 等)隐藏了细节情况,但对于标记为嵌入到会话中的数据,它们替您执行了实际操作。不幸的是,清单 1 中的用法很可能是不正确的。
与线程相关的问题
当 HTTP 请求到达 servlet 容器之后,会在 servlet 容器管理的线程上下文中创建 HttpRequest 和 HttpResponse 对象,并传递给 servlet 的 service() 方法。servlet 负责生成响应;在响应完成之前 servlet 一直控制这个线程,响应完成时该线程返回到可用的线程池。Servlet 容器没有保持线程与会话之间的联系;某个会话的下一个请求很可能由另一个不同的线程来处理。事实上,可能有多个请求同时进入同一个会话(当用户与页面交互时,使用框架或 AJAX 技术从服务器获取数据的 Web 应用程序可能发生这种现象)。在这种情况,同一用户可能发出多个请求,这些请求在不同的线程上并行执行。
大多数情况下,这类线程问题与 Web 应用程序开发人员无关。由于自身没有状态,HTTP 鼓励只有存储在请求中的数据(不与其他并发请求共享)和存储在存储库(比如数据库)中的、已进行并发性控制的数据,才具有响应功能。然而,一旦 Web 应用程序将数据存储在 HttpSession 或 ServletContext 等共享容器之后,该 Web 应用程序就具有了并发性,因此必须考虑应用程序内部的线程安全问题。
尽管我们常用线程安全描述代码,但实际上它是描述数据的。具体来说,线程安全是指适当地协调对被多个线程访问的可变数据的访问。Servlet 应用程序通常是线程安全的,因为它们没有共享任何可变数据,因此就不需要额外的同步。但可以通过很多种办法将共享状态引入到 Web 应用程序 — 除了 HttpSession 和 ServletContext 等范围容器外,还可以使用 HttpServlet 对象的静态字段和实例字段。如果要让 Web 应用程序跨请求共享数据,开发人员就必须注意共享数据的位置,并保证访问共享数据时线程之间有足够的协作(同步),以避免与线程相关的危险。
[1] [2] [3] [4] [5] 下一页