JavaServer Pages 技术

Stephanie Bodoff

JavaServer Pages (JSP)技术使你能够很容易地创建包含静态和动态组件的Web内容。JSP技术可以实现所有Java Servlet技术的动态功能,但是它提供了一个更加简单的方法来创建静态内容。JSP技术的主要特点如下:

·  它是开发JSP页面的一种语言,这种页面是基于文本的文档,描述了如何处理一个请求和如何构造一个响应。

·  可以建立对服务器端对象的访问。

·  提供了用于定义对JSP语言的扩展的机制。

JSP技术还包括一个由Web容器开发者使用的API,但是本章没有介绍这个API

什么是JSP页面?

JSP页面是一种基于文本的文档,它包含两种类型的文本:一种是静态模板数据,它们可以用任何基于文本的格式来表达,如HTML SVG WML XML;一种是JSP元素,它们用于创建动态内容。JSP元素的语法卡片和参考可从http://java.sun.com/products/jsp/technical.html#syntax获得。

15­-1Web页面是一个表单,它允许你选择一个地区,然后以一种符合该地区习惯的方式显示日期。

Localized Date Form

15-1 显示地区日期表单

这个例子的源代码在你解压tutorial包时创建的docs/tutorial/examples/web/date目录下。JSP页面index.jsp用于创建上面的表单;它是一个典型的混合了静态HTML标记和JSP元素的页面文件。如果你开发过Web页面,你可能很熟悉HTML文档结构语句(<head><body>等等)和创建表单和菜单的HTML语句<form><select>。源代码例子中粗体的行包含以下类型的JSP结构:

·  指令(<%@page...%>)导入java.util包中的类和MyLocales类,并设置页面返回的内容类型。

·  jsp:useBean元素创建一个包含地区集合的对象,并初始化一个指向这个对象的变量。

·  Scriptlet<% ... %>)获取locale请求参数的值,枚举地区名称的集合,并根据条件将HTML文本插入到输出中。

·  表达式(<%= ... %>)将地区名的值插入响应中。

·  jsp:include元素向另一个页面(date.jsp)发出请求,并将其响应加入到调用页面的的响应中。

<%@ page import="java.util.*,MyLocales" %>
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<html>
<head><title>Localized Dates</title></head>
<body bgcolor="white">
<jsp:useBean id="locales" scope="application"

  class="MyLocales"/>
<form name="localeForm" action="index.jsp" method="post">
<b>Locale:</b>
<select name=locale>
<%
 String selectedLocale = request.getParameter("locale");
 Iterator i = locales.getLocaleNames().iterator();
 while (i.hasNext()) {
   String locale = (String)i.next();
   if (selectedLocale != null &&
     selectedLocale.equals(locale)) {
%>
     <option selected><%=locale%></option>
<%  
   } else {
%>
     <option><%=locale%></option>
<%
   }
}
%>
</select>
<input type="submit" name="Submit" value="Get Date">
</form>
<jsp:include page="date.jsp"/>
</body>
</html>

要编译、部署和执行这个JSP页面:

1.     在终端窗口,进入docs/tutorial/examples/web/date目录。

2.     运行ant buildBuild目标将进行所有必需的编译,并文件拷贝到docs/tutorial/examples/web/date/build 目录。

3.     运行ant installInstall目标通知Tomcat有了新的内容。

4.     打开dateURL http://localhost:8080/date

你将会看见一个组合框,其条目是各个地区。选择一个地区并单击GetDate。你将看见以符合该地区习惯的方式表示的日期。

JSP页面示例

为了展示JSP技术,本章将第14章中介绍的Duke's Bookstore应用程序中每一个servlet重新改写为JSP页面:

15-1 Duke's Bookstore例子的JSP 页面 

功能

JSP页面

进入书店

bookstore.jsp

创建书店横幅

banner.jsp

浏览销售的图书

catalog.jsp

将书放入购物车

catalog.jsp and bookdetails.jsp

得到关于某本书的详细信息

bookdetails.jsp

显示购物车

showcart.jsp

从购物车中删除一本或者多本图书

showcart.jsp

购买购物车中的图书

cashier.jsp

收取购买的确认

receipt.jsp

书店应用程序的数据仍然保留在数据库中。不过,对数据库helper对象database.BookDB做了两处修改:

·  重新编写数据库helper对象以符合在JavaBean组件设计规范中描述的JavaBean组件设计样式。这种改变使得JSP页面可以用JavaBean组件特定的JSP语言元素访问helper对象。

·  helper对象通过数据库访问对象database.BookDAO而不是直接访问书店数据库。

数据库helper对象的实现如下。这个bean有两个实例变量:当前图书和对数据库企业bean的引用。

public class BookDB {
 private String bookId = "0";
 private BookDBEJB database = null;

  public BookDB () throws Exception {
 }
 public void setBookId(String bookId) {
   this.bookId = bookId;
 }
 public void setDatabase(BookDBEJB database) {
   this.database = database;
 }
 public BookDetails getBookDetails()
   throws Exception {
   try {
     return (BookDetails)database.
         getBookDetails(bookId);
   } catch (BookNotFoundException ex) {
     throw ex;
   }
 }
 ..
.
}

最后,这个版本的例子包含一个applet,以便在横幅中生成动态数字时钟。有关生成用于下载appletHTMLJSP元素的描述见包含Applet

这个应用程序的源代码在解压缩教程包(运行示例)时创建的docs/tutorial/examples/web/bookstore2目录中。要编译、部署和运行这个示例:

1.     在终端窗口,进入docs/tutorial/examples/web/bookstore2

2.     运行 ant buildbuild 目标将进行所有必要的编译并将文件拷贝到docs/tutorial/examples/web/bookstore2/build 目录中。

3.     确保Tomcat 已经启动。

4.     运行ant installinstall 目标通知Tomcat有新内容。

5.     如果还没有做的话,则启动PointBase数据库服务器并填充数据库(从Web应用程序访问数据库)

6.     打开书店URL http://localhost:8080/bookstore2/enter.

有关诊断常见问题的帮助见常见问题及其解决方法故障排除

JSP页面的生命周期

JSP页面服务需要像servlet。因此,JSP页面的生命周期和许多能力(特别是动态方面)都由Java Servlet技术所决定,本章讨论了很多第14章中讨论的功能。

如果请求映射为JSP页面,那么它就由一个特殊的servlet处理,这个servlet首先检查JSP页面的servlet是否比比JSP页面更老。如果是,那么它就将JSP页面转换为servlet类并编译这个类。在开发时,JSP页面优于servlet的一点是编译过程是自动进行的。

转换和编译

在转换阶段,JSP页面中各种类型的数据是区分处理的。模板数据被转换成在向客户端返回数据的流中加入数据的代码。JSP元素的处理如下:

·  用指令控制Web容器如何解释及执行JSP页面。

·  将脚本元素插队入JSP页面的servlet类中。详见JSP脚本元素

·  表单<jsp:XXX ... />的元素转换为对JavaBean组件的调用或者对Java Servlet API的调用。

对于名为pageNameJSP页面,JSP页面的servlte的源代码保存在下列文件中:

<JWSDP_HOME>/work/Standard Engine/
 localhost/context_root/pageName$jsp.java

例如,在本章开始时讨论的date本地化例子中的索引页面(名为index.jsp)的源代码会命名为:

<JWSDP_HOME>/work/Standard Engine/
 localhost/date/index$jsp.java

转换和编译阶段都有可能产生只有第一次请求页面时才会见到的错误。如果在对页面进行转换时发生错误(例如,如果转换器遇到了有问题的JSP元素),那么服务器将返回一个ParseException异常,并且servlet类资源文件会是空的或者不完整的。根据最后一个不完整的行将指出不正确的JSP元素。

如果在编译JSP页面时发生错误(如在脚本中有语法错误),那么服务器将返回一个JasperException和包括出现错误的JSP页面的servlet名和出错的行的消息。

一旦页面转换并编译完,JSP页面的servlet大多会拥有在Servlet生命周期中描述的servlet生命周期

1.     如果JSP页面的servlet的实例不存在,那么容器就会

a.   装载JSP页面的servlet

b.   实例化servlet类的一个实例

c.   调用jspInit方法初始化servlet实例

2.     容器调用_jspService方法并传递请求和响应对象。

如果容器需要删除JSP页面的servlet,那么它就调用jspDestroy方法。

执行

可以通过使用page指令控制不同的JSP页面执行参数。在这里讨论用于缓冲输出和处理错误的指令。其他指令在本章中谈到相关页面编写任务时介绍。

缓冲

执行JSP页面时,写入响应对象的输出自动缓冲。可以用下面的页面指令设置缓冲区的大小:

<%@ page buffer="none|xxxkb" %> 

更大的缓冲区可以在向客户端实际发回内容之前写入更多的内容,从而为JSP页面提供了更多时间来设置属性状态代码和头部,或者转到另一个Web资源。小的缓冲区减少服务器的内存负载并让客户端可以更快地开始接收数据。

处理错误

在执行JSP页面时可能出现任何数量的异常。要指定Web容器在出现异常时应该将控制转移给错误页面,需将下面page指令加入到JSP页面的开始位置:

<%@ page errorPage="file_name" %> 

Duke's Bookstore应用程序页面initdestroy.jsp包含指令

<%@ page errorPage="errorpage.jsp"%> 

errorpage.jsp的开始用下列page指令指明它是一个错误页面:

<%@ page isErrorPage="true|false" %> 

这个指令使得错误页面可以使用异常对象(类型为javax.servlet.jsp.JspException),这样就可以在错误页面上获取、解释并有可能显示有关产生异常的原因。

初始化和结束JSP页面

通过覆盖JspPage接口的jspInit方法,可以定制初始化过程以,使得JSP页面可以读取持久化的配置数据、初始化资源以及执行任何其他一次性的行动。用jspDestroy方法释放资源。这个方法是用JSP声明定义的,在声明中对其做过讨论。

书店示例页面initdestroy.jsp定义jspInit方法获取访问书店数据库并将对bean的引用储存在bookDBAO中的对象database.BookDBAO

private BookDBAO bookDBAO;
public void jspInit() {  
bookDBAO =
 (BookDBAO)getServletContext().getAttribute("bookDB");
 if (bookDBAO == null)
   System.out.println("Couldn't get database.");
}

从服务中删除JSP页面时,jspDestroy方法释放BookDBAO变量。

public void jspDestroy() {
 bookDBAO = null;
} 

由于企业bean在所有JSP页面中共享,所以它应该在应用程序启动时、而不是在启动每一个JSP页面时初始化。Java Servlet技术为此提供了应用程序生命周期事件和监听器类。作为练习,可以在内容监听器类中加入管理企业bean的创建的代码。有关初始化Java Servlet版本的书店应用程序的内容监听器的内容见处理Servlet生命周期事件

创建静态内容

只要像创建只包含静态内容的页面那样编写其中内容,就可在JSP中创建静态内容。静态内容可以以任何基于文本的格式表示,如HTMLWMLXML。默认格式是HTML。如果希望使用非HTML的格式,那么就在JSP页面的开始处加入一个page指令,其contentType属性设置为该种格式。例如,如果要一页面含以无线标识语言(wireless markup language WML)表达的数据,需要加入以下的指令:

<%@ page contentType="text/vnd.wap.wml"%> 

IANA在以下地址维护了一个内容类型名注册表:

ftp://ftp.isi.edu/in-notes/iana/assignments/media-types 

创建动态内容

可以通过在脚本元素中访问Java编程语言对象来创建动态内容。

JSP页面中使用对象

可以在一个JSP页面中访问不同的对象,包括企业beanJavaBean组件。JSP技术使一些对象自动可用,还可以创建并访问应用程序特定的对象。

隐式对象

隐式对象由Web容器创建,并包含与特定请求、页面或者应用程序相关的信息。许多对象是由JSP技术之下的Java Servlet技术定义的,在第14章有详细讨论。15-2总结了隐式对象。

15-2 隐式对象

变量

说明

application

javax.servlet.
ServletContext

JSP页面的servlet和所有其他包含在同一应用程序中的Web组件的上下文。见访问Web上下文

config

javax.servlet.
ServletConfig

JSP页面的servlet的初始化信息。

exception

java.lang.
Throwable

只能从错误页面访问。见处理错误

out

javax.servlet.
jsp.JspWriter

输出流。

page

java.lang.
Object

JSP页面的servlet实例处理当前请求。JSP页面编写者一般不使用它。

pageContext

javax.servlet.
jsp.PageContext

JSP页面上下文。提供了一个API用于管理在使用scope对象中描述的不同作用域的属性。

这个API在实现标签处理器中大量使用(标签处理器)

request

subtype of
javax.servlet.
ServletRequest

该请求触发JSP页面的执行。见从请求中得到信息

response

subtype of
javax.servlet.
ServletResponse

返回给客户端的响应。JSP页面编写者一般不使用。

session

javax.servlet.
http.HttpSession

客户端的会话对象。见维护客户端状态

应用程序特定的对象

在可能的情况下,应用程序的行为应该封装在对象中,这样页面设计者就可以把注意力放到内容展示上。对象可以由精通Java编程语言、数据库和其他服务访问的开发者创建。在JSP页面中有四种方式创建和使用对象:

·  声明中创建JSP页面的servlet类的实例和类变量,并在scriptlets表达式中访问它们。

·  scriptlets表达式中创建和使用JSP页面的servlet类的本地变量

·  scriptlets表达式中创建和使用scope对象的属性(使用scope对象)

·  可以用流水化JSP元素创建和访问JavaBean组件。在JSP页面中的JavaBean中讨论了这些组件。还可以在声明或者scriptlet中创建JavaBean组件,并在scriptlet或者表达式中调用JavaBean组件的方法。

JSP脚本元素中描述了声明、scriptlet和表达式。

共享对象

控制对共享资源的并发访问中描述的影响对共享对象并发访问的条件也适用于作为多线程servlet运行的JSP页面所访问的对象。可以用下面的page指令指明Web容器应该如何分配多客户端请求:

<%@ page isThreadSafe="true|false" %> 

如果isThreadSafe设置为trueWeb窗口可能选择将多个并发的客户端请求分配给JSP页面。这是默认设置。如果使用true,那么必须保证正确同步对在页面级别定义的所有共享对象的访问。这包括在声明、具有页面作用域的JavaBean组件和page scope对象中创建的对象。

如果isThreadSafe设置为false,那么就以接收它们的顺序一次分配一个请求,并且对页面级别对象的访问不一定是受控制的。不过,仍然必须保证对application或者session作用域对象的属性、以及对具有应用程序或者会话作用域的JavaBean组件的访问是正确同步的。

JSP 脚本元素

JSP脚本元素用于创建和访问对象、定义方法、和管理控制流程。由于JSP技术的一个目标是分离静态模板数据与动态生成内容的代码,所以建议尽可能少地使用JSP脚本。许多需要使用脚本的工作都可以通过使用自定义标签来消除,见JSP页面中的自定义标签中的描述。

JSP技术允许容器支持任何可以调用Java对象的脚本语言。如果希望使用默认的脚本语言java以外的语言,那么必须在JSP页面开始处的page指令中指定它。

<%@ page language="scripting language" %> 

由于在JSP页面的servlet类中脚本元素转换为编程语言语句,所以必须导入所有JSP页面使用的类和包。如果页面语言是java,那么用下面page指令导入类或者包。

<%@ page import="packagename.*, fully_qualified_classname" %> 

例如,书店示例页面showcart.jsp用下面的指令导入实现购物车所需要的实现:

<%@ page import="java.util.*, cart.*" %> 

声明

JSP声明声明页面的脚本语言中的变量和方法。声明的语法如下:

<%! scripting language declaration %> 

如果脚本语言是Java编程语言,则在JSP页面的servlet类中声明JSP声明中的变量和方法。

书店示例页面initdestroy.jsp在一个声明中定义了名为bookDBAO的变量实例和在前面讨论初始化和结束方法jspInitjspDestroy

<%!
 private BookDBAO bookDBAO;
 public void jspInit() {
   ...
 }
 public void jspDestroy() {
   ...
 }
%>

Scriptlets

JSP scriptlet用于包含对于页面中使用的脚本语言有效的所有代码段。Scriptlet语法如下:

<%  
scripting language statements
%>

如果脚本语言设置为java,那么scriptlet就转换为Java编程语言语句段并插入到JSP页面的servlet的服务方法中。在scriptlet中创建的编程语言变量可以在JSP页面的任何地方访问。

JSP页面showcart.jsp包含从由购物车维护的项目集合中提取一个枚举、并设置在小车中所有项目中循环的结构scriptlet。在循环中,JSP页面提取图书对象的属性并用HTML标识编排它们的格式。由于while循环打开一个块,所以HTML标识后面是关闭这个块的scriptlet

<% 
 Iterator i = cart.getItems().iterator();
 while (i.hasNext()) {
   ShoppingCartItem item =
     (ShoppingCartItem)i.next();
   BookDetails bd = (BookDetails)item.getItem();
%>
    <tr>
   <td align="right" bgcolor="#ffffff">
   <%=item.getQuantity()%>
   </td>
   <td bgcolor="#ffffaa">
   <strong><a href="
   <%=request.getContextPath()%>/bookdetails?bookId=
   <%=bd.getBookId()%>"><%=bd.getTitle()%></a></strong>
   </td>
   ...
<%
 // End of while
 }
%>

输出显示在15-2中。

Duke's Bookstore Shopping Cart

15-2 Duke's Bookstore购物车

表达式

JSP expression 用于将已转换为字符串的脚本语言表达式的值插入返回给客户端的数据流中。如果脚本语言是Java编程语言,那么表达式就转换为一条语句,该语句将表达式的值转换为String对象,并将它插入隐式out对象中。

表达式的语法如下:

<%= scripting language expression %> 

注意在JSP表达式中不允许使用分号,即使在scriptlet中使用同一个表达式时有分号。

下面的scriptlet提取购物车中项目的数量:

<%
 // Print a summary of the shopping cart
 int num = cart.getNumberOfItems();
 if (num > 0) {
%>

然后用表达式将num的值插入输出流中并确定适合于该数字的字符串。

<font size="+2">
<%=messages.getString("CartContents")%> <%=num%>
 <%=(num==1 ? <%=messages.getString("CartItem")%> :
 <%=messages.getString("CartItems"))%></font>