作者:自由的猪 制作整理:左岸网络http://www.leftworld.net

第11章 JSP技术

JSP技术可以让你轻松的创建静态及动态的网络内容。JSP技术设计了所有Java Servlet技术的动态能力而且还提供了更自然的途径创建静态内容,JSP主要特点如下:

☆ 有专门的语言开发jsp页面,这些都是基于文本的来描述如何处理请求及产生响应。

☆ 为访问服务端对象进行构造。

☆ 有定义扩展语言的机制。

JSP技术也包含网络容器的应用程序接口(API),这些API供开发人员使用,这章节不讨论API。

什么是JSP页面

JSP是基于文本的文件,它包含两种类型的文本:静态模板数据——任何基于文本格式的都可以被表示,例如HTML,SVG,WML,和XML;动态内容由JSP元素构成。

下面是一个表单页面,让你选择位置,并显示当地的时间:

下面的例子包含了以下构成元素:

☆ 指令(<%@ page…%>)引用包含的类

☆ jsp:useBean 元素创建一个包含对象集的场所,并初始一个变量指向该对象。

☆ Scriptlets(<%…%>)或的本地的请求参数值,重申本地名称集,有条件的插入HTML文本到输出。

☆ 表达式(<%=….%>)插入本地值到输出中。

☆ jsp:include 元素发送一个请求到另一个页面,也包含调用页面中响应的响应。

<%@ page import=”java.util.*,MyLocales” %>

<%@ page contentType=”text/html;charset=ISO8859_1” %>

<html>

<head><title>Localized Dates</title></head>

<body bgcolor=”white”>

<jsp:userBean 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(selectedLocal.equals(locale))

{

%>

<option selected><%=locale%></option>

<%

}else

{

%>

 <option><%=locale%></option>

<%

}

}

%>

</selected>

<input type=”submit” name=”submit” value=”Get Date”>

</form>

<jsp:include page=”date.jsp”/>

</body>

</html>

JSP页面的例子

为了展示JSP技术,这章重写了Duke’s Bookstore里的每个servlet,把这些servlet改写为JSP。下表列出了各功能所对应的JSP页面:

Function

JSP Page

进入书店

Bookstore.jsp

创建书店横幅

Banner.jsp

浏览供销售的图书

Catalog.jsp

将一本书放到购物车中

Catalog.jsp and bookdetails.jsp

过的特定的书的详细信息

Bookdetails.jsp

显示购物车的内容

Showcart.jsp

删除购物车中的书籍

showcashier.jsp

购买购物车中的书籍

Cashier.jsp

收到确认信息

Recipt.jsp

书店应用程序中的数据保存在数据库中。然而,数据库的帮助类database.BookDB有两处改变:

1、数据库帮助类可以重写以确定JavaBean的设计模式。这样,jsp页面可以通过jsp元素来访问特定的JavaBean组件。

2、帮助对象可以通过enterprise bean来访问数据库,从而代替直接访问数据库。使用企业Bean的好处在于帮助类不再负责连接数据库;这项工作由企业Bean代替。而且,因为EJB容器负责维护数据库连接池,企业Bean获得联接的速度要比帮助类的要快。数据库帮助类的实现如下,该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;}

}

……

}

你可以按照下面的步骤来编译、部署,运行该程序。

1、找到j2eetutorial/examples,通过运行ant并编译它;

2、启动j2ee服务器

3、启动部署工具;

4、通过运行cloudscape –start来启动Cloudscape数据库;

5、如果你还没有创建书店数据库,运行ant create web-db;

6、创建J2EE应用程序并命名为Bookstore2App

a. 选择FileàNewàApplication,

b. 在文件选择中,找到j2eetutorial/examples/src/web/bookstore2,

c. 在文件名框中输入Bookstore2App,

d. 单击New Application

e. 单击OK

7、将Bookstore2WAR加入到Bookstore2App应用程序中。

a. 选择FileàAddàWeb WAR

b. 在Add Web WAR对话框中,找到j2eetutorial/examples/build/web/bookstore2,选择bookstore2.war。单击Add Web WAR

8、把BookDBEJB企业Bean加入到应用程序中

a. 选择FileàNew Enterprise Bean;

b. 从Create New JARFile In Application 复合框中选择Bookstore2App;

c. 在显示名称的框中输入BookDBJAR;

d. 单击Edit来增加内容文件;

e. 在编辑框中,导航到j2eetutorial/examples/build/web/ejb/目录,并增加database和exception包,单击Next.

f. 为Enterprise Bean选择Session和Stateless状态。

g. 为Enterprise Bean选择database.BookDBEJBImp1

h. 在远程接口框中,为远程主接口(Remote Home)选择database.BookDBEJBHome,为database.BookDBEJB选择Remote Interface.

i. 为Enterprise Bean的名称输入BookDBEJB.

j. 单击Next,接着单击Finish.

9、增加一个数据库资源引用到BookDBEJB

a. 选择Enterprise Bean BookDBEJB

b. 选择资源引用标签

c. 单击Add

d. 从类型栏中选择javax.sql.DataSource

e. 在jndi名称中输入jdbc/BookDB

10、保存BookDBEJB

a. 选择BookDBJAR

b. 选择FileàSave as

c. 导航到目录examples/build/web/ejb

d. 在文件名框中输入bookDB.jar

e. 单击Save EJB JAR As

11、增加引用到Enterprise Bean BookDBEJB

a. 选择Bookstore2WAR

b. 选择EJB引用标签

c. 单击增加

d. 在编码命名框中输入ejb/BookDBEJB

e. 选择类型框中选择Session类型

f. 选择远程接口

g. 在主接口列中输入database.BookDBEJBHome

h. 在Local/Remote接口列中输入database.BookDBEJB

12、指定JNDI名称

a. 选择Bookstore2App

b. 在Application表中,找到EJB组件并在JNDI名框中输入BookDBEJB

c. 在引用表中,找到EJB Ref并在JNDI名框中输入BookDBEJB

d. 在引用表中找到资源组件,并在JNDI名框中输入jdbc/Cloudscape.

13、输入上下文根目录

a. 选择标签Web Context

b. 输入bookstore2

14、部署应用程序

a. 选择ToolsàDeploy

b. 单击finish

15、打开书店URL http://<host>:8000/bookstore2/enter.

JSP页面的生命周期

一个JSP页面的服务请求是作为servlet来执行的。这样,JSP页面的生命周期及各种能力就取决于servlet技术。

当一个请求映射到一个Jsp页面时,由一个特殊的servlet来处理,该servlet首先检查一下对应的JSP页面有没有改动,如果有变动则将该JSP页面转换为servlet类并编译这个类,在开发过程中,JSP的优点在于执行过程都是自动运行的。

转换与编译

在转换期间,JSP页面的每种数据类型都区别对待,模板数据都转换为代码。JSP元素分为以下几类:
☆ 指令用来控制网络容器是如何解释并执行JSP页面的。

☆ 脚本元素是插入到Jsp页面中的servlet类。

☆ 形如<jsp:XXX/>的元素用来调用JavaBean组件或调用Java Servlet API。

在JSP页面第一次获得请求时,解释与编译期间都可能产生错误。当错误发生在页面被解释时,服务器将返回ParseException,servlet类源文件将唯恐或不完全。最后不完全的行将产生一个指针指向错误JSP元素。

如果错误发生在JSP页面被编译期间(例如,有个语法错误发生在脚本中),服务器将返回JasperException,并给出出错点的JSP页面的servlet和行。

一旦页面被解释并执行,JSP页面的servlet的生命周期大部分与servlet类似:

1、如果JSP页面的servlet实例不存在,容器将:

a) 载入JSP的servlet class

b) 实例化一个servlet class

c) 通过调用jspInit 方法实例化servlet

2、调用_jspService方法,传递请求及响应对象。

3、如果容器需要移除JSP页面的servlet,就调用jspDestroy方法。

异常

你可以控制各种JSP页面的页面指令使用的执行参数。这些指令适合于缓存输出及处理错误。其它指令将在这章中讲到。

缓存

当一个JSP页面被执行时,写到响应对象中的输出自动缓存。你可以通过下面得也面指令来设置缓存大小:

<%@ page buffer=”none|xxxkb” %>

在任何数据返回到客户端之前,一个较大的缓存允许写更多的内容,这样为JSP页面提供了更多的时间来设置正确的状态嘛及头信息或重定向到另一网络资源。一个较小的缓存减少了服务器的内存使用,能让客户更快的接收数据。

处理错误

当JSP页面执行时,任何数量的错误都有可能发生。为了指定表明,容器应在异常发生时定向到一个错误页面,在JSP页面的开头可以包含下面的指令:

<%@ page errorPage=”file_name”%>

初始化及结束一个JSP页面

你可以自定义初始化过程以允许JSP页面读取持久数据,初始化资源,而且通过重载jspInit方法可以做任何其他一次性动作。你也可以通过jspDestroy方法释放资源。这些方法使用Jsp申明来定义。

书店例子页面initdestroy.jsp定义了jspInit方法来返回或创建一个企业bean,database.BookDBEJB用来访问书店数据库;initdestroy.jsp存储了一个bean的引用。

       Private BookDBEJB bookDBEJB;

       Public void  jspInit()

       {

              bookDBEJB=(BookDB)getServletContext().getAttribute(“bookDBEJB”);

              if(bookDBEJB==null)

              {

                     try

                     {

                            InitialContext ic=new InitialContext();

                            Object objRef=ic.lookup(“java:comp/env/ejb/BookDBEJB”);

                            BookDBEJBHome home=(BookDBEJBHome)PortableRemoteObject.narrow(objRef,database.BookDBEJBHome.class);

                            BookDBEJB=home.create();

                            GetServletContext().setAttribute(“bookDBEJB”,bookDBEJB);

                     }catch(RemoteException ex){System.out.println(“Couldn’t create database bean.”+ex.getMesage());

                     }catch(CreateException ex){System.out.println(“Couldn’t create database bean.”);

                     }catch(NamingException ex){System.out.println(“Unable to lookup home.”);}

              }

}

当jsp页面从服务方法中移出时,jspDestroy方法释放变量BookDBEJB.

       Public void jspDestroy()

       {

              bookDBEJB=null;

       }

由于企业Bean在各jsp页面间共享,当一个程序运行时,应将企业bean初始化。Java servlet技术提供了应用程序生命周期事件及监听类。作为练习,你可以将负责生成EJB的代码移动到context listener类。

生成静态内容

你可以在jsp页面中生成静态内容,静态的内容可以通过基于文本的格式如HTML,WML及XML来表示。缺省方式为HTML,如果你想用除HTML外的格式,可以通过包含page指令的属性contentType来设置。例如,你想一个页面包含无线标记语言(WML),可以这样:

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

生成动态内容

你可以通过访问脚本元素中的Java programming language对象来生成动态内容。

在JSP中使用对象

你可以在JSP页面中访问一个对象变量,包括企业bean及JavaBean组件。JSP技术自动产生一些对象变量,也可以生成访问应用程序特定的对象。

隐式对象

隐式对象由网络容器生成,它包含一些特定请求,页面,应用的相关信息。许多对象通过Java Servlet技术定义。下表列出了隐式对象:

变量

描述

Application

Javax.servlet.ServletContext

该上下文是为在同一个应用中的Jsp servlet及网络组件

Config

Javax.servlet.ServletConfig

为Jsp页面初始化信息

Exception

Java.lang.Throwable

来自error页的可以访问

Out

Javax.servlet.jsp.JspWriter

输出流

Page

Java.lang.Object

Jsp页面处理当前请求的实例,页面所有者一般不使用

PageContext

Javax.servlet.jsp.PageContext

Jsp页面的上下文。

Request

Javax.servlet.ServletRequest

触发执行Jsp页面的请求。

Response

Javax.servlet.ServletResponse

返回到客户端的响应

Session

Javax.servlet.http.HttpSession

客户端的会话对象

特定应用程序对象

只要可能,应用对象的行为应封装到对象中,好让页面设计人员能集中精力在表现问题上。对像可以由精于程序设计的人员来实现访问数据库及其他服务。有四种方法可以在JSP页面中创建使用对象:

☆ 在声明中创建的JSP页面的servlet类的实例及类变量,并在scriptlets和expresssion中访问;

☆ JSP页面的servlet类本地变量的创建,在scriptlets及expressions中使用;

☆ scope对象的属性在scriptlets及expressions中创建并使用;

☆ 通过JSP元素使用JavaBean组件。也可以在声明中及scriptlet中创建JavaBean组件并调用JavaBean组件的方法

共享对象

作为一个多线程的servlets,条件影响并发访问共享对象。可以通过下面的方法页面指令来让网络容器分派多客户请求:

<%@ page isThreadSafe=”true|false”%>

当isThreadSafe设置为true时,网络容器会分派多并发客户请求到JSP页面。这是缺省设置。如果使用true,你必须确保正确地同步地访问在页面及定义的共享对象,page scope的JavaBean组件,以及page scope对象的属性。

如果isThreadSafe设置为false,将按照接收顺序,一次只分派一次请求,访问页面级对象不需要控制,然而,你仍然要确保对对象具有application scope及session scope属性的及JavaBean组件具有application scope及session scope属性的访问要正确地并发运行。

JSP scripting 元素

JSP scripting 元素用来创建并访问对象,定义方法,及管理控制流。 由于JSP技术的一个目标就是尽量将静态的模板数据统动态的内容相分离,所以推荐少使用JSP scripting。许多必需的工作可以使用自定义的标签库来减少使用scripts。

JSP技术允许容器支持任何可以调用Java对象的脚本语言。如果你想要使用除了缺省外的脚本语言,你必须在page指令中指定:

<%@ page language=”scripting language”%>

由于脚本元素都已在JSP page’s servlet类中转换成编程语言声明,所以你必须在JSP页面中包含要用到的类及包。如果页面语言为java,使用page指令包含一个类及包:

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

例如,在bookstore例子showcart.jsp:

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

声明

JSP声明是用来声明在页面脚本中使用的变量及方法,声明的语法如下:

<%! Scripting language declaration %>

当脚本语言是Java时,JSP页面中声明部分的变量及方法成为JSP页面servlet类的声明部分。

在书店例子中的initdestroy.jsp页面定义了一个实例变量——bookDBEJB及两个方法jspInit和jspDestroy:

       <%!

              Private BookDBEJB bookDBEJB;

              Public void jspInit()

              {

                     ……

              }

              public void jspDestroy()

              {

                     ……

              }

       %>

脚本(scriptlets)

JSP脚本使用来包含代码片断的,语法如下:

<%

       scripting language statements

%>

当脚本语言设置为java时,脚本就转换成为java语句片断并插入到JSP servlet中的service方法中。在scriptlet中声明的变量在jsp页面中都可以访问。

JSP页面showcart.jsp包含了一个从collection返回迭代器的scriptlet。在循环中,JSP页面从book对象中取出其属性,并用HTML格式化输出。例子如下:

<%

       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>

       ……

<%

       <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>

              …

<%

       }

%>

输出结果见下图:

表达式

JSP表达式使用来插入脚本语言表达式的值,并转换为字符串返回到客户端。当脚本语言为java时,表达式转换为一个将表达式的纸转换为字符串对象的语句并插入到隐含对象out。

语法如下:

<%= scripting language expression %>

注意分号不能出现在JSP表达式中,即使是你在scriptlet中使用相同的表达式之间加分号。

下面的例子在脚本中返回了购物车中项的数量:

<%

       int num=cart.getNumberOfItems();

       if(num>0){            %>

表达式接着将num的值插入到输出流中:

<font size=”+2”>

       <%=messages.getString(“CartContents”)%><%=num%>

       <%=(num==1?<%=messages.getString(“CartItem”)%>;

       <%=messages.getString(“CartItems”))%></font>

在JSP页面中包含内容

在JSP页面中包含其他组件有两种机制:include指令和jsp:include元素。

Include指令是当JSP页面编译为servlet类时来处理的。效果就是插入另一文件中的文本——静态的内容或其他JSP页面到JSP页面中。

也许使用include指令来包含页头内容,版权信息,或其他需要在另一页面中重用的大块内容。

语法如下:

<%@ include file=”filename: %>

因为要静态的将包含指令放到每个重用资源引用的文件中,这种方法有它的局限性。为了更灵活的建立页面内容而除去大量的快,可以使用标签库。

Jsp:include元素是在当JSP页面执行时来处理的。该动作允许在JSP页面中包含静态及动态的资源。包含静态与动态的内容的结果是不同的。如果资源是静态的,内容直接插入到调用JSP文件。如果为动态的,则请求发送到被包含的资源,被包含的页面被执行,结果在调用页面中反映出来。语法如下:

<jsp:include page=”includepage” />

转换控制到另一网络组件

转换控制到另一网络组件的机制使用了Java Servlet API提供的功能。可以通过jsp:forward元素来实现该功能:

<jsp:forward page=”main.jsp”/>

注意:任何数据如果已经返回到客户端,jsp:forward元素将会抛出异常IllegalStateException。

参数元素

当include或forward元素被调用时,最初的请求对象提供给目标页面。如果要提供额外的数据到目标页面,可以通过jsp:param元素来增加参数到请求对象。

<jsp:include page=”…”>

       <jsp:param name=”param1” value=”value1”/>

</jsp:include>

包含Java小程序(applet)

可以在jsp页面中通过使用jsp:plugin元素来加入applet及JavaBean组件。该元素产生包含适当的依赖于客户浏览器的结构的HTML。它下载Java插件并顺序执行客户端组件。该元素语法如下:

       <jsp:plugin type=”bean|applet” code=”objectCode” codebase=”objectCodebase”

              { align=”alignment”}  {archive=”archiveList”} { height=”height”} { hspace=”hspace”}

              {jreversion=”jreversion”} {name=”componentName”}{vspace=”vspace:”}

              {width=”width”} {nspluginurl=”url”} {iepluginur=”url”}>

              {<jsp:params>

                     {<jsp:param name=”paramName” value=”paramValue”/>}+

              {</jsp:params>}

              {<jsp:fallback>arbitrary_text</jsp:fallback>}

       </jsp:plugin> 

jsp:plugin标签被客户的请求转换为<object>或<embed>标签。Jsp:plugin的属性为表现层提供配置数据,如同插件需要的版本一样。属性nspluginrul和iepluginurl指定了下载插件的URL。

Jsp:param元素指定了传递到applet或JavaBean的参数。Jsp:fallback元素指明了在客户浏览器不支持插件的情况下使用的内容。

如果插件可以运行但applet或JavaBean不能,一个特定的消息将发送到用户,就像弹出窗口报告ClassNotFoundException.

在书店应用中有一页面banner.jsp创建了一个横幅用来显示由DigitalClock产生的动态数字钟。如下图:

Jsp:plugin元素用来下载下面的applet:

<jsp:plugin type=”applet” code=”DigitalClock.class”  codebase=”/bookstore2” jreversion=”1.3”

       align=”center” height=”25” width=”300”

nspluginurl=”http://java.sun.com/products/plugin/1.3.0_01/plugin-install.html

iepluginurl=”http://java.sun.com/products/plugin/1.3.0_01/jinstall-130_01-win32.cab#Version=1.3.0.1”>

<jsp:params>

       <jsp:param name=”language” value=”<%=request.getLocale().getCountry()%>”/>

       <jsp:param name=”country” value=”<%=request.getLocale().getCountry()%>” />    

       <jsp:param name=”bgcolor” value=”FFFFFF” />

       <jsp:param name=”fgcolor” value=”CC0066” />

       </jsp:params>

              <jsp:fallback>

              <p>Unable to start plugin.</p>

              </jsp:fallback>

</jsp:plugin>

扩展JSP语言

你可以通过与脚本衔接的JavaBean组件执行广泛的多样的动态处理任务,包括访问数据库,使用企业服务例如电子邮件,目录,管理流控制等。然而使用脚本的缺陷是难于维护页面。在这种情况下JSP技术提供了自定义标签技术,它可以让你通过扩喊来访问封装动态功能的对象。自定义标签给JSP页面提供了另一组件级技术。

例子,重调用来循环显示购物车内容的脚本如下:

<%

       Iterator I=cart.getItems().iterator();

       While(i.hasNext())

       {

              ShoppingCartItem item=(ShoppingCartItem)i.next();

              ……

%>

<tr>

       <td align=”center” bgcolor=”#ffffff” >

       <%=item.getQuantity()%>

       </td>

       ……

<%

       }

%>

迭代自定义标签取出了代码逻辑并管理脚本中的变量item:

<logic:iterate id=”item”

              collection=”<%=cart.getItems()%>”>

              <tr>

              <td align=”right” bgcolor=”#ffffff”><%=item.getQuantity()%>

              </td>

              ……

</logic:iterate>

自定义标签被打包并发布在称之为标签库的单元里。自定义标签库的语法如同使用JSP元素,类似<prefix:tag>,然而,对于自定义标签,prefix为被用户定义标签库。