创建有状态应用程序的一个基本组成部分就是数据管理。通过从一个会话全面地收集有关一个用户的数据,我们可以为那个用户创建一个简要表(profile),然后使用这个简要表来个性化他的用户体验。为了智能地管理用户数据,必须将其置于上下文中。所以我们使用作用域。
在 J2EE 中有 4 种作用域(或者说上下文)用于管理数据:application(应用程序)、session(会话)、request(请求)和page(页面)。在本文中您将会了解每种作用域为J2EE 中的有状态应用程序开发带来了哪些东西,以及每种作用域最适合于哪种会话管理场景。
构建有状态 Web 应用程序如果用户只需要用 Web 应用程序来查看静态内容,那么除了 HTML 外我们不需要任何其他的技术。但是,除了基于文本的信息和好看的背景颜色之外,大多数用户还想从 Web 那里得到更多的功能。实际上,目前多数的用户访问
网络都是为了执行复杂的电子商务交易,参与在线社团,下载(和上传)媒体,等等。对于大多数流行站点来说,交互性是关键,而且,个性化的功能越多越好。对于这种类型的开发,我们使用一些高级的 Web 技术,例如 CGI、PHP、ASP、JSP 以及 Java Servlet。
交互性的一个重要的组成部分就是个性化。 个性化的 Web 站点能够针对用户的特定需求和兴趣来定制内容和输出。个性化的一个重要组成部分就是 duration。用户体验不仅应该具有内聚性,还应该加以扩展,超越单事务模式。例如,当登录到像 Yahoo 这样的一个个性化的门户时,您将建立一个 身份(identity),在 Yahoo 域中,无论导航到哪里,您的身份都不会改变。对于许多在线银行、信用卡以及股票交易系统来说,也是如此。在线商店也逐渐地采用了这种模型。
当我们谈到扩展了单事务模式的用户体验时,我们称之为会话。当我们创建支持会话而不是单独的、孤立的事务的 Web 应用程序时,我们就是在创建有状态的应用程序。
在一个有状态应用程序中,用户可以导航到站点的许多不同的地方,并执行多个事务,但是他将一直维护着他自己惟一的身份。这是可以实现的,因为系统会不断地跟踪他的会话的 状态。在 J2EE 中,这种类型的开发是通过将每个用户的信息
存储在一个后端数据
服务器上来进行管理的。当一个用户第一次登录到站点时,他便建立一个惟一的 ID。从那一刻开始,与该用户的 ID 相关的数据便被存储起来,并且在需要的时候系统可以访问这些数据。
至于 Java 平台,我们可以使用三种技术之一来将用户 ID 与单个用户关联起来,这三种技术是:URL 重写、隐藏表单字段以及 HTTP cookies。在 J2EE 中,我们使用 HTTPSession API 来存储、检索会话数据,或者将会话数据与某一特定用户 ID 相关联。
除了存储数据,我们还必须能够 contextualize数据(将数据置于上下文中)。术语 作用域 指的是被存储数据的上下文(即该数据的 “作用域”);对作用域的适当处理在有状态 Web 应用程序的设计中处于核心地位。
会话作用域术语“作用域”指的是一个上下文,在这个上���文中数据被关联或者存储。在传统的独立应用程序中存在着一些可以在其中关联变量和对象引用的上下文(或作用域)。典型的作用域包括:
局部/方法
类/对象/组件
包/库
protected(受保护的)
全局/public(公共的)
在 Web 应用程序中所说的作用域不同于更传统的、独立的应用程序中所使用的作用域。在 Web 应用程序中,作用域指的是一个对象可以多大程度地为一个应用程序的组件所使用。而在独立应用程序中,虽然作用域所指的也是可用性,但这里的作用域是由代码块来划分界限的。
在 J2EE Web 应用程序中,一共有 4 种会话作用域:
page
request
session
application
每种 J2EE 作用域都有一个上下文,在这个上下文中可以关联(存储)基本类型的数据和对象引用,以供享有同一上下文的其他组件使用。表 1 列出了这 4 种作用域,并说明了它们是否能应用于 servlet 和 JSP 页面,还给出了对每种作用域的描述。
表 1. 4种会话作用域
会话作用域ServletsJSP 页面描述page否是代表与一个页面相关的对象和属性。一个页面由一个编译好的 Java servlet 类(可以带有任何的 include 指令,但是没有 include 动作)表示。这既包括 servlet 又包括被编译成 servlet 的 JSP 页面request是是代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件(由于 forward 指令和 include 动作的关系)session是是代表与用于某个 Web 客户机的一个用户体验相关的对象和属性。一个 Web 会话可以也经常会跨越多个客户机请求application是是代表与整个 Web 应用程序相关的对象和属性。这实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域
接下来,我们将看看用于将数据存储在这 4 种作用域中的一些机制。
将数据存储在作用域中
在这 4 种作用域中,每一种作用域都有一个不同的机制,用于存储并最终访问上下文相关的数据。每种作用域都有一个单独的类,通过这个类可以存储和检索上下文相关的数据。表 2 标出了 4 个类,分别对应于 4 种会话作用域。
表 2. 用于存储有作用域的数据的类
类名作用域注释javax.servlet.jsp.PageContextpage特定于 JSP 的一个类型,代表当前的 JSP 页面 。PageContext 是一个抽象类,设计用来供 JSP 引擎供应商加以扩展以提供特定于实现的类型。 PageContext 实例提供了对所有与一个 JSP 页面相关的名称空间的访问途径javax.servlet.http.HttpServletRequestrequset这种类型代表当前的 HTTP 请求。 HttpServletRequest 类型是一个接口,设计用来供 servlet 引擎供应商加以实现。由客户机提供的特定于请求的数据(request方法,UTI,HTTP 参数,等等),以及由另一个 servlet 或者 JSP 页面提供的数据,都可以存储在一个 request 作用域中javax.servlet.http.HttpSessionsession这种类型代表当前的 HTTP 会话。 HttpSession 类型是一个接口,设计用来供 servlet 引擎供应商加以实现javax.servlet.ServletContextapplication这种类型代表整个的运行时 Web 模块。 ServletContext 类型是一个模块,设计用来供 servlet 引擎供应商加以实现
乍一看来,使用这 4 种数据类型来存储有作用域的数据是相当直观的。然而,麻烦的是,对于一个给定的场景,哪一种作用域最合适却并不总是那么清楚的事了。我们将看看可能碰到的一些常见的场景,以便作出总结。另外,我们还将实际地讨论,对于每一种环境,哪一种作用域最适合。
作用域解决方案
在由 JSP 和 Java Servlet 规范定义的这 4 种作用域中,每一种作用域在 Web 应用程序中都有其明确的用途。
page 提供代表一个 JSP 页面的上下文,与用户所看到的真实页面之间常常具有一对一的映射关系。这种作用域只能用于 JSP 页面,并且也是所有对象,包括 JavaBean 组件的默认作用域。具有 page 作用域的对象通常是那些在 scriptlet、表达式、JavaBean 标记以及自定义标记中被访问的局部变量。如果必须获得一个有 page 作用域的对象的一个引用,则可以在该页面的 javax.servlet.jsp.PageContext 变量上调用 getAttribute() 。
request 最适合的环境是:单个的用户请求可能涉及不止一个的 servlet 或 JSP 页面。request 是一种能够在一个原子请求内跨越多个页面的上下文。有 request 作用域的数据存储在 javax.servlet.ServletRequest 对象中(使用 javax.servlet.http.HttpServletRequest object )并通过使用 getAttribute() 和 setAttribute() 方法来访问。
session 是有状态 J2EE Web 应用程序作用域的核心和灵魂。正是这种作用域使得跨越多个请求的持久用户体验的创建成为可能。 javax.servlet.http.HttpSession 是存储有 session 作用域的数据的地方,可以通过调用 getAttribute() 和 setAttribute() 方法来访问。为了在一个 JSP 页面内使用有 session 作用域的数据,必须首先声明这个页面要参与会话。为了做到这一点,只需在页面的任何地方(一般是在顶部)插入 JSP 会话属性 。
application 是具有最长运行时间的作用域。这是 J2EE 为全局数据提供的。应用程序数据被一个应用程序模块内的所有 Web 组件所共享。具有应用程序作用域的对象属于 javax.servlet.ServletContext ,可以通过调用 getAttribute() 和 setAttribute() 方法来访问。
记住了这些定义,我们就可以制定使用不同作用域的一些原则:
对于 JSP 数据坚决使用 page 作用域。这是与 JSP 页面打交道的最简单的方式。JSP 页面内所有数据的默认作用域都是 page,它允许您在为局部变量指定的范围内(类/方法/局部变量作用域)使用这种数据。
清楚 JSP include 对作用域的影响。 page 作用域适用于单个的、经过编译的 Java servlet 类。因为 include 指令是在编译的时候处理的,包括在指令中的任何内容都是在 page 作用域的上行文中操作的。另一方面, include 动作是在运行的时候处理的。如果使用了 include 动作,为了在两个组件之间共享数据,应该使用 request 作用域。
为了在运行的时候在 Web 组件之间共享数据,使用 request 作用域。如果使用一个 forward 动作或者 include 动作来在两个或更多组件之间共享一个请求,那么通过将数据的作用域设为 request 作用域便可以在这些组件之间共享该数据。
为了提供有状态用户体验,使用 session 作用域。无论要构建的是在线商店,电子邮件管理站点,个人化信息门户,还是财务管理应用,session 作用域都是对用户采取从请求到请求的跟踪或者为用户提供无缝的、持久的环境的最佳选择。
将 application 作用域专用于全局数据。应用程序对象是一些静态的对象,为应用程序内的所有对象所共享。对 application 作用域的使用应该保留给真正需要在组件之间共享或者跨用户会话的数据。典型的例子有:缓存的 DAO,JNDI 引用的缓存,或者任何类型的公共工厂或者其他需要使用 Singleton 模式的组件。
结束语
对会话作用域(即在一个会话中存储和维护的数据的上下文)的适当处理是有状态应用程序开发的关键。在本期的 J2EE 探索者中,我指出了 4 种 J2EE 会话作用域,以及用于代表每种上下文的多个数据类型,并讨论了一些实际的注意事项,这些注意事项将对您使用作用域有所帮助。