隐式对象的方法
隐式对象的方法
|
级别:中级 |
Kyle Gabhart (kyle@gabhart.com)
顾问,Gabhart Consulting
2003年 9 月
接着上月对会话作用域的介绍,企业 Java 专家 Kyle Gabhart 深入研究了 JSP 隐式对象的多种用法。接下来,他将介绍 9 个隐式对象,解释每个对象的用途(或者多种用途),最后给出一些怎样在 JSP 编程中使用这些便利工具的最佳实践。您可以到我们的 讨论论坛 中分享您对这篇文章或者 J2EE 探索者 系列中的任何其他文章的想法。
本期的 J2EE探索者 是上个月的 正确处理会话作用域入门 的续篇。除了访问会话作用域之外,JSP 隐式对象还可以用来处理 HTML 参数,转发请求到一个 Web 组件,包括组件的内容、通过 JSP 容器的日志数据、控制输出流,处理异常,等等。
本月,您将学到在 JSP 页面中使用隐式对象。我们首先简要概括 JSP 架构,其中包括了隐式对象。然后,我将介绍每个对象并描述它的核心功能。最后,我们将给出使用每种类型的对象和它提供的容器管理服务的一些最佳实践。
隐式对象简介
JSP 架构背后的理念是提供一个 Web 组件,它允许开发人员着重关注 Web 内容的表示,而不用陷入解析、编程和数据操纵等细节。JSP 应用程序本质上是特殊的 Web 组件,在处理用户请求之前,J2EE Web 容器首先将其转换成 servlet。在每个 JSP 应用程序内部有一套完整的隐式对象。
隐式对象使得开发人员可以访问容器提供的服务和资源。这些对象之所以定义为隐式的,是因为您不必显式地声明它们。不论您是否声明它们——虽然您不能重复声明它们,它们在每个 JSP 页面当中都进行定义,并且在后台由容器使用。因为隐式对象是自动声明的,所以我们只需要使用与一个给定对象相关的引用变量来调用其方法。
9 个隐式对象及其功能的简单描述如下:
-
Application
-
Config
-
Exception
include 含有只能由指定的 JSP“error pages”访问的异常数据。 -
Out
提供对 servlet 的输出流的访问。 -
Page
-
PageContext
-
Request
-
Response
-
Session
虽然有些隐式对象只提供单一的功能,但是几个结合起来使用就可以提供多种功能。在接下来的一节里,我们将按照功能分类来考察隐式对象:
-
会话管理:
application
,session
,request
,pageContext
-
流控制:
application
,config
,pageContext
,request
,session
-
日志记录和异常:
application
,config
,exception
,pageContext
,request
,session
-
输入/输出控制:
request
,response
,out
-
初始化参数:
config
会话管理
上个月我们提到过,为 JSP 定义的四个隐式对象可以用来在一个特定的上下文或者作用域中加入有状态数据。这四个对象是 application
、session
、request
和 pageContext
。下表列出了这四个对象和它们定义的状态上下文,同时还给出了对每个对象的简单描述。
表1. JSP 状态管理
隐式对象 | 作用域 | 描述 |
javax.servlet.ServletContext |
Application |
代表整个运行时的 Web 模块(应用程序)。作用域为 application 的数据在同一个应用程序模块的所有 Web 组件之间共享。这很像J2EE 中提供的“全局(global)”数据 |
javax.servlet.http.HttpSession |
Session |
代表当前的 HTTP 会话。除 page 作用域外,session 作用域是使用最普遍的上下文。这个对象在提供跨多个请求的持久的、有状态的用户体验方面使用得最普遍 |
javax.servlet.http.HttpServletRequest |
Request |
代表当前的 HTTP 请求。这个上下文可以跨越多个 Web 组件(servlet 和 JSP 页面),只要这些组件属于同一原子请求的一部分。由客户机提供的特定于请求的数据(请求方法、URI、HTTP 参数等等)都被自动地保存在一个request 上下文中。servlet 或 JSP 页面还可以程式化地(programmatically)将数据的作用域指定为 request ,以便允许同一 request 作用域中的其他 servlet 或 JSP 页面可以获取该数据 |
javax.servlet.jsp.PageContext |
Page |
代表当前 JSP 页面的上下文。因为一个 JSP 页面的上下文包括当前的请求、会话和应用程序,所以使用pageContext 实例可以访问与一个JSP 页面相关的所有命名空间。它是所有对象的默认作用域,包括 JavaBeas 对象在内。 具有 page 作用域的对象通常会绑定到一个局部变量,以便在 scriptlet、表达式、JavaBeans 标记和自定义标记中可以访问它 |
从最佳实践的立场来看,我们应该尽可能地使用 page
作用域。它简单,而且是 JSP 数据的默认作用域。request
作用域非常适合于运行期间在组件间共享数据以处理一个特定的请求。session
作用域被设计用来为单个用户提供持久的、有状态的体验,它可以跨越多个请求。application
作用域只有需要在组件之间跨用户会话共享数据时才应该使用。参阅参考资料以了解更多有关 session 作用域的信息。
流控制
面向对象设计方法的最大好处是可重用性。特别是,J2EE 系统将它们借用到模块化风格的开发中,其中组件可以在其他应用程序中重新安排、重新打包和重新使用。即使您对设计可重用的 Web 模块不感兴趣,也很可能会发现您的 J2EE 应用程序由几个部分组成。任何时候使用多个 servlet 或者 JSP 页面(也就是组件)完成一个请求的时候,都需要使用某种类型的流控制技术。Servlet 架构提供两种这样的技术:forward(转发) 和 include(包括)。
在 J2EE Web 开发中,forward 会把处理用户请求的控制权转交给到其他 Web 组件。forward 在有些时候会比较有用,比如说需要用一个组件设置一些 JavaBean、打开或关闭资源、认证用户,或者在将控制权传递给下一个组件之前需要执行一些准备工作。在转发之前可以执行很多类型的任务,但是要转发的组件不能设置响应头部信息,也不能有内容发送到输出缓冲区。所有与向客户发送内容直接相关的任务必须由被转发的组件完成。
J2EE 中第二种流控制技术是include。在使用 forward 时,要传递控制权。与此不同的是,执行 include 的组件维持对请求的控制权,而只是简单地请求将另一个组件的输出包括在该页面的某个特定的地方。对于常见的设计元素,例如页首、页脚和导航栏等,这是一个非常好的方法。
forward 和 include 都是通过一个专门的对象 java.servlet.RequestDispatcher
来完成的。简单地调用一个 ServletContext
对象的 getRequestDispatcher()
方法就可以获得一个RequestDispatcher
对象。得到对 ServletContext
对象的引用有几种方法,我们可以:
-
使用隐式声明的
application
ServletContext。
-
调用方法
getServletContext()
,该方法返回一个对隐式声明的 application 变量的引用。 -
调用隐式声明的
config
etServletContext()
方法。
-
调用隐式声明的
pageContext
getServletContext()
方法。
-
调用隐式声明的
request
变量的getServletContext()
方法。
-
调用隐式声明的
session
getServletContext()
方法。
清单1给出了使用隐式变量 application
的 forward 流控制机制的代码示例。
|
清单2给出了同样使用变量 application
的 include 流控制的代码示例。
|
forward 和 include 是添加到 J2EE Web 开发工具包中的两个非常棒的技术。还有其他一些方法可以在 JSP 页面中完成 include,而且还有很多解决 J2EE 设计模式方面的文献中讲到了如何结合使用这两种技术。参阅参考资料以了解更多信息。
日志记录和异常
如果您需要把与 Web 应用程序相关的信息存储到一个日志中,依然有内建的方法可用。ServletContext
接口声明了两个方法,用于把数据传给一个日志。其中一个方法接受简单的文本消息:log( java.lang.String )
,另一个方法接受一个异常信息和一个文本消息:log(java.lang.Throwable, java.lang.String )
。
在有了 ServletContext
接口提供的两个可用的日志记录方法之后,剩下的关键是获取一个对ServletContext
类型的对象的引用。像我们前面讨论过的流控制对象一样,有多种方法可以获取对 ServletContext
类型的对象的引用。在获得了对象引用之后,简单地调用 log()
方法并向方法中传递必需的数据即可。一旦调用了这个方法,您当然就会希望能够查看应用程序日志以查看消息。ServletContext
是一个简单的接口,并且也没有规定怎样实现它声明的方法。因而 log 方法的具体实现是由供应商处理的。他们可以把日志信息存储到一个文本文件、二进制文件、数据库中,或者是供应商认为合适的其他格式中。您需要从服务器的文档中得知存储日志的位置。
虽然向一个日志文件发送消息相当有用,但是很多时候您可能还想在发生不可恢复的异常时显示一个用户友好的错误消息。要实现这一功能,您可以声明,您的 JSP 页面使用一个单独的页面来处理错误消息。这是在 JSP 页面的任何地方通过包含下面的 page 指令实现的:
|
如果在处理 JSP 页面时有一个异常抛出的话,exception 对象就会立即通过隐式声明的 exception
变量的方式抛给指定的错误页面。
为了使一个 JSP 页面能够作为一个错误页面,它必须包含一个指令来声明这个页面是指定用来处理错误的特殊页面,指令如下:
|
为了使用 ErrorMessage.jsp 页面能够作为一个错误页面,这个指令必须出现在页面的某个地方。错误页面可以显示一个友好的信息给用户,然后可以将相关的异常信息写入日志以供管理员日后查看。
输入和输出控制
因为 JSP 页面仅仅是 HTTP servlet 的一个简单抽象,所以您可以访问 HttpServletRequest
和 HttpServletResponse
对象。如果需要特定于请求的信息,比如客户机浏览器的类型、HTTP post 的内容类型、客户机性能、Cookie 数据或者请求参数,简单地用隐式声明的 request
变量直接调用适当的方法即可。类似地,如果您需要设置响应头部信息,比如说浏览器类型、内容类型、内容长度等等,简单地用隐式变量 response
调用适当的方法即可。
如果需要直接访问 JSP 页面的输出流,您可能会试图通过隐式 response
变量调用 getWriter()
或 getOutputStream()
。然而由于 JSP 页面的特殊性,您不能这样做。如果需要直接访问输出流,必须通过一个 avax.servlet.jsp.JSPWriter
类型的特殊缓冲 PrintWriter
对象来访问。怎样定位这样一个对象的引用呢?JSP 容器将会为您隐式地声明一个,并通过 out
变量提供给您。在 JSP scriptlet 中可以通过简单地调用 out.print()
或 out.println()
使用它。
一般来说不需要像这样直接使用 JSPWriter 对象,而只需简单地把内容作为普通文本或者通过 JSP 表达式写入,然后允许容器将这些信息翻译成 JSPWriter 调用。然而,在两种情况下您需要直接使用 out
变量。一种情况是要为 JSP 自定义标记定义处理程序,这部分内容我们将在下个月重点讲到。另外一种情况是您想要对 JSP 创建的输出拥有更多的控制。如果您有一段夹杂着 JSP scriptlets 和表达式的 HTML,您可能会发现创建大的 scriptlet 然后在需要输出内容到客户机的时候使用 out.println()
语句这样做会更简洁、更容易。
初始化参数
如果您有一些静态数据想提供给 JSP 页面使用,并且那些数据不会频繁地改动,初始化参数可能会是一个比较好的选择。初始化参数有时候又叫环境变量或者“init”参数,这些参数通过位于一个 per-servlet/JSP 内的 Web 应用程序的 web.xml 文件指定,并且它们在servlet 的生命周期中只读取一次,即在初始化时读龋
清单3是一个初始化参数声明的例子。
清单3. 初始化参数声明
|
使用隐式变量 config
可以访问这些参数的值,隐式变量 config
是对 JSP 页面的ServletConfig
对象的引用。通过 ServletConfig
接口提供了两个处理 init 参数的方法。可以根据名字对一个特定的参数完成一个查找(getInitParameter( java.lang.String)
), 或者也可以检索到为 JSP 页面定义的所有参数名字的一个 enumeration(getInitParameterNames()
)。在拥有了enumeration 之后,可以通过循环查找每一个值。所有 init参数都是String
对象。如果需要其他的数据类型,比如说整数、浮点数或者布尔值,必须使用相应的包装器类来解析字符串。
结束语
JSP 技术提供了 Servlet 架构之上的一个非常有用的抽象,它让 Web 设计者可以着重关注内容表示,而只要求知道较少的编程细节。在本文中,您已经看到了我们是如何使用隐式对象来快速、容易地开发 Web 应用程序的。
下个月,我们将开始讲述 JSP 自定义标记和 JSP 标准标记库(JSTL)。学习自定义标记如何促进表示和业务逻辑之间的分离,同时还让您可以将动态数据合并到表示层。到那时,一起快乐地探索吧!