您正在查看: jsp 分类下的文章

JavaServer Pages Standard Tag Library JSTL

第七章 JSTL 1.1

JSTL 全名为JavaServer Pages Standard Tag Library,目前最新的版本为1.1。JSTL是由JCP

(Java Community Process)所指定的标准规格,它主要提供给Java Web 开发人员一个标准通用的

标签函数库。

Web 程序开发人员能够利用JSTL 和EL来开发Web 程序,取代传统直接在页面上嵌入Java程序

(Scripting)的做法,以提高程序可读性、维护性和方便性。

本章中,笔者将详细介绍如何使用JSTL 中各种不同的标签,将依序介绍条件、循环、URL、U18N、

XML、SQL 等标签的用法,让读者对JSTL 有更深层的了解,并且能够学会如何使用JSTL。

7-1 JSTL 1.1 简介

JavaServer Pages Standard Tag Library (1.1 ),它的中文名称为JSP 标准标签函数库。JSTL

是一个标准的已制定好的标签库,可以应用于各种领域,如:基本输入输出、流程控制、循环、XML

文件剖析、数据库查询及国际化和文字格式标准化的应用等。从表7-1 可以知道,JSTL所提供的标

签函数库主要分为五大类:

(1)核心标签库 (Core tag library)

(2)I18N 格式标签库 (I18N-capable formatting tag library)

(3)SQL 标签库 (SQL tag library)

(4)XML 标签库 (XML tag library)

(5)函数标签库 (Functions tag library)

表 7-1

JSTL 前置名称URI 范例

核心标签库c http://java.sun.com/jsp/jstl/core <c:out>

I18N格式标签库fmt http://java.sun.com/jsp/jstl/xml <fmt:formatDate>

SQL 标签库sql http://java.sun.com/jsp/jstl/sql <sql:query>

XML 标签库xml http://java.sun.com/jsp/jstl/fmt <x:forBach>

函数标签库fn http://java.sun.com/jsp/jstl/functions <fn:split>

另外,JSTL 也支持EL(Expression Language)语法,例如:在一个标准的JSP 页面中可能会使

用到如下的写法:

<%= userList.getUser().getPhoneNumber() %>

使用JSTL 搭配传统写法会变成这样:

<c_rt:out value="<%= userList.getUser( ).getPhoneNumber( ) %>" />

使用JSTL 搭配EL,则可以改写成如下的形式:

<c:out value="${userList.user.phoneNumber}" />

虽然对网页设计者来说,假如没有学过Java Script 或者是第一次看到这种写法时,可能会搞

不太懂,但是与Java 语法相比,这应该更加容易学习。

7-1-1 安装使用JSTL 1.1 JSTL

1.1 必须在支持servlet 2.4 且JSP 2.0 以上版本的Container 才可使用。JSTL 主要由apache

组织的Jakarta Project 所实现,因此读者可以到

http://jakarta.apache.org/builds/jakarta-taglibs/releases/standard/ 下载实现好的JSTL

1.1,或者直接使用本__________书光盘中JSTL 1.1,软件名称为:jakarta-taglibs-standard-current.zip。

下载完后解压缩,可以发现文件夹中所包含的内容如图7-1 所示:

图 7-1 jakarta-taglibs-standard-1.1.0-B1 的目录结构

将 lib 中的jstl.jar、standard.jar 复制到Tomcat的WEB-INF\lib 中,然后就可以在JSP 网

页中使用JSTL了。除了复制 .jar 文件外,最好也把tld 文件的目录也复制到WEB-INF 中,以便日

后使用。

注意

lib 目录下,除了jstl.jar 和standard.jar之外,还有old-dependencies目录,这目录里面

的东西是让之前JSTL 1.0 的程序也能够在JSTL 1.1 环境下使用。tld 目录下有许多TLD 文件,其

中大部分都是JSTL 1.0 的TLD 文件,例如:c-1_0.tld 和c-1_0-rt.tld。

下面写一个测试用的范例程序HelloJSTL.jsp,程序主要是显示浏览器的版本和欢迎的字符串。

■ HelloJSTL.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>测试你的第一个使用到JSTL 的网页</title>

</head>

<body>

<c:out value="欢迎测试你的第一个使用到JSTL 的网页"/>

</br>你使用的浏览器是:</br>

<c:out value="${header['User-Agent']}"/>

<c:set var="a" value="David O'Davies" />

<c:out value="David O'Davies" escapeXml="true"/>

</body>

</html>

在HelloJSTL.jsp 的范例里,笔者用到核心标签库(Core)中的标准输出功能和EL 的header

隐含对象。若要在JSP 网页中使用JSTL 时,一定要先做下面这行声明:

< %@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

这段声明表示我将使用JSTL 的核心标签库。一般而言,核心标签库的前置名称(prefix)都为

c,当然你也可以自行设定。不过uri 此时就必须为http://java.sun.com/jsp/jstl/core。

注意

JSTL 1.0 中,核心标签库的uri默认为http://java.sun.com/jstl/core,比JSTL 1.1 少一

个jsp/ 的路径。因为JSTL 1.1 同时支持JSTL 1.0 和1.1,所以假若核心标签库的uri 为

http://java.sun.com/jstl/core,则将会使用到JSTL 1.0 的核心标签库。

接下来使用核心标签库中的out 标签,显示value的值。${header['User-Agent']}表示取得

表头里的User-Agent 的值,即有关用户浏览器的种类。

<c:out value="欢迎测试你的第一个使用到JSTL 的网页" />

<c:out value="${header['User-Agent']}" />

HelloJSTL.jsp 的执行结果如图7-2 所示。

图 7-2 HelloJSTL.jsp 的执行结果

假若读者想要自定义taglib的uri 时,那就必须在web.xml 中加入设定值。例如:假若uri想

要改为http://www.javaworld.com.tw/jstl/core 时,web.xml 就必须加入如下设定:

<web-app>

<jsp-config>

<taglib>

<taglib-uri>http://www.javaworld.com.tw/jstl/core</taglib-uri>

<taglib-location>/WEB-INF/tld/c.tld</taglib-location>

</taglib>

</jsp-config>

</web-app>

在上面的设定中,<taglib-uri>主要是设定标签库的URI;而<taglib-location>则是用来设定

标签对应的TLD 文件。因此,使用<%@ taglib %>指令时,可以直接写成如下语句:

<%@ taglib prefix="c" uri="http://www.javaworld.com.tw/jsp/jstl/core" %>

7-1-2 JSTL 1.1 VS. JSTL 1.0

JSTL 1.0 更新至JSTL 1.1 时,有以下几点不同:

(1) EL 原本是定义在JSTL 1.0的,现在EL 已经正式纳入JSP 2.0标准规范中,所以在JSTL 1.1

规范中,已经没有EL 的部分,但是JSTL 依旧能使用EL。

(2) JSTL 1.0 中,又分EL 和RT 两种函数库,到了JSTL 1.1 之后,已经不再分这两种了。以

下说明EL 和RT 的差别:

EL

■ 完全使用Expression Language

■ 简单

■ 建议使用

RT

■ 使用Scriptlet

■ Java 语法

■ 供不想转换且习惯旧表示法的开发者使用

笔者在此强烈建议大家使用EL 来做,简单又方便。

(3) JSTL 1.1 新增函数(functions)标签库,主要提供一些好用的字符串处理函数,例如:

fn:contains、fn:containsIgnoreCase、fn:endsWith、fn:indexOf、fn:join、fn:length、fn:replace、

fn:split、fn:startsWith 和fn:substring 等等。

除了上述三项比较大的改变之外,还包括许多小改变,在此不多加说明,有兴趣的读者可以去

看 JSTL 1.1 附录B“Changes”部分,那里有更详尽的说明。

7-1-3 安装standard-examples

当解压缩jakarta-taglibs-standard-current.zip 后,文件夹内(见图7-1)有一个

standard-examples.war的文件,将它移至Tomcat的webapps后,重新启动Tomcat会发现,在webapps

目录下多了一个standard-examples 的目录。接下来我们打开IE,在URL 位置上输入

http://localhost:8080/ standard-examples,你将会看到图7-3 所示的画面。

这个站台有很多JSTL 的范例,它包括以下几部分:

■ General Purpose Tags

■ Conditional Tags

■ Iterator Tags

■ Import Tags

■ I18N & Formatting Tags

■ XML Tags

■ SQL Tags

■ Functions

■ Tag Library Validators

■ Miscellaneous

图 7-3 standard-examples 站台

这些范例程序几乎涵盖了所有的JSTL 标签函数库,假若读者对哪一个标签的使用有问题,可以

先来找一找这里的范例程序,应该或多或少会有所帮助。

7-2 核心标签库 (Core tag library)

首先介绍的核心标签库(Core)主要有:基本输入输出、流程控制、迭代操作和URL 操作。详细

的分类如表7-2 所示,接下来笔者将为读者一一介绍每个标签的功能。

表 7-2

分类功能分类标签名称

表达式操作

out

set

remove

catch

流程控制

if

choose

when

otherwise

迭代操作

forEach

forTokens

Core

URL 操作

Import

param

url

param

redirect

param

在JSP 中要使用JSTL 中的核心标签库时,必___________须使用<%@ taglib %>指令,并且设定prefix 和

uri 的值,通常设定如下:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

上述的功用在于声明将使用JSTL 的核心标签库。

注意

假若没有上述声明指令,将无法使用JSTL的核心功能,这是读者在使用JSTL 时必须要

小心的地方。

7-2-1 表达式操作

表达式操作分类中包含四个标签:<c:out>、<c:set>、<c:remove>和<c:catch>。接下来将依序

介绍这四个标签的用法。

<c:out>

<c:out>主要用来显示数据的内容,就像是 <%= scripting-language %> 一样,例如:

Hello ! <c:out value="${username}" />

语法

语法1:没有本体(body)内容

<c:out value="value" [escapeXml="{true|false}"] [default="defaultValue"] />

语法2:有本体内容

<c:out value="value" [escapeXml="{true|false}"]>

default value

</c:out>

属性

名称 说明EL 类型必须默认值

value 需要显示出来的值YObject Object 是无

default 如果value 的值为null,则显示default 的值YObject Object 否无

escapeXml 是否转换特殊字符,如:<转换成&lt; Yboolean boolean 否 true

注意

表格中的EL字段,表示此属性的值是否可以为EL 表达式,例如:Y表示 attribute = "${表达式}"

为符合语法的,N 则反之。

Null 和错误处理

· 假若 value为null,会显示default 的值;假若没有设定default的值,则会显示一个空

的字符串。

说明

一般来说,<c:out>默认会将 <、>、’、” 和 & 转换为 &lt;、&gt;、&#039;、&#034; 和 &amp;。

假若不想转换时,只需要设定<c:out>的escapeXml 属性为fasle 就可以了(见表7-3)

表 7-3

字符Entity

< &lt;

> &gt;

’ &#039;

” &#034;

& &amp;

范例

<c:out value="Hello JSP 2.0 !! " />

<c:out value="${ 3 + 5 }" />

<c:out value="${ param.data }" default="No Data" />

<c:out value="<p>有特殊字符</p>" />

<c:out value="<p>有特殊字符</p>" escapeXml="false" />

1.在网页上显示 Hello JSP 2.0 !! ;

2.在网页上显示 8;

3.在网页上显示由窗体传送过来的data 参数之值,假若没有data 参数,或data 参数的值为

null 时,则网页上会显示No Data;

4.在网页上显示“<p>有特殊字符</p>”;

5.在网页上显示“有特殊字符”。

<c:set>

<c:set>主要用来将变量储存至JSP 范围中或是JavaBean 的属性中。

语法

语法1:将 value 的值储存至范围为scope 的 varName 变量之中

<c:set value="value" var="varName" [scope="{ page|request|session|application }"]/>

语法2:将本体内容的数据储存至范围为scope 的 varName 变量之中

<c:set var="varName" [scope="{ page|request|session|application }"]>

… 本体内容

</c:set>

语法3:

将 value 的值储存至 target 对象的属性___________中

< c:set value="value" target="target" property="propertyName" />

语法4:

将本体内容的数据储存至 target 对象的属性中

<c:set target="target" property="propertyName">

… 本体内容

</c:set>

属性

名称 说明EL 类型必须默认值

value 要被储存的值Y Object 否无

var 欲存入的变量名称N String 否无

scope var 变量的JSP 范围N String 否page

target 为一JavaBean 或java.util.Map 对象Y Object 否无

property 指定target 对象的属性Y String 否无

Null 和错误处理

语法3 和语法4 会产生异常错误,有以下两种情况:

☆ target 为null

☆ target 不是java.util.Map 或JavaBean 对象

假若 value 为null 时:将由储存变量改为移除变量

☆ 语法1:由var 和scope 所定义的变量,将被移除

□ 若 scope 已指定时,则PageContext.removeAttribute(varName, scope)

□ 若 scope 未指定时,则PageContext.removeAttribute(varName)

☆ 语法3:

□ 假若 target 为Map 时,则Map.remove(property)

□ 假若 target 为JavaBean 时,property 指定的属性为null

说明

使用<c:set>时,var 主要用来存放表达式的结果;scope 则是用来设定储存的范围,例如:假

若scope="session",则将会把数据储存在session中。如果<c:set>中没有指定scope时,则它会

默认存在Page 范围里。

注意

var 和scope 这两个属性不能使用表达式来表示,例如:我们不能写成

scope="${ourScope}"或者是var="${username}"。

我们考虑下列的写法:

<c:set var="number" scope="session" value="${1 + 1}"/>

把1+1的结果2储存到number变量中。如果<c:set>没有value属性,此时value之值在<c:set>

和</c:set>之间,本体内容看下面的范例:

<c:set var="number" scope="session">

<c:out value="${1+1}" />

</c:set>

上面的 <c:out value="${1+1}" /> 部分可以改写成2 或是 <%=1+1%> ,结果都会一样,也就

是说,<c:set>是把本体(body)运算后的结果来当做value的值。另外,<c:set>会把body 中最开头

和结尾的空白部分去掉。如:

<c:set var="number" scope="session">

_____________1 + 1

</c:set>

则number 中储存的值为1 + 1 而不是 1 + 1。

范例

<c:set var="number" scope="request" value="${1 + 1}" />

<c:set var="number" scope="session" />

${3 + 5}

</c:set>

<c:set var="number" scope="request" value="${ param.number }" />

<c:set target="User" property="name" value="${ param.Username}" />

1.将2 存入Request 范围的number 变量中;

2.将8 存入Session 范围的number 变量中;

3.假若 ${param.number}为null 时,则移除Request 范围的number 变量;若${param.number}

不为null 时,则将 ${param.number}的值存入Request 范围的number 变量中;

4.假若 ${param.Username}为null 时,则设定User(JavaBean)的name 属性为null;若不为

null 时,则将 ${param.Username}的值存入User(JavaBean)的name 属性(setter 机制)。

注意

上述范例的3.中,假若 ${param.number}为null时,则表示移除Request范围的number变量。

● <c:remove>

<c:remove>主要用来移除变量。

语法

<c:remove var="varName" [scope="{ page|request|session|application }"] />

属性

名称 说明EL 类型必须默认值

var 欲移除的变量名称N String 是无

scope var 变量的JSP 范围N String 否page

说明

<c:remove>必须要有var 属性,即要被移除的属性名称,scope 则可有可无,例如:

<c:remove var="number" scope="session" />

将number 变量从Session 范围中移除。若我们不设定scope,则<c:remove>将会从Page、

Request、Session 及Application 中顺序寻找是否存在名称为number 的数据,若能找到时,

则将它移除掉,反之则不会做任何的事情。

范例

笔者在这里写一个使用到<c:set>和<c:remove>的范例,能让读者可以更快地了解如何使用

它们,此范例的名称为Core_set_remove.jsp。

■ Core_set_remove.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_set_remove.jsp</title>

</head>

<body>

<h2><c:out value="<c:set>和<c:remove> 的用法" /></h2>

<c:set scope="page" var="number">

<c:out value="${1+1}"/>

</c:set>

<c:set scope="request" var="number">

<%= 3 %>

</c:set>

<c:set scope="session" var="number">

4

</c:set>

初始设置

<table border="1" width="30%">

<tr>

<th>pageScope.number</th>

<td><c:out value="${pageScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>requestScope.number</th>

<td><c:out value="${requestScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>sessionScope.number</th>

<td><c:out value="${sessionScope.number}" default="No Data" /></td>

</tr>

</table></br>

<c:out value='<c:remove var="number" scope="page" />之后'/>

<c:remove var="number" scope="page" />

<table border="1" width="30%">

<tr>

<th>pageScope.number</th>

<td><c:out value="${pageScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>requestScope.number</th>

<td><c:out value="${requestScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>sessionScope.number</th>

<td><c:out value="${sessionScope.number}" default="No Data" /></td>

</tr>

</table></br>

<c:out value='<c:remove var="number" />之后'/>

<c:remove var="number" />

<table border="1" width="30%">

<tr>

<th>pageScope.number</th>

<td><c:out value="${pageScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>requestScope.number</th>

<td><c:out value="${requestScope.number}" default="No Data" /></td>

</tr>

<tr>

<th>sessionScope.number</th>

<td><c:out value="${sessionScope.number}" default="No Data" /></td>

</tr>

</table>

</body>

</html>

笔者一开始各在Page、Request和Session三个属性范围中储存名称为number 的变量。然

后先使用<c:remove var="number" scope="page" />把Page 中的number 变量移除,最后再使

用<c:remove var="number" />把所有属性范围中number 的变量移除。Core_set_remove.jsp

的执行结果如图7-4 所示:

图 7-4 Core_set_remove.jsp 的执行结果

● <c:catch>

<c:catch>主要用来处理产生错误的异常状况,并且将错误信息储存起来。

语法

<c:catch [var="varName"] >

… 欲抓取错误的部分

</c:catch>

属性

名称 说明EL 类型必须默认值

var 用来储存错误信息的变量N String 否无

说明

<c:catch>主要将可能发生错误的部分放在<c:catch>和</c:catch>之间。如果真的发生错

误,可以将错误信息储存至varName 变量中,例如:

<c:catch var="message">

: //可能发生错误的部分

</c:catch>

另外,当错误发生在<c:catch>和</c:catch>之间时,则只有<c:catch>和</c:catch>之间的程序

会被中止忽略,但整个网页不会被中止。

范例

笔者写一个简单的范例,文件名为Core_catch.jsp,来让大家看一下<c:catch>的使用方式。

■ Core_catch.jsp

<%@ page contentType="text/html;charset=GB2312 " %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_catch.jsp</title>

</head>

<body>

<h2><c:out value="<c:catch> 的用法" /></h2>

<c:catch var="error_Message">

<%

String eFormat = "not number";

int i = Integer.parseInt(eFormat);

%>

</c:catch>

${error_Message}

</body>

</html>

笔者将一个字符串转成数字,如果字符串可以转为整数,则不会发生错误。但是这里笔者故意

传入一个不能转成数字的字符串,让<c:catch>之间产生错误。当错误发生时,它会自动将错误存到

error_Message 变量之中,最后再用<c:out>把错误信息显示出来,执行结果如图7-5 所示。

图 7-5 Core_catch.jsp 的执行结果

可以发现到网页确实显示格式错误的信息。如果我们不使用<c:catch>,而把范例中的<c:catch>

和</c:catch>拿掉,结果如图7-6 所示。

图 7-6 Core_catch.jsp 没有<c:catch>和</c:catch>的执行结果

7-2-2 流程控制

流程控制分类中包含四个标签:<c:if>、<c:choose>、<c:when>和<c:otherwise>,笔者依此顺

序依次说明这四个标签的使用。

● <c:if>

<c:if>的用途就和我们一般在程序中用的if 一样。

语法

语法1:没有本体内容(body)

<c:if test="testCondition" var="varName"

[scope="{page|request|session|application}"]/>

语法2:有本体内容

<c:if test="testCondition" [var="varName"]

[scope="{page|request|session|application}"]>

具体内容

</c:if>

属性

名称 说明EL 类型必须默认值

test

如果表达式的结果为true,则执行本体内容,false

则相反

Y boolean 是 无

var 用来储存test 运算后的结果,即true 或false N String 否 无

scope var 变量的JSP 范围N String 否 page

说明

<c:if> 标签必须要有test 属性,当test 中的表达式结果为true 时,则会执行本体内容;如

果为false,则不会执行。例如:${param.username = = 'admin'},如果param.username 等于admin

时,结果为true;若它的内容不等于admin 时,则为false。

接下来看下列的范例:

<c:if test="${param.username = = 'admin' }">

ADMIN 您好!! //body 部分

</c:if>

如果名称等于admin,则会显示"ADMIN您好!! "的动作,如果相反,则不会执行<c:if>的body

部分,所以不会显示"ADMIN 您好!! //body 部分"。另外<c:if>的本体内容除了能放纯文字,还可以

放任何JSP 程序代码(Scriptlet)、JSP 标签或者HTML 码。

除了test 属性之外,<c:if>还有另外两个属性var和scope。当我们执行<c:if>的时候,可以

将这次判断后的结果存放到属性var 里;scope 则是设定var 的属性范围。哪些情况才会用到var

和scope 这两个属性呢?例如:当表达式过长时,我们会希望拆开处理,__________或是之后还须使用此结果

时,也可以用它先将结果暂时保留,以便日后使用。

范例

笔者写了一个简单的范例,名称为Core_if.jsp。

■ Core_if.jsp

<%@ page contentType="text/html;charset=GB2312 " %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<head>

<title>CH7 - Core_if.jsp</title>

</head>

<body>

<h2><c:out value="<c:if> 的用法" /></h2>

<c:if test="${param.username == 'Admin'}" var="condition" scope="page">

您好Admin 先生

</c:if></br>

执行结果为: ${condition}

</body>

</html>

笔者在判断用户送来的参数时,如果username 的值等于Admin 时,则会将condition 设为true

并存放于pageScope中,否则存放于condition中,最后再显示结果。因为JSTL会自动找寻condition

所存在的属性范围,因此只须使用${condition},而不用 ${pageScope.condition}。Core_if.jsp

的执行结果如图7-7。

注意

执行本范例时,请在Core_if.jsp 后加上?username=Admin。

图 7-7 Core_if.jsp的执行结果

● <c:choose>

<c:choose>本身只当做 <c:when> 和 <c:otherwise> 的父标签。

语法

<c:choose>

本体内容( <when> 和 <otherwise> )

</c:choose>

属性

限制

<c:choose>的本体内容只能有:

·空白

·1 或多个 <c:when>

·0 或多个 <c:otherwise>

说明

若使用<c:when>和<c:otherwise>来做流程控制时,两者都必须为<c:choose>的子标签,即:

<c:choose>

<c:when>

</c:when>

<c:otherwise>

</c:otherwise>

</c:choose>

● <c:when>

<c:when> 的用途就和我们一般在程序中用的when 一样。

语法

<c:when test="testCondition" >

本体内容

</c:when>

属性

名称 说明EL 类型必须默认值

test

如果表达式的结果为true,则执行本体内容,false

则相反

Y boolean 是 无

限制

☆ <c:when>必须在<c:choose>和</c:choose>之间

☆ 在同一个<c:choose>中时,<c:when>必须在<c:otherwise>之前

说明

<c:when>必须有test 属性,当test 中的表达式结果为true 时,则会执行本体内容;如果为

false 时,则不会执行。

● <c:otherwise>

在同一个 <c:choose> 中,当所有 <c:when> 的条件都没有成立时,则执行<c:otherwise> 的

本体内容。

语法

<c:otherwise>

本体内容

</c:otherwise>

属性

限制

·<c:otherwise> 必须在 <c:choose> 和 </c:choose>之间

·在同一个 <c:choose> 中时,<c:otherwise> 必须为最后一个标签

说明

在同一个<c:choose> 中,假若所有<c:when> 的test 属性都不为true 时,则执行

<c:otherwise> 的本体内容。

范例

笔者举一个典型的 <c:choose>、<c:when>和<c:otherwise>范例:

<c:choose>

<c:when test="${condition1}">

condition1 为true

</c:when>

<c:when test="${ condition2}">

condition2 为true

</c:when>

<c:otherwise>

condition1 和condition2 都为false

</c:otherwise>

</c:choose>

范例说明:当condition1 为true 时,会显示“condition1为true”;当condition1为false

且condition2为true 时,会显示“condition2为true”,如果两者都为false,则会显示__________“condition1

和condition2 都为false”。

注意

假若 condition1 和condition2 两者都为true 时,此时只会显示"condition1 为true",这是因为

在同一个<c:choose>下,当有好几个<c:when>都符合条件时,只能有一个<c:when>成立。

7-2-3 迭代操作

迭代(Iterate)操作主要包含两个标签:<c:forEach>和<c:forTokens>,笔者依此顺序依次说明

这两个标签的使用。

● <c:forEach>

<c:forEach> 为循环控制,它可以将集合(Collection)中的成员循序浏览一遍。运作方式为当

条件符合时,就会持续重复执行<c:forEach>的本体内容。

语法

语法1:迭代一集合对象之所有成员

<c:forEach [var="varName"] items="collection" [varStatus="varStatusName"] [begin="begin"]

[end="end"] [step="step"]>

本体内容

< /c:forEach>

语法2:迭代指定的次数

<c:forEach [var="varName"] [varStatus="varStatusName"] begin="begin" end="end"

[step="step"]>

本体内容

</c:forEach>

属性

名称 说明EL 类型必须默认值

var 用来存放现在指到的成员N String 否无

items 被迭代的集合对象Y

Arrays

Collection

Iterator

Enumeration

Map

String

否无

varStatus 用来存放现在指到的相关成员信息N String 否无

begin 开始的位置Y int 否0

end 结束的位置Y int 否最后一个成员

step 每次迭代的间隔数Y int 否1

限制

·假若有begin 属性时,begin 必须大于等于 0

·假若有end 属性时,必须大于begin

·假若有step 属性时,step 必须大于等于0

Null 和错误处理

·假若items 为null 时,则表示为一空的集合对象

·假若begin 大于或等于items 时,则迭代不运算

说明

如果要循序浏览一个集合对象,并将它的内容显示出来,就必须有items 属性。

范例

下面的范例 Core_forEach.jsp 是将数组中的成员一个个显示出来的:

■ Core_forEach.jsp

<%@ page contentType="text/html;charset=GB2312 " %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_forEach.jsp</title>

</head>

<body>

<h2><c:out value="<c:forEach> 的用法" /></h2>

<%

String atts[] = new String [5];

atts[0]="hello";

atts[1]="this";

atts[2]="is";

atts[3]="a";

atts[4]="pen";

request.setAttribute("atts", atts);

%>

<c:forEach items="${atts}" var="item" >

${item}</br>

</c:forEach>

</body>

</html>

在上述范例中,笔者先产生一个字符串数组,然后将此数组atts 储存至Request 的属性范围中,

再用<c:forEach>将它循序浏览一遍。这里items表示被浏览的集合对象,var用来存放指定的集合

对象中成员,最后使用<c:out>将item 的内容显示出来,执行结果如图7-8 所示。

图 7-8 Core_forEach.jsp的执行结果

注意

varName 的范围只存在<c:forEach>的本体中,如果超出了本体,则不能再取得varName 的值。上个

例子中,若${item} 是在</c:forEach>之后执行时,如:

<c:forEach items="${atts}" var="item" >

</c:forEach>

${item}</br>

${item}则不会显示item 的内容。

<c:forEach>除了支持数组之外,还有标准J2SE 的集合类型,例如:ArrayList、List、

LinkedList、Vector、Stack和Set 等等;另外还包括java.util.Map 类的对象,例如:HashMap、

Hashtable、Properties、Provider 和Attributes。

<c:forEach>还有begin、end 和step 这三种属性:begin主要用来设定在集合对象中开始的位

置(注意:第一个位置为0);end 用来设定结束的位置;而step 则是用来设定现在指到的成员和下

一个将被指到成员之间的间隔。我们将之前的范例改成如下:

■ Core_forEach1.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_forEach1.jsp</title>

</head>

<body>

<h2><c:out value="<c:forEach> begin、end 和step 的用法" /></h2>

<%

String atts[] = new String [5];

atts[0]="hello";

atts[1]="this";

atts[2]="is";

atts[3]="a";

atts[4]="pen";

request.setAttribute("atts", atts);

%>

<c:forEach items="${atts}" var="item" begin="1" end="4" step="2" >

${item}</br>

</c:forEach>

</body>

</html>

<c:forEach>中指定的集合对象atts 将会从第2 个成员开始到第5 个成员,并且每执行一次循

环都会间隔一个成员浏览。因此结果是只显示atts[1]和atts[3]的内容,如图7-9 所示。

图 7-9 Core_forEach1.jsp的执行结果

为了方便详细介绍begin、end 和step 的不同设定下所产生的结果,笔者将上面的范例改成如

下:

<%

int atts[] = {1,2,3,4,5,6,7,8,9,10};

request.setAttribute("atts", atts);

%>

<c:forEach items="${atts}" var="item" begin="0" end="9" step="1" >

${item}</br>

</c:forEach>

这里笔者改变begin、end 和step 的值时,在网页上输出结果的变化如表7-4。

表 7-4

begin end step 结果

- - - 1 2 3 4 5 6 7 8 9 10

5 - - 6 7 8 9 10

- 5 - 1 2 3 4 5 6

- - 5 1 6

5 5 - 6

5 5 5 6

0 8 2 1 3 5 7 9

0 8 3 1 4 7

0 8 4 1 5 9

15 20 - 无

20 8 - 空白结果

0 20 – 1 2 3 4 5 6 7 8 9 10

从表7-4 中可以发现:

(1) 当begin 超过end 时将会产生空的结果;

(2) 当begin 虽然小于end 的值,但是当两者都大过容器的大小时,将不会输出任何东西;

(3) 最后如果只有end 的值超过集合对象的大小,则输出就和没有设定end 的情况相同;

(4) <c:forEach>并不只是用来浏览集合对象而已,读者可以从表7-4 中发现,items并不是一

定要有的属性,但是当没有使用items 属性时,就一定要使用begin 和end 这两个属性。下面

为一个简单的范例:

■ Core_forEach2.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_forEach2.jsp</title>

</head>

<body>

<h2><c:out value="<c:forEach> 循环" /></h2>

<c:forEach begin="1" end="10" var="item" >

${item}</br>

</c:forEach>

</body>

</html>

上述范例中,我们并没有执行浏览集合对象,只是设定begin 和end 属性的值,这样它就

变成一个普通的循环。此范例是将循环设定为:从1 开始__________跑到10,总共会重复循环10 次,并

且将数字放到item 的属性当中。Core_forEach2.jsp 的执行结果如图7-10 所示。

图 7-10 Core_forEach2.jsp 的执行结果

当然它也可以搭配step 使用,如果将step 设定为2,结果如图7-11 所示。

图 7-11 当step 设定为2 时的结果

另外,<c:forEach>还提供varStatus 属性,主要用来存放现在指到之成员的相关信息。例如:

我们写成varStatus="s",那么将会把信息存放在名称为s 的属性当中。varStatus属性还有另外四

个属性:index、count、first 和last,它们各自代表的意义如表7-5:

表 7-5

属性类型意义

index number 现在指到成员的索引

count number 总共指到成员的总数

first boolean 现在指到的成员是否为第一个成员

last boolean 现在指到的成员是否为最后一个成员

我们可以使用varStatus 属性取得循环正在浏览之成员的信息,下面为一个简单的范例:

■ Core_forEach3.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_forEach3.jsp</title>

</head>

<body>

<h2><c:out value="<c:forEach> varStatus 的四种属性" /></h2>

<%

String atts[] = new String [5];

atts[0]="hello";

atts[1]="this";

atts[2]="is";

atts[3]="a";

atts[4]="pen";

request.setAttribute("atts", atts);

%>

<c:forEach items="${atts}" var="item"varStatus="s">

<h2><c:out value="${item}"/>的四种属性:</h2>

index:${s.index}</br>

count:${s.count}</br>

first:${s.first}</br>

last:${s.last}</br>

</c:forEach>

</body>

</html>

执行结果如图7-12 所示。

图 7-12 Core_forEach3.jsp 的执行结果

● <c:forTokens>

<c:forTokens> 用来浏览一字符串中所有的成员,其成员是由定义符号(delimiters)所分隔的。

语法

<c:forTokens items="stringOfTokens" delims="delimiters" [var="varName"]

[varStatus="varStatusName"] [begin="begin"] [end="end"] [step="step"]>

本体内容

</c:forTokens>

属性

名称 说明EL 类型必须默认值

var 用来存放现在指到的成员N String 否无

items 被迭代的字符串Y String 是无

delims 定义用来分割字符串的字符N String 是无

varStatus 用来存放现在指到的相关成员信息N String 否无

begin 开始的位置Y int 否0

end 结束的位置Y int 否最后一个成员

step 每次迭代的间隔数Y int 否1

限制

·假若有begin 属性时,begin 必须大于等于 0

·假若有end 属性时,必须大于begin

·假若有step 属性时,step 必须大于等于0

Null 和错误处理

·假若items 为null 时,则表示为一空的集合对象

·假若begin 大于或等于items 的大小时,则迭代不运算

说明

<c:forTokens>的begin、end、step、var 和varStatus 用法都和<c:forEach>一样,因此,笔

者在这里就只介绍items 和delims 两个属性:items 的内容必须为字符串;而delims 是用来分割

items 中定义的字符串之字符。

范例

下面为一个典型的<c:forTokens>的范例:

<c:forTokens items="A,B,C,D,E" delims="," var="item" >

${item}

</c:forTokens>

上面范例执行后,将会在网页中输出ABCDE。它会把符号“,”当做分割的标记,拆成5 个部分,

也就是执行循环5 次,但是并没有将A,B,C,D,E 中的“,”显示出来。items 也可以放入EL 的表达

式,如下:

<%

String phoneNumber = "123-456-7899";

request.setAttribute("userPhone", phoneNumber);

%>

<c:forTokens items="${userPhone}" delims="-" var="item" >

${item}

</c:forTokens>

这个范例将会在网页上打印1234567899,也就是把123-456-7899以“-”当做分割标记,将字

符串拆为3 份,每执行一次循环就将浏览的部分放到名称为item 的属性当中。delims 不只指定一

种字符来分割字符串,它还可以一次设定多个分割字符串用的字符。如下面这个范例:

<c:forTokens items="A,B;C-D,E" delims=",;-" var="item" >

${item}

</c:forTokens>

此范例会在网页输出ABCDE,也就是说,delims 可以一次设定所有想当做分割字符串用的字符。

其实用<c:forEach>也能做到分割字符串,写法如下:

<c:forEach items="A,B,C,D,E" var="item" >

${item}

</c:forEach>

上述范例同样也会在网页输出ABCDE。<c:forEach>并没有delims这个属性,因此<c:forEach>

无法设定分割字符串用的字符,而<c:forEach>分割字符串用的字符只有“,”,这和使用

<c:forTokens>,delims 属性设为“,”的结果相同。所以如果使用<c:forTokens>来分割字符串,

功能和弹性上会比使用<c:forEach>来得较大。

7-2-4 URL 操作

JSTL 包含三个与URL 操作有关的标签,它们分别为:<c:import>、<c:redirect>和<c:url>。

它们主要的功能是:用来将其他文件的内容包含起来、网页的导向,还有url 的产生。笔者将依序

介绍这三个标签。

● <c:import>

<c:import> 可以把其他静态或动态文件包含至本身JSP 网页。它和JSP Action 的

<jsp:include>最大的差别在于:<jsp:include>只能包含和自己同一个web application下的文件;

而<c:import>除了能包含和自己同一个web application 的文件外,亦可以包含不同web

application 或者是其他网站的文件。

语法

语法1:

<c:import url="url" [context="context"] [var="varName"]

[scope="{page|request|session|application}"] [charEncoding="charEncoding"]>

本体内容

</c:import>

语法2:

<c:import url="url" [context="context"]

varReader="varReaderName" [charEncoding="charEncoding"]>

本体内容

</c:import>

属性

名称 说明EL 类型必须默认值

url 一文件被包含的地址Y String 是 无

context

相同Container 下,其他web站台必须以“/”

开头

Y String 否 无

var 储存被包含的文件的内容(以String类型存入) N String 否 无

scope var 变量的JSP 范围N String 否 Page

charEncoding 被包含文件之内容的编码格式Y String 否 无

varReader 储存被包含的文件的内容(以Reader类型存入) N String 否 无

Null 和错误处理

·假若url 为null 或空时,会产生JspException

说明

首先<c:import>中必须要有url 属性,它是用来设定被包含网页的地址。它可以为绝对地址或

是相对地址,使用绝对地址的写法如下:

<c:import url="http://java.sun.com" />

<c:import>就会把http://java.sun.com 的内容加到网页中。

另外<c:import>也支持FTP 协议,假设现在有一个FTP站台,地址为ftp.javaworld.com.tw,

它里面有一个文件data.txt,那么可以写成如下方式将其内容显示出来:

<c:import url="ftp://ftp.cse.yzu.edu.tw/data.txt" />

如果是使用相对地址,假设存在一个文件名为Hello.jsp,它和使用<c:import>的网页存在于

同一个webapps 的文件夹时,<c:import>的写法如下:

<c:import url="Hello.jsp" />

如果以“/”开头,那么就表示跳到web 站台的根目录下,以Tomcat 为例,即webapps 目录。

假设一个文件为hello.txt,存在于webapps/examples/images 里,而context 为examples,可以

写成以下方式将hello.txt 文件包含进我们的JSP 页面之中:

<c:import url="images/hello.txt" />

接下来如果要包含在同一个服务器上,但并非同一个web 站台的文件时,就必须加上context

属性。假设此服务器上另外还有一个web 站台,名为others,others 站台底下有一个文件夹为jsp,

且里面有index.html 这个文件,那么就可以写成如下方式将此文件包含进来:

<c:import url="/jsp/index.html" context="/others" />

注意

被包含文件的web 站台必须在server.xml中定义过,且<Context>的crossContext属性值必须

为true,这样一来,others 目录下的文件才可以被其他 web 站台调用。

server.xml 的设定范例如下:

: <Context path="/others" docBase="others" debug="0"

reloadable="true" crossContext="true"/>

除此之外,<c:import>也提供var 和scope 属性。当var 属性存在时,虽然同样会把其他文件

的内容包含进来,但是它并不会输出至网页上,而是以String 的类型储存至varName中。scope则

是设定varName 的范围。储存之后的数据,我们在需要用时,可以将它取出来,代码如下:

<c:import url="/images/hello.txt" var="s" scope="session" />

我们可以把常重复使用的商标、欢迎语句或者是版权声明,用此方法储存起来,想输出在网页

上时,再把它导入进来。假若想要改变文件内容时,可以只改变被包含的文件,不用修改其他网页。

另外,可以在<c:import>的本体内容中使用<c:param>,它的功用主要是:可以将参数传递给被

包含的文件,它有两个属性name 和value,如表7-6 所示:

表 7-6

名称说明EL 类型必须默认值

name 参数名称Y String 是无

value 参数的值Y String 否本体内容

这两个属性都可以使用EL,所以我们写成如下形式:

<c:import url="http://java.sun.com" >

<c:param name="test" value="1234" />

</c:import>

这样的做法等于是包含一个文件,并且所指定的网址会变成如下:

http://java.sun.com?test=1234

范例

下 面为一用到<c:import> 、<c:param> 及属性范围的范例, Core_import.jsp 和

Core_imported.jsp:

■ Core_import.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_import.jsp</title>

</head>

<body>

<h2><c:out value="<c:import> 的用法" /></h2>

<c:set var="input1" value="使用属性范围传到Core_imported.jsp 中" scope="request"/>包含

core_imported.jsp 中<hr/>

<c:import url="Core_imported.jsp"charEncoding="GB2312" >

<c:param name="input2" value="使用<c:param>传到Core_imported.jsp 中"/>

</c:import><hr/>

${output1}

</body>

</html>

程序中,笔者分别使用<c:set>和<c:param>来传递参数。

■ Core_imported.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<html>

<head>

<title>CH7 - Core_imported.jsp</title>

</head>

<body>

<fmt:requestEncoding value="GB2312" />

<c:set var="output1" value="使用属性范围传到Core_import.jsp 中" scope="request"/>

${input1}</br>

<c:out value="${param.input2}" escapeXml="true" />

</body>

</html>

Core_imported.jsp 是被包含的文件,它会把从Core_import.jsp 传来的参数分别输出到页面

上,必须注意的是input1 参数是使用属性范围来传递的,因此可以直接用${input1}来得到参数,

而input2 则必须使用${param.input2}来得到参数。

此外,笔者还使用<c:set>来传递值给Core_import.jsp,这就是<c:param>无法做到的动作,

<c:param>只能从包含端抛给被包含端,但是在属性范围中,可以让包含端也能得到被包含端传来的

数据。Core_import.jsp 的执行结果如图7-13 所示:

图 7-13 Core_import.jsp 的执行结果

● <c:url>

<c:url>主要用来产生一个URL。

语法

语法1:没有本体内容

<c:url value="value" [context="context"] [var="varName"]

[scope="{page|request|session|application}"] />

语法2:本体内容代表查询字符串(Query String)参数

<c:url value="value" [context="context"] [var="varName"]

[scope="{page|request|session|application}"] >

<c:param> 标签

</c:url>

属性

名称 说明EL 类型必须默认值

value 执行的URL Y String 是无

context 相同Container 下,其他web 站台必须以“/”开头Y String 否无

var 储存被包含文件的内容(以String 类型存入) N String 否无

scope var 变量的JSP 范围N String 否Page

说明

在这里笔者直接使用例子来说明。

<c:url value=" " >

<c:param name="param" value="value"/>

</c:url>

读者可以发现<c:url>也可以搭配<c:param>使用,上面执行结果将会产生一个网址为

?param=value,我们更可以搭配HTML 的<a>使用,如下:

<a href="

<c:url value=" " >

<c:param name="param" value="value"/>

</c:url>">Java 爱好者</a>

另外<c:url>还有三个属性,分别为context、var 和scope。context属性和之前的<c:import>

相同,可以用来产生一个其他web站台的网址。如果<c:url>有var属性时,则网址会被存到varName

中,而不会直接输出网址。

哪些状况下才会去使用<c:url>?例如:当我们须动态产生网址时,有可能传递的参数不固定,

或者是需要一个网址能连至同服务器的其他web站台之文件,而且<c:url>更可以将产生的网址储存

起来重复使用。另外,在以前我们必须使用相对地址或是绝对地址去取得需要的图文件或文件,现

在我们可以直接利用<c:url>从web 站台的角度来设定需要的图文件或文件的地址,如下:

<img src="<c:url value="/images/code.gif" />" />

如此就会自动产生连到image文件夹里的code.gif的地址,不再须耗费精神计算相对地址,并

且当网域名称改变时,也不用再改变绝对地址。

● <c:redirect>

<c:redirect>可以将客户端的请求从一个JSP 网页导向到其他文件。

语法

语法1:没有本体内容

<c:redirect url="url" [context="context"] />

语法2:本体内容代表查询字符串(Query String)参数

<c:redirect url="url" [context="context"] > <c:param> </c:redirect >

属性

名称 说明EL 类型必须默认值

url 导向的目标地址Y String 是 无

context 相同Container 下,其他web 站台必须以“/”开头Y String 否 无

说明

url 就是设定要被导向到的目标地址,它可以是相对或绝对地址。例如:我们写成如下:

<c:redirect url="" />

那么网页将会自动导向到。另外,我们也可以加上context这个属性,

用来导向至其他 web站台上的文件,例如:导向到/others下的/jsp/index.html时,写法如下:

<c:redirect url="/jsp/index.html" context="/others" />

<c:redirect> 的功能不止可以导向网页,同样它还可以传递参数给目标文件。在这里我们同样

使用<c:param>来设定参数名称和内容。

范例

■ Core_redirect.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH7 - Core_redirect.jsp</title>

</head>

<body>

<h2><c:out value="<c:redirect> 的用法" /></h2>

<c:redirect url="http://java.sun.com">

<c:param name="param" value="value"/>

</c:redirect>

<c:out value="不会执行喔!!!" />

</body>

</html>

jsp Context 有关的隐含对象

5-4 与Context 有关的隐含对象(1)

在本节中,我们要介绍session、application、pageContext 这三个对象。session对象提供一

些机制,让服务器能个别辨认用户。当程序在执行时,application对象能提供服务端(Server-Side)

的Context,说明哪些资源是可利用的,哪些信息是可获取的。pageContext对象提供存取所有在此

网页中可被利用的隐含对象,并且可以管理它们的属性。

session 对象

session 对象表示目前个别用户的会话(session)状况,用此项机制可以轻易识别每一个用户,

然后针对每一个别用户的要求,给予正确的响应。例如:购物车最常使用session 的概念,当用户

把物品放入购物车时,他不须重复做身份确认的动作(如:Login),就能把物品放入用户的购物车。

服务器利用session 对象,就能确认用户是谁,把它的物品放在属于用户的购物车,而不会将物

品放错到别人的购物车。除了购物车之外,session 对象也通常用来__________做追踪用户的功能,这在第十

章有更加详细的说明。

session 对象实现javax.servlet.http.HttpSession 接口,表5-12 列出了一些常用的方法。

表 5-12 javax.servlet.http.HttpSession 接口所提供的方法

方 法 说 明

long getCreationTime()

取得session产生的时间,单位是毫秒,由1970

年1 月1 日零时算起

String getId() 取得session 的ID

续表

方 法 说 明

long getLastAccessedTime()

取得用户最后通过这个session送出请求的时

间,单位是毫秒,由1970 年1 月1 日零时算

long getMaxInactiveInterval()

取得最大session不活动的时间,若超过这时

间,session 将会失效,时间单位为秒

void invalidate()

取消session 对象,并将对象存放的内容完全

抛弃

boolean isNew()

判断session 是否为"新"的,所谓"新"的

session,表示session 已由服务器产生,但

是client 尚未使用

void setMaxInactiveInterval(int

interval)

设定最大session不活动的时间,若超过这时

间,session 将会失效,时间单位为秒

session 对象也可以储存或取得用户相关的数据,例如:用户的名称、用户所订购的物品、用户的

权限,等等,这些要看我们的程序如何去设计。例如:我要设定某些网页必须要求用户先做登录(Login)

的动作,确定是合法的用户时,才允许读取网页内容,否则把网页重新转向到登录的网页上。

Login.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Login.jsp</title>

</head>

<body>

<h2>javax.servlet.http.HttpSession - session 对象</h2>

<form action=Login.jsp method="POST" >

Login Name: <input type="text" name="Name"><br>

Login Password: <input type="text" name="Password" ><br>

<input type="submit" value="Send"><br>

<form>

<% if (request.getParameter("Name") != null &&

request.getParameter("Password") != null) {

String Name = request.getParameter("Name");

String Password = request.getParameter("Password");

if (Name.equals("mike") && Password.equals("1234")) {

session.setAttribute("Login", "OK");

response.sendRedirect("Member.jsp");

}

else {

out.println("登录错误,请输入正确名称");

}

}

%>

</body>

</html>

在Login.jsp 的程序中,我要求用户分别输入名称和密码,如果输入的名称和密码分别为mike

和1234 时,就把名称为Login、其值为OK 的属性,加入到session 对象当中,然后进入Member.jsp

网页,如图5-9;若输入错误时,就显示出“登录错误,请输入正确名称”。不允许登录至Member.jsp,

如图5-10 所示。

图 5-9 登录成功,顺利进入Member.jsp

图 5-10 登录失败画面

这时大家一定会想,如果我不通过Login.jsp网页,直接执行Member.jsp,那不就能够进去___________了。

没错,因此我们还要在Member.jsp 中加入一段程序代码,来确认用户是否有先通过Login.jsp的身

份确认,然后再到Member.jsp 中。Member.jsp 程序如下:

Member.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Member.jsp</title>

</head>

<body>

<h2>javax.servlet.http.HttpSession - session 对象</h2>

<%

String Login = (String)session.getAttribute("Login");

if (Login != null && Login.equals("OK")) {

out.println("欢迎进入");

session.invalidate();

}

else {

out.println("请先登录,谢谢") ;

out.println("<br>经过五秒之后,网页会自动返回Login.jsp");

response.setHeader("Refresh","5;URL=Login.jsp");

}

%>

</body>

</html>

在Member.jsp中我利用session.getAttribute("Login"),如果用户是通过Login.jsp网页进入,

并且顺利通过身份确认取得Login=OK,到Member.jsp 再做确认时,也能顺利通过;否则,如果直接

连接到Member.jsp时,Login的值会等于NULL,则程序经过五秒后,重新加载Login.jsp,要求用户

先行登录。若直接执行Member.jsp,而没有经过登录手续时,就会发现如图5-11。

图 5-11 直接执行Member.jsp,并未经过登录手续

最后要提醒读者一点,session对象不像其他的隐含对象,可以在任何的JSP 网页中使用,如果

在JSP 网页中,page 指令的属性session 设为false 时,使用session 对象就会产生编译错误

(javax.servlet.ServletException: Compilation error occurred ),如下所示:

<%@ page session="false" %>

<%

String Login = (String)session.getAttribute("Login");

….

….

%>

在本书“第十章:Session Tracking”中对session 有更多更详细的介绍。

application 对象

application 对象实现javax.servlet.ServletContext 接口,它主要功用在于取得或更改

Servlet 的设定。下面程序用来说明JSP 网页被编译成Servlet 时,application 对象是如何初

始化的:

pageContext = JspxFactory.getPageContext ( this , request , response ,

"errorpage.jsp" , true , 8192 , true );

application = pageContext.getServletContext( );

你可以看到产生的Servlet 取得了目前的ServletContext,并且将它储存在application 对象

当中。application 对象拥有Application 的范围,意思就是说它的生命周期是由服务器产生开始

至服务器关机为止。表5-13、表5-14、表5-15 列出了其相关方法:

表 5-13 javax.servlet.ServletContext 接口容器相关信息的方法

方 法 说 明

int getMajorVersion( ) 取得 Container 主要的Servlet API 版本,如:2

int getMinorVersion( ) 取得 Container 次要的Servlet API 版本,如:4

String getServerInfo( ) 取得 Container 的名称和版本

<%= application.getMajorVersion() %><br>

<%= application.getMinorVersion() %><br>

<%= application.getServerInfo() %><br>

上述的getMajorVersion( )和getMinorVersion( )是取得Servlet Engine 的版本信息,假如

想要取得JSP 容器的版本信息,则可能就要使用到下面这段程序代码:

GetJspVersion.jsp

<%@ page import="javax.servlet.jsp.JspFactory"

contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - GetJspVersion.jsp</title>

</head>

<body>

<h2>取得 JSP Container 版本 - JspFactory 对象</h2>

<%

JspFactory factory = JspFactory.getDefaultFactory();

out.println("JSP v 2.0"+

factory.getEngineInfo().getSpecificationVersion());

%>

</body>

</html>

执行结果如图5-12 所示。

表 5-14 javax.servlet.ServletContext 接口有关服务端的路径和文件的方法

方 法 说 明

String getMimeType(String file) 取得指定文件的MIME 类型

ServletContext getContext(String

uripath)

取得指定Local URL 的Application

context

String getRealPath(String path) 取得本地端path 的绝对路径

范例:

<%= application.getMimeType("MyFile") %>

<%= application.getContext("/") %>

<%= application.getRealPath("/") %>

表 5-15 javax.servlet.ServletContext 接口有关信息记录的方法

方 法 说 明

void log(String message) 将信息写入log 文件中

void log(String message, Throwable

throwable)

将stack trace 所产生的异常信息写入

log文件中

application对象最常被使用在存取环境的信息,因为环境的信息通常都储存在ServletContext中,

所以常利用application对象来存取ServletContext中的信息。

图 5-12 GetJspVesion.jsp 的执行结果

pageContext 对象

pageContext对象能够存取其他隐含对象。当隐含对象本身也支持属性时,pageContext对象也

提供存取那些属性的方法。不过在使用下列方法时,需要指定范围的参数:

Object getAttribute(String name, int scope)

Enumeration getAttributeNamesInScope(int scope)

void removeAttribute(String name, int scope)

void setAttribute(String name, Object value, int scope)

范围参数有四个常数,分别代表四种范围:PAGE_SCOPE 代表Page范围,REQUEST_SCOPE代表Request

范围,SESSION_SCOPE 代表Session 范围,最后APPLICATION_SCOPE 代表Application 范围(见表

5-16、表5-17、表5-18)。

表 5-16 javax.servlet.jsp.PageContext 类取得其他隐含对象的方法

方 法 说 明

Exception getException( )

回传目前网页的异常,不过此网页要为error page,

例如:exception 隐含对象

JspWriter getOut( ) 回传目前网页的输出流,例如:out 隐含对象

Object getPage( )

回传目前网页的Servlet 实体(instance),例如:page

隐含对象

ServletRequest

getRequest( )

回传目前网页的请求,例如:request 隐含对象

ServletResponse

getResponse( )

回传目前网页的响应,例如:response 隐含对象

ServletConfig

getServletConfig( )

回传目前此网页的ServletConfig 对象,例如:config

隐含对象

ServletContext

getServletContext( )

回传目前此网页的执行环境(context),例如:

application隐含对象

HttpSession getSession( )

回传和目前网页有联系的会话(session),例如:

session 隐含对象

表 5-17 javax.servlet.jsp.PageContext 类所提供取得属性的方法

方 法 说 明

Object getAttribute(String name, int

scope)

回传name 属性,范围为scope 的

属性对象, 回传类型为

java.lang.Object

Enumeration getAttributeNamesInScope(int

scope)

回传所有属性范围为scope 的属

性名称,回传类型为Enumeration

int getAttributesScope(String name) 回传属性名称为name 的属性范围

void removeAttribute(String name) 移除属性名称为name 的属性对______________象

void removeAttribute(String name, int

scope)

移除属性名称为name,范围为

scope 的属性对象

void setAttribute(String name, Object

value, int scope)

指定属性对象的名称为name、值

为value、范围为scope

Object findAttribute(String name)

寻找在所有范围中属性名称为

name 的属性对象

表 5-18 javax.servlet.jsp.PageContext 类所提供范围的变量

常 数 说 明

PAGE_SCOPE 存入pageContext 对象的属性范围

REQUEST_SCOPE 存入request 对象的属性范围

SESSION_SCOPE 存入session 对象的属性范围

APPLICATION_SCOPE 存入application 对象的属性范围

接下来示范一个小程序,让读者能够更加明白。

PageContext.jsp

<%@ page import="java.util.Enumeration"

contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - PageContext.jsp</title>

</head>

<body>

<h2>javax.servlet.jsp.PageContext - pageContext </h2>

<%

Enumeration enum =

pageContext.getAttributeNamesInScope(PageContext.APPLICATION_SCOPE );

while (enum.hasMoreElements())

{

out.println("application attribute:"+enum.nextElement( )

+"<br>"); }

%>

</body>

</html>

PageContext.jsp 主要目的是:在这页当中,取得所有属性范围为Application 的属性名称,

然后再依序显示出来这些属性。

首先要记得导入 java.util.Enumeration。pageContext.getAttributeNamesInScope( )会回传

所有指定范围的属性名称,因此,我们产生Enumeration 对象enum,利用enum 来收集所有属性范

围为Application 的数据,然后再一一地取出打印出来。这里最重要的是让读者了解如何设定scope

的参数,因此下面这行代码:

PageContext.APPLICATION_SCOPE

是最主要的。有了这个范例程序之后,读者应该能够快速学会使用pageContext 对象所提供的

方法。

pageContext对象除了提供上述的方法之外,另外还有两种方法:forward (Sting Path)、include

(String Path),这两种方法的功能和之前提到的<jsp:forward>与<jsp:include>相似,因此在这也

不多加讨论。

5-5 与Error 有关的隐含对象

最后一类的隐含对象只有一个成员:exception 对象。当JSP 网页有错误时会产生异常,而

exception 对象就来针对这个异常做处理。

exception 对象

exception 对象和session 对象一样,并不是在每一个JSP 网页中都能够使用。若要使用

exception 对象时,必须在page 指令中设定。

<%@ page isErrorPage="true" %>

才能使用,不然在编译时会产生错误。

Exception.jsp

<%@ page contentType="text/html;charset=GB2312" isErrorPage="true" %>

<html>

<head>

<title>CH5 - Exception.jsp</title>

</head>

<body>

<h2> exception 对象</h2>

Exception:<%= exception %><br>

Message:<%= exception.getMessage() %><br>

Localized Message:<%= exception.getLocalizedMessage() %><br>

Stack Trace:<% exception.printStackTrace(new java.io.PrintWriter(out));

%><br>

</body>

</html>

一般error page 的程序代码和Exception.jsp 程序相似,它已经将所有该打印出来的错误信息

包括进来。在这段程序代码中使用了三个方法:getMessage( )、getLocalizedMessage( )、

printStackTrace(new java.io.PrintWriter(out)) ,其中printStackTrace( )的参数要为

PrintWriter 而不是JspWriter。

第六章 Expression Language

6-1 EL 简介

6-1 EL 简介

EL 全名为Expression Language,它原本是JSTL 1.0为方便存取数据所自定义的语言。当时EL

只能在JSTL 标签中使用,如下:

<c:out value="${ 3 + 7}">

程序执行结果为10。但是你却不能直接在JSP 网页中使用:

<p>Hi ! ${ username }</p>

到了JSP 2.0 之后,EL 已经正式纳入成为标准规范之一。因此,只要是支持Servlet 2.4 / JSP

2.0 的Container,就都可以在JSP 网页中直接使用EL 了。

除了JSP 2.0 建议使用EL 之外,JavaServer Faces( JSR-127 ) 也考虑将EL 纳入规范,由此

可知,EL 如今已经是一项成熟、标准的技术。

注意

假若您所用的Container 只支持Servlet 2.3/JSP 1.2,如:Tomcat 4.1.29,您就不能在

JSP 网页中直接使用EL,必须安装支持Servlet 2.4 / JSP 2.0 的Container。

6-2 EL 语法

EL 语法很简单,它最大的特点就是使用上很方便。接下来介绍EL 主要的语法结构:

${sessionScope.user.sex}

所有EL都是以${ 为起始、以} 为结尾的。上述EL范例的意思是:从Session的范围中,取得

用户的性别。假若依照之前JSP Scriptlet的写法如下:

User user = (User)session.getAttribute("user");

String sex = user.getSex( );

两者相比较之下,可以发现EL 的语法比传统JSP Scriptlet 更为方便、简洁。

6-2-1 .[ ] 运算符

EL 提供 . 和 [ ] 两种运算符来存取数据。下列两者所代表的意思是一样的:

${sessionScope.user.sex}

等于

${sessionScope.user["sex"]}

. 和 [ ] 也可以同时混合使用,如下:

${sessionScope.shoppingCart[0].price}

回传结果为shoppingCart中第一项物品的价格。

不过,以下两种情况,两者会有差异:

(1) 当要存取的属性名称中包含一些特殊字符,如. 或 – 等并非字母或数字的符号,就一定

要使用 [ ],例如:

${user.My-Name }

上述是不正确的方式,应当改为:

${user["My-Name"] }

(2) 我们来考虑下列情况:

${sessionScope.user[data]}

此时,data 是一个变量,假若data的值为"sex"时,那上述的例子等于${sessionScope.user.sex};

假若data 的值为"name"时,它就等于${sessionScope.user.name}。因此,如果要动态取值时,就可以

用上述的方法来做,但. 无法做到动态取值。

接下来,我们更详细地来讨论一些情况,首先假__________设有一个EL:

${expr-a[expr-b]}

(1) 当expr-a 的值为null时,它会回传null。

(2) 当expr-b的值为null时,它会回传null。

(3) 当 expr-a 的值为一Map 类型时:

● 假若 !value-a.containsKey(value-b)为真,则回传null。

● 否则回传value-a.get(value-b)。

(4) 当expr-a 的值为List 或array 类型时:

● 将 value-b 的值强制转型为int,假若不能转型为int 时,会产生error。

● 然后, 假若value-a.get(value-b) 或Array.get(value-a, value-b) 产生

ArrayIndexOutOfBoundsException 或IndexOutOfBoundsException 时,则回传null。

● 假若 value-a.get(value-b)或Array.get(value-a, value-b)产生其他的异常时,则会产生

error。

● 最后都没有任何异常产生时,回传value-a.get(value-b)或Array.get(value-a, value-b)。

(5) 当expr-a 的值为JavaBean 对象时:

● 将 value-b 的值强制转型为String。

● 假若 getter 产生异常时,则会产生error。若没有异常产生时,则回传getter 的结果。

6-2-2 EL 变量

EL 存取变量数据的方法很简单,例如:${username}。它的意思是取出某一范围中名称为username

的变量。因为我们并没有指定哪一个范围的username,所以它的默认值会先从Page 范围找,假如

找不到,再依序到Request、Session、Application范围。假如途中找到username,就直接回传,

不再继续找下去,但是假如全部的范围都没有找到时,就回传null(见表6-1):

表 6-1

属性范围在 EL 中的名称

Page PageScope

Request RequestScope

Session SessionScope

Application ApplicationScope

自动搜索顺序

我们也可以指定要取出哪一个范围的变量(见表6-2):

表 6-2

范例 说 明

${pageScope.username} 取出Page 范围的username 变量

${requestScope.username} 取出Request 范围的username 变量

${sessionScope.username} 取出Session 范围的username 变量

${applicationScope.username} 取出Application 范围的username 变量

其中,pageScope、requestScope、sessionScope和applicationScope都是EL 的隐含对象,由

它们的名称可以很容易猜出它们所代表的意思,例如:${sessionScope.username}是取出Session

范围的username 变量。这种写法是不是比之前JSP 的写法:

String username = (String) session.getAttribute("username");

容易、简洁许多。有关EL 隐含对象在6-3 节中有更详细的介绍。

6-2-3 自动转变类型

EL 除了提供方便存取变量的语法之外,它另外一个方便的功能就是:自动转变类型,我们来看

下面这个范例:

${param.count + 20}

假若窗体传来count的值为10时,那么上面的结果为30。之前没接触过JSP 的读者可能会认为

上面的例子是理所当然的,但是在JSP 1.2 之中不能这样做,原因是从窗体所传来的值,它们的类

型一律是String,所以当你接收之后,必须再将它转为其他类型,如:int、float 等等,然后才能

执行一些数学运算,下面是之前的做法:

String str_count = request.getParameter("count");

int count = Integer.parseInt(str_count);

count = count + 20;

接下来再详细说明EL类型转换的规则:

(1) 将A 转为String 类型

● 假若 A 为String 时:回传A

● 否则,当A 为null 时:回传 ""

● 否则,当A.toString( )产生异常时:错误!

● 否则,回传 A.toString( )

(2) 将A 转为Number 类型的N

● 假若 A 为null 或 "" 时:回传0

● 假若 A 为Character 时:将A 转为 new Short((short)a.charValue( ))

● 假若 A 为Boolean 时:错误!

● 假若 A 为Number 类型和N 一样时:回传A

● 假若 A 为Number 时:

·假若N 是BigInteger 时:

·假若A 为BigDecimal 时:回传 A.toBigInteger( )

·否则,回传 BigInteger.valueOf(A.longValue( ))

·假若N 是BigDecimal 时:

·假若A 为BigInteger 时:回传 A.toBigDecimal( )

·否则,回传 BigDecimal.valueOf(A.doubleValue( ))

·假若N 为Byte 时:回传 new Byte(A.byteValue( ))

·假若N 为Short 时:回传 new Short(A.shortValue( ))

·假若N 为Integer 时:回传 new Integer(A.intValue( ))

·假若N 为Long 时:回传 new Long(A.longValue( ))

·假若N 为Float 时:回传 new Float(A.floatValue( ))

·假若N 为Double 时:回传 new Double(A.doubleValue( ))

·否则,错误!

● 假若 A 为String 时:

·假若N 是BigDecimal 时:

·假若 new BigDecimal(A)产生异常时:错误!

·否则,回传 new BigDecimal(A)

·假若N 是BigInteger 时:

·假若 new BigInteger(A)产生异常时:错误!

·否则,回传 new BigInteger(A)

·假若N.valueOf(A)产生异常时:错误!

·否则,回传 N.valueOf(A)

● 否则,错误!

(3) 将A 转为Character 类型

● 假若 A 为null 或 "" 时:回传 (char)0

● 假若 A 为Character 时:回传 A

● 假若 A 为Boolean 时:错误!

● 假若 A 为Number 时:转换为Short 后,然后回传Character

● 假若 A 为String 时:回传 A.charAt(0)

● 否则,错误!

(4) 将A 转为Boolean 类型

● 假若 A 为null 或 "" 时:回传 false

● 否则,假若A 为Boolean 时:回传 A

● 否则,假若A为String,且Boolean.valueOf(A)没有产生异常时:回传Boolean.valueOf(A)

● 否则,错误!

6-2-4 EL 保留字

EL 的保留字如表6-3:

表 6-3

And eq gt true

Or ne le false

No lt ge null

instanceof empty div mod

所谓保留字的意思是指变量在命名时,应该避开上述的名字,以免程序编译时发生错误。

6-3 EL 隐含对象

笔者在“第五章:隐含对象(Implicit Object)”中,曾经介绍过9 个JSP 隐含对象,而EL本

身也有自己的隐含对象。EL 隐含对象总共有11 个(见表6-4):

表 6-4

隐含对象类 型 说 明

PageContext javax.servlet.ServletContext 表示此JSP 的PageContext

PageScope java.util.Map 取得Page范围的属性名称所对应的值

RequestScope java.util.Map

取得Request 范围的属性名称所对应

的值

sessionScope java.util.Map

取得Session 范围的属性名称所对应

的值

applicationScope java.util.Map

取得Application 范围的属性名称所

对应的值

param java.util.Map

如同

ServletRequest.getParameter(String

name)。回传String 类型的值

续表

隐含对象类 型 说 明

paramValues java.util.Map 如同

隐含对象类 型 说 明

ServletRequest.getParameterValues(String

name)。回传String []类型的值

header java.util.Map

如同ServletRequest.getHeader(String

name)。回传String 类型的值

headerValues java.util.Map

如同ServletRequest.getHeaders(String

name)。回传String []类型的值

cookie java.util.Map 如同HttpServletRequest.getCookies( )

initParam java.util.Map

如同

ServletContext.getInitParameter(String

name)。回传String 类型的值

这 11 个隐含对象(Implicit Object),笔者将它分成三类:

1. 与范围有关的隐含对象

applicationScope

sessionScope

requestScope

pageScope

2. 与输入有关的隐含对象

param

paramValues

3. 其他隐含对象

cookie

header

headerValues

initParam

pageContext

接下来笔者会依照上面的分类顺序,为读者介绍这些隐含对象。

6-3-1 属性(Attribute)与范围(Scope)

与范围有关的EL 隐含对象包含以下四个:pageScope、requestScope、sessionScope 和

applicationScope,它们基本上就和JSP的pageContext、request、session和application一样,

所以笔者在这里只稍略说明。不过必须注意的是,这四个隐含对象只能用来取得范围属性值,即JSP

中的getAttribute(String name),却不能取得其他相关信息,例如:JSP中的request对象除可以存

取属性之外,还可以取得用户的请求参数或表头信息等等。但是在EL中,它就只能单纯用来取得对应

范围的属性值,例如:我们要在session 中储存一个属性,它的名称为username,在JSP 中使用

session.getAttribute("username") 来取得username 的值, 但是在EL 中, 则是使用

${sessionScope.username}来取得其值的。接下来分别对这四个隐含对象做简短的说明:

● pageScope

范围和JSP 的Page 相同,也就是单单一页JSP Page 的范围(Scope)。

● requestScope

范围和JSP 的Request 相同,requestScope 的范围是指从一个JSP 网页请求到另一个JSP 网页

请求之间,随后此属性就会失效。

● sessionScope

范围和JSP Scope 中的session 相同,它的属性范围就是用户持续在服务器连接的时间。

● applicationScope

范围和JSP Scope 中的application 相同,它的属性范围是从服务器一开始执行服务,到服务

器关闭为止。

6-3-2 与输入有关的隐含对象

与输入有关的隐含对象有两个:param和paramValues,它们是EL中比较特别的隐含对象。一般

而言,我们在取得用户的请求参数时,可以利用下列方法:

request.getParameter(String name)

request.getParameterValues(String name)

在 EL中则可以使用param和paramValues两者来取得数据。

${param.name}

${paramValues.name}

这里param 的功能和request.getParameter(String name) 相同, 而paramValues 和

request.getParameterValues(String name)相同。如果用户填了一个表格,表格名称为username,则我们

就可以使用${param.username}来取得用户填入的值。

为了让读者更加了解param 和paramValues 隐含对象的使用,再来看下面这个范例。此范例共

有两个文件,分别为给用户输入值用的Param.html 和显示出用户所传之值的Param.jsp。

Param.html

<html>

<head>

<title>CH6 - Param.html</title>

</head>

<body>

<h2>EL 隐含对象 param、paramValues</h2>

<form method = "post" action = "Param.jsp">

<p>姓名:<input type="text" name="username" size="15" /></p>

<p>密码:<input type="password" name="password" size="15" /></p>

<p>性别:<input type="radio" name="sex" value="Male" checked/> 男

<input type="radio" name="sex" value="Female" /> 女</p>

<p>年龄:

<select name="old">

<option value="10">10 - 20</option>

<option value="20" selected>20 - 30</option>

<option value="30">30 - 40</option>

<option value="40">40 - 50</option>

</select>

</p>

<p>兴趣:

<input type="checkbox" name="habit" value="Reading"/>看书

<input type="checkbox" name="habit" value="Game"/>玩游戏

<input type="checkbox" name="habit" value="Travel"/>旅游

<input type="checkbox" name="habit" value="Music"/>听音乐

<input type="checkbox" name="habit" value="Tv"/>看电视

</p>

<p>

<input type="submit" value="传送"/>

<input type="reset" value="清除"/>

</p>

</form>

</body>

</html>

Param.html 的执行结果如图6-1 所示。当我们把窗体填好后按下传送钮,它将会把信息传送到

Param.jsp 做处理。

图 6-1 Param.html 的执行结果,并填入信息

接下来,Param.jsp 接收由Param.html 传来的信息,并且将它显示出来:

Param.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<html>

<head>

<title>CH6 - Param.jsp</title>

</head>

<body>

<h2>EL 隐含对象 param、paramValues</h2>

<fmt:requestEncoding value="GB2312" />

姓名: ${param.username}</br>

密码: ${param.password}</br>

性别: ${param.sex}</br>

年龄: ${param.old}</br>

兴趣: ${paramValues.habit[0]}

${paramValues.habit[1]}

</body>

</html>

由Param.html 窗体传过来的值,我们必须指定编码方式,才能够确保Param.jsp能够顺利接收中

文,传统的做法为:

<%

request.setCharacterEncoding("GB2312");

%>

假若是使用JSTL写法时,必须使用I18N 格式处理的标签库,如下:

<fmt:requestEncoding value="GB2312" />

Param.jsp主要使用EL的隐含对象param来接收数据。但是必须注意:假若要取得多重选择的复

选框的值时,必须使用paramValues,例如:使用paramValues 来取得“兴趣”的值,不过这里笔者

最多只显示两笔“兴趣”的值:

${param.username}

………

${paramValues.habit[0]}

${paramValues.habit[1]}

有关JSTL的使用,第七章有更加详细的说明。图6-2是Param.jsp的执行结果:

图 6-2 Param.jsp 的执行结果

6-3-3 其他隐含对象

介绍完上面六个隐含对象后,接下来将介绍最后五个隐含对象。

● cookie

所谓的cookie是一个小小的文本文件,它是以key、value的方式将Session Tracking的内容记录

在这个文本文件内,这个文本文件通常存在于浏览器的暂存区内。JSTL并没有提供设定cookie的动作,

因为这个动作通常都是后端开发者必须去做的事情,而不是交给前端的开发者。假若我们在cookie 中

设定一个名称为userCountry的值,那么可以使用${cookie.userCountry}来取得它。

● header 和headerValues

header 储存用户浏览器和服务端用来沟通的数据,当用户要求服务端的网页时,会送出一个记

载要求信息的标头文件,例如:用户浏览器的版本、用户计算机所设定的区域等其他相关数据。假

若要取得用户浏览器的版本,即${header["User-Agent"]}。另外在鲜少机会下,有可能同一标头名

称拥有不同的值,此时必须改为使用headerValues 来取得这些值。

注意

因为User-Agent 中包含“-”这个特殊字符,所以必须使用“[]”,而不能写成

$(header.User-Agent)。

● initParam

就像其他属性一样,我们可以自行设定web 站台的环境参数(Context),当我们想取得这些参数

时,可以使用initParam 隐含对象去取得它,例如:当我们在web.xml 中设定如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

<context-param>

<param-name>userid</param-name>

<param-value>mike</param-value>

</context-param>

</web-app>

那么我们就可以直接使用 ${initParam.userid}来取得名称为userid,其值为mike 的参数。下

面是之前的做法:

String userid = (String)application.getInitParameter("userid");

● pageContext

我们可以使用 ${pageContext}来取得其他有关用户要求或页面的详细信息。表6-5 列出了几个

比较常用的部分。

表 6-5

Expression 说明

${pageContext.request.queryString} 取得请求的参数字符串

${pageContext.request.requestURL} 取得请求的URL,但不包括请求之参数字符串

${pageContext.request.contextPath} 服务的web application 的名称

${pageContext.request.method} 取得HTTP 的方法(GET、POST)

${pageContext.request.protocol} 取得使用的协议(HTTP/1.1、HTTP/1.0)

${pageContext.request.remoteUser} 取得用户名称

${pageContext.request.remoteAddr } 取得用户的IP 地址

${pageContext.session.new}

判断session 是否为新的,所谓新的session,

表示刚由server 产生而client 尚未使用

${pageContext.session.id} 取得session 的ID

${pageContext.servletContext.serverInfo}取得主机端的服务信息

我们来看下面这个范例:pageContext.jsp,相信对读者来说能更加了解pageContext的用法。

pageContext.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH6 - pageContext.jsp</title>

</head>

<body>

<h2>EL 隐含对象 pageContext</h2>

\${pageContext.request.queryString}:${pageContext.request.queryString}</br>

\${pageContext.request.requestURL}:${pageContext.request.requestURL}</br>

\${pageContext.request.contextPath}:${pageContext.request.contextPath}</br>

\${pageContext.request.method}:${pageContext.request.method}</br>

\${pageContext.request.protocol}:${pageContext.request.protocol}</br>

\${pageContext.request.remoteUser}:${pageContext.request.remoteUser}</br>

\${pageContext.request.remoteAddr }:${pageContext.request.remoteAddr}</br>

\${pageContext.session.new}:${pageContext.session.new}</br>

\${pageContext.session.id}:${pageContext.session.id}</br>

</body>

</html>

pageContext.jsp 的执行结果如图6-3,执行时必须在pageContext.jsp 之后加上?test=1234,即

PageContext.jsp?test=1234,这样${pageContext.request.queryString}才会显示test=1234。

图 6-3 pageContext.jsp 的执行结果

注意

因为 ${} 在JSP 2.0 中是特殊字符,JSP容器会自动将它当做EL来执行,因此,假若要显

示 ${}时,必须在 $ 前加上 \ ,如:\${ XXXXX }

6-4 EL 算术运算符

EL 算术运算符主要有以下五个(见表6-6):

表 6-6

算术运算符说 明 范 例 结 果

+ 加${ 17 + 5 } 22

- 减 ${ 17 - 5 } 12

* 乘${ 17 * 5 } 85

/ 或 div 除${ 17 / 5 } 或 ${ 17 div 5 } 3

% 或 mod 余数${ 17 % 5 } 或 ${ 17 mod 5 } 2

接下来,我们依照下列几种情况,详细说明EL 算术运算符的规则:

(1) A {+ ,- , *} B

● 假若 A 和B 为null:回传 (Long)0

● 假若 A 或B 为BigDecimal 时,将另一个也转为BigDecimal,则:

·假若运算符为 + 时:回传 A.add(B)

·假若运算符为- 时:回传 A.subtract(B)

·假若运算符为 * 时:回传 A.multiply(B)

● 假若 A 或B 为Float、Double 或包含 e / E 的字符串时:

·假若A 或B 为BigInteger 时,将另一个转为BigDecimal,然后依照运算符执行

运算

·否则,将两者皆转为Double,然后依照运算符执行运算

● 假若 A 或B 为BigInteger 时,将另一个也转为BigInteger,则:

·假若运算符为 + 时:回传 A.add(B)

·假若运算符为- 时:回传 A.subtract(B)

·假若运算符为 * 时:回传 A.multiply(B)

● 否则,__________将A 和B 皆转为Long,然后依照运算符执行运算

● 假若运算结果产生异常时,则错误!

(2) A {/ , div} B

● 假若 A 和B 为null:回传 (Long)0

● 假若 A 或B 为BigDecimal 或BigInteger 时,皆转为BigDecimal,然后回传 A.divide(B,

BigDecimal.ROUND_HALF_UP)

● 否则,将A 和B 皆转为Double,然后依照运算符执行运算

● 假若运算结果产生异常时,则错误!

(3) A {% , mod} B

● 假若 A 和B 为null:回传 (Long)0

● 假若A 或B为BigDecimal、Float、Double或包含 e / E 的字符串时,皆转为Double,然

后依照运算符执行运算

● 假若 A 或B 为BigInteger 时,将另一个转为BigInteger,则回传 A.remainder(B)

● 否则,将A 和B 皆转为Long,然后依照运算符执行运算

● 假若运算结果产生异常时,则错误!

(4) -A

● 假若 A 为null:回传 (Long)0

● 假若 A 为BigDecimal 或BigInteger 时,回传 A.negate( )

● 假若 A 为String 时:

·假若A 包含 e / E 时,将转为Double,然后依照运算符执行运算

·否则,转为Long,然后依照运算符执行运算

·假若运算结果产生异常时,则错误!

● 假若 A 为Byte、Short、Integer、Long、Float 或Double

·直接依原本类型执行运算

·假若运算结果产生异常时,则错误!

● 否则,错误!

Tomcat 上的jsp-examples 中,有一个EL算术运算符的范例 basic-arithmetic.jsp。它的程序

很简单,所以不在这里多做说明,它的执行结果如图6-4 所示。

图 6-4 basic-arithmetic.jsp 的执行结果

6-5 EL 关系运算符

EL 关系运算符有以下六个运算符(见表6-7):

表 6-7

关系运算符说 明 范 例 结 果

= = 或 eq 等于${ 5 = = 5 } 或 ${ 5 eq 5 } true

!= 或 ne 不等于${ 5 != 5 } 或 ${ 5 ne 5 } false

< 或 lt 小于${ 3 < 5 }或 ${ 3 lt 5 } true

> 或 gt 大于${ 3 > 5 }或 ${ 3 gt 5 } false

<= 或 le 小于等于${ 3 <= 5 }或 ${ 3 le 5 } true

>= 或 ge 大于等于${ 3 >= 5 }或 ${ 3 ge 5 } false

注意

在使用EL 关系运算符时,不能够写成:

${param.password1} = = ${param.password2}

或者

${ ${param.password1 } = = ${ param.password2 } }

而应写成

${ param.password1 = = param.password2 }

接下来,我们依照下列几种情况,详细说明EL 关系运算符的规则:

(1) A {<, >, <=, >=, lt, gt, le, ge} B

● 假若 A= = B,运算符为<=, le, >=, ge 时,回传true,否则回传false

● 假若 A 为null 或B 为null 时,回传false

● 假若A或B为BigDecimal时,将另一个转为BigDecimal,然后回传A.compareTo(B)的值

● 假若 A 或B 为Float、__________Double 时,皆转为Double 类型,然后依其运算符运算

● 假若A或B为BigInteger时,将另一个转为BigInteger,然后回传A.compareTo(B)的值

● 假若A 或B为Byte、Short、Character、Integer或Long时,皆转为Long 类型,然后依其

运算符运算

● 假若 A 或B 为String 时,将另一个也转为String,然后做词汇上的比较

● 假若 A 为Comparable 时,则:

·假若A.compareTo(B)产生异常时,则错误!

● 否则,采用 A.compareTo(B) 的比较结果

● 假若 B 为Comparable 时,则:

· 假若 B.compareTo(A)产生异常时,则错误!

● 否则,采用 A.compareTo(B) 的比较结果

● 否则,错误!

(2) A {= =, !=, eq, ne} B

● 假若 A= = B,依其运算符运算

● 假若 A 为null 或B 为null 时:= = /eq 则回传false,!= / ne 则回传true

● 假若 A 或B 为BigDecimal 时,将另一个转为BigDecimal,则:

· 假若运算符为 = = / eq,则回传A.equals(B)

· 假若运算符为 != / ne,则回传 !A.equals(B)

● 假若 A 或B 为Float、Double 时,皆转为Double 类型,然后依其运算符运算

● 假若 A 或B 为BigInteger 时,将另一个转为BigInteger,则:

·假若运算符为 = = / eq,则回传A.equals(B)

·假若运算符为 != / ne,则回传 !A.equals(B)

● 假若A 或B为Byte、Short、Character、Integer 或Long时,皆转为Long 类型,然后依其

运算符运算

● 假若 A 或B 为Boolean 时,将另一个也转为Boolean,然后依其运算符运算

● 假若 A 或B 为String 时,将另一个也转为String,然后做词汇上的比较

● 否则,假若A.equals(B)产生异常时,则错误!

● 否则,然后依其运算符运算,回传A.equals(B)

Tomcat 上的jsp-examples 中,有一个EL 关系运算符的范例basic-comparisons.jsp。它的程

序很简单,所以不在这里多做说明,大家直接看它的执行结果(如图6-5 所示):

图 6-5 basic-comparisons.jsp 的执行结果

6-6 EL 逻辑运算符

EL 逻辑运算符只有三个(见表6-8):

表 6-8

逻辑运算符说 明 范 例 结 果

&& 或 and 交集${ A && B } 或 ${ A and B } true / false

|| 或 or 并集${ A || B } 或 ${ A or B } true / false

! 或 not 非${ !A } 或 ${ not A } true / false

下面举几个例子:

${ param.month = = 7 and param.day = = 14 }

${ param.month = = 7 || param.day = = 14 }

${ not param.choice }

EL 逻辑运算符的规则很简单:

(1) A {&&, and, || 或 or } B

·将A 和B 转为Boolean,然后依其运算符运算

(2) {!, not}A

·将A 转为Boolean,然后依其运算符运算

6-7 EL 其他运算符

EL 除了上述三大类的运算符之外___________,还有下列几个重要的运算符:

(1) Empty 运算符

(2) 条件运算符

(3) ( ) 括号运算符

6-7-1 Empty 运算符

Empty 运算符主要用来判断值是否为null 或空的,例如:

${ empty param.name }

接下来说明Empty 运算符的规则:

(1) {empty} A

● 假若 A 为null 时,回传true

● 否则,假若A 为空String 时,回传true

● 否则,假若A 为空Array 时,回传true

● 否则,假若A 为空Map 时,回传true

● 否则,假若A 为空Collection 时,回传true

● 否则,回传false

6-7-2 条件运算符

所谓条件运算符如下:

${ A ? B : C}

意思是说,当A 为true 时,执行B;而A 为false 时,则执行C。

6-7-3 括号运算符

括号运算符主要用来改变执行优先权,例如:${ A * (B+C) }

至于运算符的优先权,如下所示(由高至低,由左至右):

· []、.

· ( )

· - (负)、not、!、empty

· *、/、div、%、mod

· +、- (减)

· <、>、<=、>=、lt、gt、le、ge

· = =、!=、eq、ne

· &&、and

· ||、or

· ${ A ? B : C}

最后笔者写一个ELOperator.jsp 范例,将所有运算符实际操作一遍。

ELOperator.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH6 – ELOperator.jsp</title>

</head>

<body>

<h2>EL 的运算符</h2>

<c:set value="mike" var="username" scope="request" />

<table border="1" width="50%" align="left">

<TR>

<TR>

<TH>运算式</TH>

<TH>结果</TH>

</TR>

<TR><TD>14 + 3</TD><TD>${14 + 3}</TD></TR>

<TR><TD>14 - 3</TD><TD>${14 - 3}</TD></TR>

<TR><TD>14 * 3</TD><TD>${14 * 3}</TD></TR>

<TR><TD>14 / 3</TD><TD>${14 / 3}</TD></TR>

<TR><TD>14 % 3</TD><TD>${14 % 3}</TD></TR>

<TR><TD>14 == 3</TD><TD>${14 == 3}</TD></TR>

<TR><TD>14 != 3</TD><TD>${14 != 3}</TD></TR>

<TR><TD>14 < 3</TD><TD>${14 < 3}</TD></TR>

<TR><TD>14 > 3</TD><TD>${14 > 3}</TD></TR>

<TR><TD>14 <= 3</TD><TD>${14 <= 3}</TD></TR>

<TR><TD>14 >= 3</TD><TD>${14 >= 3}</TD></TR>

<TR><TD>true && false</TD><TD>${true && false}</TD></TR>

<TR><TD>true || false</TD><TD>${true || false}</TD></TR>

<TR><TD>! false</TD><TD>${! false}</TD></TR>

<TR><TD>empty username</TD><TD>${empty username}</TD></TR>

<TR><TD>empty password</TD><TD>${empty password}</TD></TR>

</table>

</body>

</html>

EL 的数学运算符、相等运算符、关系运算符和逻辑运算符就跟其他程序语言一样,并没有特别

的地方。但是它的empty 运算符就比较特别,为了测试它,笔者写了这样一行程序代码:

<c:set value="mike" var="username" scope="request" />

这样Request 属性范围里就存在一个名称为username、值为mike 的属性。执行此程序时,读者

将会发现${empty username}为false; ${empty password}为true,其代表的意义就是:它可以在

四种属性范围中找到username这个属性,但是找不到password这个属性。ELOperator.jsp 的执行

结果如图6-6:

图 6-6 ELOperator.jsp 的执行结果

6-8 EL Functions

前面几节主要介绍EL 语法的使用和规则,本节笔者将介绍如何自定义EL 的函数(functions)。

EL 函数的语法如下:

ns:function( arg1, arg2, arg3 …. argN)

其中ns 为前置名称(prefix),它必须和taglib 指令的前置名称一样。如下范例:

<% @ taglib prefix="my"

uri="http://jakarta.apache.org/tomcat/jsp2-example-taglib" %>

…….

${my:function(param.name)}

前置名称都为my,至于function 为EL 函数的名称,而arg1、arg2 等等,都是function 的传

入值。在Tomcat 5.0.16中有一个简单的EL 函数范例,名称为functions.jsp,笔者接下来将依此

范例来说明如何自定义EL 函数。

6-8-1 Tomcat EL 函数范例

Tomcat 提供的EL 函数范例中,自定义两个EL 函数:reverse 和countVowels,其中:

reverse 函数:将传入的字符串以反向顺序输出。

countVowels 函数:计算传入的字符串中,和aeiouAEIOU 吻合的字符个数。

图 6-7 是functions.jsp 程序的执行结果:

图 6-7 functions.jsp 的执行结果

输入JavaWorld.com.tw 字符串至reverse 函数后,回传 wt.moc.dlroWavaJ 的结果;若传入

countVowels 函数后,因为有两个a 和o,总共四个字符吻合,所以回传4。

Tomcat 的EL 函数范例,主要分为四个部分(见表6-9):

表 6-9

web.xml 设定taglib 的TLD 文件位置

functions.jsp 使用EL 函数的范例程序

jsp2-example-taglib.tld EL 函数、标签库的设定文件

jsp2.examples.el.Functions.java EL 函数主要程序逻辑处理部分

这四个部分环环相扣,都互有关系,笔者依functions.jsp为中心,然后再慢慢说明其他部分。

首先我们直接来看functions.jsp 程序:

6-8-2 functions.jsp

functions.jsp

<%@ taglib prefix="my"

uri="http://jakarta.apache.org/tomcat/jsp2-example-taglib"%>

<html>

<head>

<title>JSP 2.0 Expression Language - Functions</title>

</head>

<body>

<h1>JSP 2.0 Expression Language - Functions</h1>

…. 略

<blockquote>

<u><b>Change Parameter</b></u>

<form action="functions.jsp" method="GET">

foo = <input type="text" name="foo" value="${param['foo']}">

<input type="submit">

</form>

<br>

<code>

<table border="1">

<thead>

<td><b>EL Expression</b></td>

<td><b>Result</b></td>

</thead>

<tr>

<td>\${param["foo"]}</td>

<td>${param["foo"]}&nbsp;</td>

</tr>

<tr>

<td>\${my:reverse(param["foo"])}</td>

<td>${my:reverse(param["foo"])}&nbsp;</td>

</tr>

<tr>

<td>\${my:reverse(my:reverse(param["foo"]))}</td>

<td>${my:reverse(my:reverse(param["foo"]))}&nbsp;</td>

</tr>

<tr>

<td>\${my:countVowels(param["foo"])}</td>

<td>${my:countVowels(param["foo"])}&nbsp;</td>

</tr>

</table>

</code>

</blockquote>

</body>

</html>

functions.jsp程序中,一开始定义taglib,它的前置名称为my;uri为http://jakarta.apache.org/

tomcat/jsp2-example-taglib,如下所示:

<%@ taglib prefix="my"

uri="http://jakarta.apache.org/tomcat/jsp2-example-taglib"%>

当Container执行这段程序时,它会根据uri的值,到web.xml中找相对应的TLD (Tag Library

Descriptor)文件。至于web.xml 如何设定两者之间的对应关系,我们在6-8-3 小节再说明。

functions.jsp 中包含一个窗体(form),当用户在文本[玉玉1]输入框(text input)中输入字符

串,按下按钮时,底下会显示字符串经过EL 函数处理后的结果。functions.jsp 程序最重要的部分

是调用EL 函数:

${my:reverse(param["foo"])}

上述的意思是接收foo参数,然后传入reverse 函数。调用EL函数的方式很简单,只要前置名

称:其中EL 函数名称是被定义在TLD 文件中,这会在6-8-4 小节详细说明。至于reverse 函数的逻

辑运算,则是被定义在jsp2.examples.el.Functions.java程序中,这部分会在6-8-5 小节中说明。

注意

TLD 文件主要为标签的设定文件,其中包含标签的名称、参数等等。在JSP 2.0 之后,相关

EL 函数的设定,也可以在TLD 文件中定义。

6-8-3 web.xml

web.xml是每个web站台最主要的设定文件,在这个设定文件中,可以设定许多东西,如:Servlet、

Resource、Filter 等等。不过现在关心的是如何在web.xml 中设定taglib的uri 是对应到哪个TLD

文件。笔者从范例的web.xml 中节录出设定的片段程序如下:

web.xml

<jsp-config>

<taglib>

<taglib-uri>

http://jakarta.apache.org/tomcat/jsp2-example-taglib

</taglib-uri>

<taglib-location>

/WEB-INF/jsp2/jsp2-example-taglib.tld

</taglib-location>

</taglib>

</jsp-config>

在web.xml 中,<taglib>用来设定标签的TLD 文件位置。<taglib-uri>用来指定taglib 的uri

位置,用户可以自行给定一个uri,例如:

<taglib-uri>http://www.javaworld.com.tw/jute</taglib-uri>

<taglib-uri>tw.com.javaworld</taglib-uri>

<taglib-location>用来指定TLD 文件的位置。依照范例,它是指定在WEB-INF/jsp2/目录下的

jsp2-example-taglib.tld。

因 此 ,笔者所节录下来的web.xml , 它所代表的意思是: taglib 的uri 为

http://jakarta.apache.org/tomcat/jsp2-example-taglib,它的TLD 文件是在WEB-INF/jsp2/目

录下的jsp2-example-taglib.tld。

6-8-4 jsp2-example-taglib.tld

在jsp2-example-taglib.tld 中定义许多标签,其中笔者节录一段定义EL 函数:

jsp2-example-taglib.tld

<function>

<description>Reverses the characters in the given String</description>

<name>reverse</name>

<function-class>jsp2.examples.el.Functions</function-class>

<function-signature>

java.lang.String reverse( java.lang.String )

</function-signature>

</function>

<function>

<description>Counts the number of vowels (a,e,i,o,u) in the given

String</description>

<name>countVowels</name>

<function-class>jsp2.examples.el.Functions</function-class>

<function-signature>

java.lang.String numVowels( java.lang.String )

</function-signature>

</function>

上述定义两个EL 函数,用<name>来设定EL 函数名称,它们分别为reverse和countVowels;用

<function-class> 设定EL 函数的Java 类, 本范例的EL 函数都是定义在

jsp2.examples.el.Functions;最后用<function-signature>来设定EL 函数的传入值和回传值,例

如:

<function-signature>java.lang.String

reverse( java.lang.String )</function-signature>

表示reverse 函数有一String 类型的传入值,然后回传String 类型的值。最后我们再来看

reverse 和countVowels 的程序。

6-8-5 Functions.java

Functions.java 主要定义三个公开静态的方法,分别为:reverse、numVowels 和caps(见表

6-10)。下面是Functions.java 完整的程序代码:

Functions.java

package jsp2.examples.el;

import java.util.*;

/**

* Defines the functions for the jsp2 example tag library.

* <p>Each function is defined as a static method.</p>

*/

public class Functions {

public static String reverse( String text ) {

return new StringBuffer( text ).reverse().toString();

}

public static int numVowels( String text ) {

String vowels = "aeiouAEIOU";

int result = 0;

for( int i = 0; i < text.length(); i++ ) {

if( vowels.indexOf( text.charAt( i ) ) != -1 ) {

result++;

}

}

return result;

}

public static String caps( String text ) {

return text.toUpperCase();

}

}

表 6-10

String reverse(String

text)

将text 字符串的顺序反向处理,然后回传反向后的字符串

int numVowels(String text)将 text 字符串比对aeiouAEIOU 等字符,然后回传比对中的次

_______________数

String caps(String text) 将 text 字符串都转为大写,然后回传此字符串

注意

在定义EL 函数时,都必须为公开静态(public static)

JSP错误处理

4-7 错误处理

通常 JSP 在执行时,在两个阶段下会发生错误。

JSP 网页→ servlet

servlet 类处理每一个请求时

在第一阶段时,产生的错误我们称为Translation Time Processing Errors;在第二阶段时,

产生的错误我们称为Client Request Time Processing Errors。接下来我们将针对这两个阶段产

生错误的原因和处理方法,为各位读者做详细的介绍。

4-7-1 Translation Time Processing Errors

Translation Time Processing Errors 产生的主要原因:我们在撰写JSP时的语法有错误,导

致JSP Container 无法将JSP 网页编译成Servlet 类文件( .class),例如:500 Internal Server

Error,500 是指HTTP 的错误状态码,因此是Server Error,如图4-8。

通常产生这种错误时,可能是JSP 的语法有错误,或是JSP Container 在一开始安装、设定时,

有不适当的情形发生。解决的方法就是再一次检查程序是否有写错的,不然也有可能是JSP

Container 的bug。

图 4-8 500 Internal Server Error

4-7-2 Client Request Time Processing Errors

Client Request Time Processing Errors 错误的发生,往往不是语法错误,而可能是逻辑上

的错误,简单地说,你写一个计算除法的程序,当用户输入的分母为零时,程序会发生错误并抛出

异常(Exception),交由异常处理(Exception Handling)机制做适当的处理。

对于这种错误的处理,我们通常会交给errorPage 去处理。下面举个例子,我想各位读者应该

能够很容易了解。

使用errorPage 的范例程序 (一):ErrorPage.jsp

ErrorPage.jsp

<%@ page contentType="text/html;charset=GB2312" errorPage="Error.jsp" %>

<html>

<head>

<title>CH4 - ErrorPage.jsp</title>

</head>

<body>

<h2>errorPage 的范例程序</h2>

<%!

private double toDouble(String value)

{

return(Double.valueOf(value).doubleValue());

}

%>

<%

double num1 = toDouble(request.getParameter("num1"));

double num2 = toDouble(request.getParameter("num2"));

%>

您传入的两个数字为:<%= num1 %> 和 <%= num2 %><br>

两数相加为 <%= (num1+num2) %>

</body>

</html>

ErrorPage.jsp 程序中,我们使用page 指令中的errorPage 属性,告诉JSP Container,如果

在程序中有错误产生时,会自动交给Error.jsp 处理。

<%@ page contentType="text/html;charset=GB2312 " errorPage="Error.jsp" %>

我们声明一个函数叫toDouble,主要把我们接收进来的字符串转成Double 的类型,才能做加

法运算。

<%!

private double toDouble(String value)

{

return(Double.valueOf(value).doubleValue());

}

%>

接收num1 和num2 两个字符串,并且将它们转为double 的类型,并各自命名为num1 和num2。

<%

double num1 = toDouble(request.getParameter("num1"));

double num2 = toDouble(request.getParameter("num2"));

%>

Error.jsp

<%@ page contentType="text/html;charset=GB2312" isErrorPage="true" %>

<%@ page import="java.io.PrintWriter" %>

<html>

<head>

<title>CH4 - Error.jsp</title>

</head>

<body>

<h2>errorPage 的范例程序</h2>

<p>ErrorPage.jsp 错误产生:<I><%= exception %></I></p><br>

<pre>

问题如下:<% exception.printStackTrace(new PrintWriter(out)); %>

</pre>

</body>

</html>

Error.jsp 主要处理ErrorPage.jsp 所产生的错误,所以在ErrorPage.jsp 中page 指令的属性

errorPage设为Error.jsp,因此,若ErrorPage.jsp有错误发生时,会自动转到Error.jsp来处理。

Error.jsp 必须设定page 指令的属性isErrorPage为true,因为Error.jsp是专门用来处理错

误的网页。

<%@ page contentType="text/html;charset=GB2312" isErrorPage="true" %>

下面这几行代码用来显示出错误发生的原因:

<p>ErrorPage.jsp 错误产生:<I><%= exception %></I></p><br>

<PRE>

问题如下:<% exception.printStackTrace(new PrintWriter(out)); %>

</PRE>

由于在这个程序中并没有做一个窗体来输入两个数字,所以必须手动在URL后输入num1和num2

的值。如图4-9 所示:

图 4-9 ErrorPage.jsp 的执行结果

上一个范例的参数设定为:

http://localhost:8080/JSPBook/CH4/ErrorPage.jsp?num1=100&num2=245

此时num1 的值为100,num2 的值为245,所以相加的结果就为345。但是,如果num1 或num2

的参数不是数字的话,例如:

http://localhost:8080/JSPBook/CH4/ErrorPage.jsp?num1=javaworld&num2=245

那么就会产生错误结果如图4-10。

当 ErrorPage.jsp 产生错误时,就会交由Error.jsp 去处理,所以您看到的结果,其实是执行

Error.jsp 后的结果。

图 4-10 ErrorPage.jsp 执行时产生错误

5-1 属性( Attribute )与范围( Scope )

有些JSP 程序员会将request、session、application 和pageContext 归为一类,原因在于:

它们皆能借助setAttribute( )和getAttribute( )来设定和取得其属性(Attribute),通过这两种

方法来做到数据分享

我们先来看下面这段小程序:

Page1.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Page1.jsp</title>

</head>

<body>

</br>

<%

application.setAttribute("Name","mike");

application.setAttribute("Password","browser");

%>

<jsp:forward page="Page2.jsp"/>

</body>

</html>

在这个程序中,笔者设定两个属性:Name、Password,其值为:mike、browser。然后再转交(forward)

到Page2.jsp。我只要在Page2.jsp 当中加入application.getAttribute( ),就能取得在Page1.jsp

设定的数据。

Page2.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Page2.jsp</title>

</head>

<body>

<%

String Name = (String) application.getAttribute("Name");

String Password = (String) application.getAttribute("Password");

out.println("Name = "+Name);

out.println("Password = "+ Password);

%>

</body>

</html>

Page1.jsp 的执行结果如图5-1 所示。

图 5-1 Page1.jsp 的执行结果

从图5-1 可以看出,此时Name的值就会等于mike,Password 的值就等于browser。看完这个小

范例之后,读者有没有发现网页之间要传递数据时,除了可以使用窗体、隐藏字段来完成之外,JSP

技术还提供给开发人员一项传递数据的机制,那就是利用setAttribute( )和getAttribute( )方法,

如同Page1.jsp 和Page2.jsp 的做法。不过它还是有些限制的,这就留到下一节来说明。

在上面 Page1.jsp 和Page2.jsp 的程序当中,是将数据存入到application 对象之中。除了

application 之外,还有request、pageContext 和session,也都可以设定和取得属性值,那它们

之间有什么分别吗?

它们之间最大的差别在于范围(Scope)不一样,这个概念有点像C、C++中的全局变量和局部变量

的概念。接下来就介绍JSP 的范围。

5-1-1 JSP ScopePage

JSP 有四种范围,分别为Page、Request、Session、Application。所谓的Page,指的是单单一

页JSP Page 的范围。若要将数据存入Page 范围时,可以用pageContext 对象的setAttribute( )

方法;若要取得Page范围的数据时,可以使用pageContext对象的getAttribute( )方法。我们将

之前的范例做小幅度的修改,将application 改为pageContext。

PageScope1.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - PageScope1.jsp</title>

</head>

<body>

<h2>Page 范围 - pageContext</h2>

<%

pageContext.setAttribute("Name","mike");

pageContext.setAttribute("Password","browser");

%>

<jsp:forward page="PageScope2.jsp"/>

</body>

</html>

PageScope2.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - PageScope2.jsp</title>

</head>

<body>

<h2>Page 范围 - pageContext</h2>

</br>

<%

String Name = (String)pageContext.getAttribute("Name");

String Password = (String)pageContext.getAttribute("Password");

out.println("Name = "+Name);

out.println("Password = "+ Password);

%>

</body>

</html>

执行结果如图5-2 所示。

图 5-2 PageScope1.jsp 的执行结果

这个范例程序和之前有点类似,只是之前的程序是application,现在改为pageContext,但是结

果却大不相同,PageScope2.jsp 根本无法取得PageScope1.jsp 设定的Name 和Password 值,因为在

PageScope1.jsp 当中,是把Name 和Password 的属性范围设为Page,所以Name 和Password 的值只能

在PageScope1.jsp当中取得。若修改PageScope1.jsp的程序,重新命名为PageScope3.jsp,如下:

PageScope3.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - PageScope3.jsp</title>

</head>

<body>

<h2>Page 范围 - pageContext</h2>

</br>

<%

pageContext.setAttribute("Name","mike");

pageContext.setAttribute("Password","browser");

String Name = (String)pageContext.getAttribute("Name");

String Password = (String)pageContext.getAttribute("Password");

out.println("Name = "+Name);

out.println("Password = "+ Password);

%>

</body>

</html>

PageScope3.jsp 的执行结果如图5-3 所示。

图 5-3 PageScope3.jsp 的执行结果

经过修改后的程序,Name 和Password 的值就能顺利显示出来。这个范例主要用来说明一个概

念:若数据设为Page范围时,数据只能在同一个JSP网页上取得,其他JSP网页却无法取得该数据。

5-1-2 JSP ScopeRequest

接下来介绍第二种范围:Request。Request 的范围是指在一JSP 网页发出请求到另一个JSP 网

页之间,随后这个属性就失效。设定Request 的范围时可利用request 对象中的setAttribute( )

和getAttribute( )。我们再来看下列这个范例:

RequestScope1.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - RequestScope1.jsp</title>

</head>

<body>

<h2>Request 范围 - request</h2>

<%

request.setAttribute("Name","mike");

request.setAttribute("Password","browser");

%>

<jsp:forward page="RequestScope2.jsp"/>

</body>

</html>

RequestScope2.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - RequestScope2.jsp</title>

</head>

<body>

<h2>Request 范围 - request</h2>

<%

String Name = (String) request.getAttribute("Name");

String Password = (String) request.getAttribute("Password");

out.println("Name = "+Name);

out.println("Password = "+ Password);

%>

</body>

</html>

RequestScope1.jsp 的执行结果如图5-4 所示。

现在将Name和Password的属性范围设为Request,当RequestScope1.jsp转向到RequestScope2.jsp

时,RequestScope2.jsp也能取得RequestScope1.jsp设定的Name和Password值。不过其他的JSP网页

无法得到Name和Password值,除非它们也和RequestScope1.jsp有请求的关系。

图 5-4 RequestScope1.jsp 的执行结果

除了利用转向(forward)的方法可以存取request 对象的数据之外,还能使用包含(include)的

方法。

假若我将RequestScope1.jsp 的

<jsp:forward page="RequestScope2.jsp"/>

改为

<jsp:include page="RequestScope2.jsp" flush="true"/>

执行RequestScope1.jsp 时,结果还是和图5-4 一样。表示使用<jsp:include>标签所包含进来

的网页,同样也可以取得Request 范围的数据。

5-1-3 JSP ScopeSessionApplication

表5-2 介绍了最后两种范围:Session、Application

表 5-2

范围 说 明

Session

Session 的作用范围为一段用户持续和服务器所连接的时间,但与服务

器断线后,这个属性就无效。只要将数据存入session 对象,数据的范

围就为Session

Application

Application 的作用范围在服务器一开始执行服务,到服务器关闭为

止。Application的范围最大、停留的时间也最久,所以使用时要特别注

意,不然可能会造成服务器负载越来越重的情况。只要将数据存入

application对象,数据的Scope 就为Application

表5-3 列出了一般储存和取得属性的方法,以下pageContext、request、session和application

皆可使用。

注意

pageContext 并无getAttributeNames( )方法。

表 5-3

方法 说 明

void setAttribute(String name, Object value) 设定name属性的值为value

Enumeration getAttributeNamesInScope(int

scope )

取得所有scope 范围的属性

Object getAttribute(String name) 取得name 属性的值

void removeAttribute(String name) 移除name 属性的值

当 我们使用getAttribute(String name)取得name 属性的值时,它会回传一个

java.lang.Object,因此,我们还必须根据name 属性值的类型做转换类型(Casting)的工作。例如:

若要取得String 类型的Name 属性时:

String Name = (String)pageContext.getAttribute("Name");

若是Integer 类型的Year 属性时:

Integer Year = (Integer)session.getAttribute("Year");

到目前已大约介绍完JSP 中四种范围(Scope):Page、Request、Session 和Application。假若

我的数据要设为Page 范围时,则只需要:

pageContext.setAttribute("Year", new Integer(2001));

若要为Request、Session 或Application 时,就分别存入request、session 或application

对象之中,如下:

request.setAttribute("Month", new Integer(12) );

session.setAttribute("Day", new Integer(27) );

application.setAttribute("Times", new Integer(10));

接下来就正式进入本章的主题:隐含对象(Implicit Object)。

5-2 与Servlet 有关的隐含对象

与 Servlet 有关的隐含对象有两个:page 和config。page 对象表示Servlet 本身;config 对

象则是存放Servlet 的初始参数值。

page 对象

page对象代表JSP本身,更准确地说,它代表JSP被转译后的Servlet,因此,它可以调用Servlet

类所定义的方法,不过实际上,page 对象很少在JSP 中使用。我们来看看以下的范例程序:

PageInfo.jsp

<%@ page info="JSP 2.0 技术手册" contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - PageInfo.jsp</title>

</head>

<body>

<h2>page 隐含对象</h2>

Page Info = <%= ((javax.servlet.jsp.HttpJspPage)page).getServletInfo() %>

</body>

</html>

这个例子中,我们先设定page 指令的info 属性为“JSP 2.0 技术手册”,page 对象的类型为

java.lang.Object,我们调用javax.servlet.jsp.HttpJspPage 中getServletInfo( )的方法,将

Info 打印出来,执行结果如图5-5 所示。

图 5-5 PathInfo.jsp 的执行结果

config 对象

config 对象里存放着一些Servlet 初始的数据结构,config 对象和page 对象一样都很少被用

到。config 对象实现于javax.servlet.ServletConfig 接口,它共有下列四种方法:

public String getInitParameter(name)

public java.util.Enumeration getInitParameterNames( )

public ServletContext getServletContext( )

public Sring getServletName( )

上述前两种方法可以让config对象取得Servlet 初始参数值,如果此数值不存在,就传回null。

例如:当我们在web.xml 中设定如下时:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

<servlet>

<servlet-name>ServletConfigurator</servlet-name>

<servlet-class>

org.logicalcobwebs.proxool.configuration.ServletConfigurator

</servlet-class>

<init-param>

<param-name>propertyFile</param-name>

<param-value>

WEB-INF/classes/Proxool.properties

</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

</web-app>

那么我们就可以直接使用config.getInitParameter("propertyFile")来取得名称为

propertyFile、其值为WEB-INF/classes/Proxool.properties 的参数。如下范例:

String propertyFile = (String)config.getInitParameter("propertyFile");

5-3 与 Input / Output 有关的隐含对象

本节中,我们将介绍和Input/Output 有关的隐含对象,它们包括:out、request 和response

对象。request 对象表示客户端请求的内容;response 对象表示响应客户端的结果;而out 对象负

责把数据的结果显示到客户端的浏览器。

request 对象

request 对象包含所有请求的信息,如:请求的来源、标头、cookies和请求相关的参数值等等。

在JSP 网页中,request 对象是实现javax.servlet.http.HttpServletRequest 接口的,

HttpServletRequest 接口所提供的方法,可以将它分为四大类:

(1) 在5-1-3 小节提到的储存和取得属性方法;

(2) 能够取得请求参数的方法,如表5-4:

表 5-4 取得请求参数的方法

方 法 说 明

String getParameter(String name) 取得name 的参数值

Enumeration getParameterNames( ) 取得所有的参数名称

String [] getParameterValues(String name) 取得所有name 的参数值

Map getParameterMap( ) 取得一个要求参数的Map

(3) 能够取得请求HTTP 标头的方法,如表5-5:

表 5-5 取得请求标头的方法

方 法 说 明

String getHeader(String name) 取得name 的标头

Enumeration getHeaderNames() 取得所有的标头名称

Enumeration getHeaders(String name) 取得所有name 的标头

int getIntHeader(String name) 取得整数类型name 的标头

long getDateHeader(String name) 取得日期类型name 的标头

Cookie [] getCookies( ) 取得与请求有关的cookies

(4) 其他的方法,例如:取得请求的URL、IP 和session,如表5-6:

表 5-6 其他请求的方法

方 法 说 明

String getContextPath( ) 取得Context 路径(即站台名称)

String getMethod( ) 取得HTTP 的方法(GET、POST)

String getProtocol( ) 取得使用的协议(HTTP/1.1、HTTP/1.0 )

String getQueryString( )

取得请求的参数字符串,不过,HTTP的方

法必须为GET

String getRequestedSessionId( ) 取得用户端的Session ID

String getRequestURI( )

取得请求的URL,但是不包括请求的参数

字符串

String getRemoteAddr( ) 取得用户的IP 地址

String getRemoteHost( ) 取得用户的主机名称

int getRemotePort( ) 取得用户的主机端口

String getRemoteUser( ) 取得用户的名称

void etCharacterEncoding(String

encoding)

设定编码格式,用来解决窗体传递中文的

问题

我们来看下面这个程序范例,相信对读者会更加有帮助。

Request.html

<html>

<head>

<title>CH5 - Request.html</title>

<meta http-equiv="Content-Type" content="text/html; charset=GB2312">

</head>

<body>

<form action="Request.jsp" method="GET">

Name:<input type="text" name="Name" size="20" maxlength="20"><br>

Number:<input type="text" name="Number" size="20" maxlength="20"><br><br>

<input type="submit" value="传送">

</form>

</body>

</html>

Request.html的执行结果如图5-6 所示,笔者在Name 的字段中输入browser;Number字段中输

入123456789。

图 5-6 Request.html 的执行结果

Request.jsp

<%@ page language="java" contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Request.jsp</title>

</head>

<body>

<h2>javax.servlet.http.HttpServletRequest 接口所提供的方法</h2>

getParameter("Name"):<%= request.getParameter("Name") %><br>

getParameter("Number"):<%= request.getParameter("Number") %><br>

getAttribute("Name"):<%= request.getAttribute("Name") %><br>

getAttribute("Number"):<%= request.getAttribute("Number") %><br><br>

getAuthType( ):<%= request.getAuthType() %><br>

getProtocol( ):<%= request.getProtocol() %><br>

getMethod( ):<%= request.getMethod() %><br>

getScheme( ):<%= request.getScheme() %><br>

getContentType( ):<%= request.getContentType() %><br>

getContentLength( ):<%= request.getContentLength() %><br>

getCharacterEncoding( ):<%= request.getCharacterEncoding() %><br>

getRequestedSessionId( ):<%= request.getRequestedSessionId() %><br><br>

getContextPath( ):<%= request.getContextPath() %><br>

getServletPath( ):<%= request.getServletPath() %><br>

getPathInfo( ):<%= request.getPathInfo() %><br>

getRequestURI( ):<%= request.getRequestURI() %><br>

getQueryString( ):<%= request.getQueryString() %><br><br>

getRemoteAddr( ):<%= request.getRemoteAddr() %><br>

getRemoteHost( ):<%= request.getRemoteHost() %><br>

getRemoteUser( ):<%= request.getRemoteUser() %><br>

getRemotePort( ):<%= request.getRemotePort() %><br>

getServerName( ):<%= request.getServerName() %><br>

getServerPort( ):<%= request.getServerPort() %><br>

</body>

</html>

Request.jsp 的执行结果如图5-7 所示。

图 5-7 Request.jsp 的执行结果

在Request.jsp中,使用request.getParameter("Name")和request.getParameter("Number"),

能够取得Request.html窗体的值。除了取得请求的参数值之外,也能取得一些相关信息,如:使用的

协议、方法、URI 等等。

response 对象

response 对象主要将JSP 处理数据后的结果传回到客户端。response 对象是实现

javax.servlet.http.HttpServletResponse 接口。表5-7、表5-8、表5-9列出了response对象的

方法。

表 5-7 设定表头的方法

方 法 说 明

void addCookie(Cookie cookie) 新增cookie

void addDateHeader(String name, long

date)

新增long类型的值到name标头

void addHeader(String name, String value)

新增String 类型的值到name 标

void addIntHeader(String name, int value)新增int 类型的值到name 标头

void setDateHeader(String name, long

date)

指定long类型的值到name标头

void setHeader(String name, String value)

指定String 类型的值到name 标

void setIntHeader(String name, int value)指定int 类型的值到name 标头

表 5-8 设定响应状态码的方法

方 法 说 明

void sendError(int sc) 传送状态码(status code)

void sendError(int sc, String

msg)

传送状态码和错误信息

void setStatus(int sc) 设定状态码

表 5-9 用来URL 重写(rewriting)的方法

方 法 说 明

String encodeRedirectURL(String

url)

对使用sendRedirect( )方法的URL 予以

编码

有时候,当我们修改程序后,产生的结果却是之前的数据,执行浏览器上的刷新,才能看到更

改数据后的结果,针对这个问题,有时是因为浏览器会将之前浏览过的数据存放在浏览器的cache

中,所以当我们再次执行时,浏览器会直接从cache 中取出,因此,会显示之前旧的数据。笔者将

写一个Non-cache.jsp 程序来解决这个问题。

Non-cache.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Non-cache.jsp</title>

</head>

<body>

<h2>解决浏览器 cache 的问题 - response</h2>

<%

if (request.getProtocol().compareTo("HTTP/1.0") == 0)

response.setHeader("Pragma", "no-cache");

else if (request.getProtocol().compareTo("HTTP/1.1") == 0)

response.setHeader("Cache-Control", "no-cache");

response.setDateHeader("Expires", 0);

%>

</body>

</html>

先用request 对象取得协议,如果为HTTP/1.0,就设定标头,内容为setHeader ("Pragma",

"no-cache");若为HTTP/1.1,就设定标头为response.setHeader ("Cache-Control", "no-cache"),

最后再设定response.setDateHeader("Expires", 0)。这样Non-cache.jsp 网页在浏览过后,就不

会再存放到浏览器或是proxy 服务器的cache 中。表5-10 列出了HTTP/1.1 Cache-Control 标头的

设定参数:

表 5-10 HTTP/1.1 Cache-Control 标头的设定参数

参 数 说 明

public

数据内容皆被储存起来,就连有密码保护的网页也是一样,

因此安全性相当低

private

数据内容只能被储存到私有的caches,即non-shared caches

no-cache

数据内容绝不被储存起来。proxy 服务器和浏览器读到此标

头,就不会将数据内容存入caches 中

no-store

数据内容除了不能存入caches中之外,亦不能存入暂时的磁

盘中,这个标头防止敏感性的数据被复制

must-revalidate

用户在每次读取数据时,会再次和原来的服务器确定是否为

最新数据,而不再通过中间的proxy 服务器

proxy-revalidate

这个参数有点像must-revalidate,不过中间接收的proxy

服务器可以互相分享caches

max-age=xxx

数据内容在经过xxx秒后,就会失效,这个标头就像Expires

标头的功能一样,不过max-age=xxx只能服务HTTP/1.1 的用

户。假设两者并用时,max-age=xxx 有较高的优先权

有时候,我们想要让网页自己能自动更新,因此,须使用到Refresh 这个标头。举个例子,我

们告诉浏览器,每隔三分钟,就重新加载此网页:

response.setIntHeader("Refresh" , 180)

如果想要过十秒后,调用浏览器转到http://Server/Path 的网页时,可用如下代码:

response.setHeader("Refresh","10; URL=http://Server/Path" )

如果大家对HTML 语法还熟悉,则HTML 语法中也有类似的功能:

<META HTTP-EQUIV="Refresh" CONTENT=" 10; URL=http://Server/Path" >

上述两种方法皆可以做到自动重新加载。

out 对象

out 对象能把结果输出到网页上。通常我们最常使用out.println(String name)和out.print(String

name),它们两者最大的差别在于println( )在输出的数据后面会自动加上换行的符号,例如:你在

Dos Console的窗口下,发现到它输出数据后会自动换行;反之,print( )不会在数据后自动换行。

out 对象除了这两种方法最常使用之外,它还有一些方法(见表5-11),这些方法主要是用来控

制管理输出的缓冲区(buffer)和输出流(output stream)。

表 5-11 out 对象方法

方 法 说 明

void clear( ) 清除输出缓冲区的内容

void clearBuffer( ) 清除输出缓冲区的内容

void close( ) 关闭输出流,清除所有的内容

int getBufferSize( ) 取得目前缓冲区的大小(KB)

int getRemaining( ) 取得目前使用后还剩下的缓冲区大小(KB)

boolean isAutoFlush( )

如果回传为true,表示如缓冲区满了,会自动清除;

若为false,表示如果缓冲区满了,不会自动清除,

而会产生异常处理

我们在这里举个例子,说明如何知道目前输出缓冲区的大小。

Out.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH5 - Out.jsp</title>

</head>

<body>

<h2>javax.servlet.jsp.JspWriter - out 对象</h2>

<%

int BufferSize = out.getBufferSize();

int Available = out.getRemaining();

int Used = BufferSize - Available;

%>

BufferSize : <%= BufferSize %><br>

Available : <%= Available %><br>

Used : <%= Used %><br>

</body>

</html>

BufferSize是一开始默认缓冲区的大小,默认值为8KB;Available 则是表示经过程序的执行,

目前缓冲区还剩下多少可以使用;而Used 则表示我们使用了多少的缓冲区。Out.jsp 执行结果如图

Java Server Pages 技术

JSP 2.0 简介

3-1 JavaServer Pages 技术

JavaServer Pages技术是一个纯Java 平台的技术,它主要用来产生动态网页内容,包括:html

Dhtml、XHTML 和XML。JavaServer Pages技术能够让网页人员轻易建立起功能强大、有弹性的动态

内容。

JavaServer Pages 技术有下列优点:

Write Once, Run Anywhere 特性

作为 Java 平台的一部分,JavaServer Pages 技术拥有Java语言“一次编写,各处执行”的特

点。随着越来越多的供货商将JavaServer Pages 技术添加到他们的产品中,您可以针对自己公司

的需求,做出审慎评估后,选择符合公司成本及规模的服务器,假若未来的需求有所变更时__________,更换

服务器平台并不影响之前所投下的成本、人力所开发的应用程序。

● 搭配可重复使用的组件

JavaServer Pages技术可依赖于重复使用跨平台的组件(如:JavaBean或Enterprise JavaBean

组件)来执行更复杂的运算、数据处理。开发人员能够共享开发完成的组件,或者能够加强这些组

件的功能,让更多用户或是客户团体使用。基于善加利用组件的方法,可以加快整体开发过程,也

大大降低公司的开发成本和人力。

● 采用标签化页面开发

Web 网页开发人员不一定都是熟悉Java 语言的程序员。因此,JSP 技术能够将许多功能封装起

来,成为一个自定义的标签,这些功能是完全根据XML 的标准来制订的,即JSP 技术中的标签库(Tag

Library)。因此,Web 页面开发人员可以运用自定义好的标签来达成工作需求,而无须再写复杂的

Java 语法,让Web 页面开发人员亦能快速开发出一动态内容网页。

今后,第三方开发人员和其他人员可以为常用功能建立自己的标签库,让Web 网页开发人员能

够使用熟悉的开发工具,如同HTML 一样的标签语法来执行特定功能的工作。本书将在“第十五章:

JSP Tag Library”和“第十六章:Simple Tag与Tag File”中详细地为各位介绍如何制作标签。

N-tier 企业应用架构的支持

有鉴于网际网络的发展,为因应未来服务越来越繁杂的要求,且不再受地域的限制,因此,

必须放弃以往Client-Server的Two-tier 架构,进而转向更具威力、弹性的分散性对象系统。由于

JavaServer Page 技术是Java 2 Platform Enterprise Edition (J2EE) (相关信息请参阅

www.javasoft.com/products/j2ee)集成中的一部分,它主要是负责前端显示经过复杂运算后之结果

内容,而分散性的对象系统则是主要依赖EJB ( Enterprise JavaBean )和JNDI ( Java Naming and

Directory Interface )构建[1]而成。

3-2 What is JSP

JSP( JavaServer Pages )是由Sun 公司倡导、许多别的公司参与一起建立的一种新动态网页

技术标准,类似其他技术标准,如ASP、php 或是ColdFusion,等等。

在传统的网页HTML 文件( *.htm,*.html )中加入__________Java程序片段( Scriptlet )和JSP标签,构

成了JSP 网页(*.jsp)。servlet/JSP Container 收到客户端发出的请求时,首先执行其中的程序片

段,然后将执行结果以HTML格式响应给客户端。其中程序片段可以是:操作数据库、重新定向网页

以及发送E-Mail 等等,这些都是建立动态网站所需要的功能。所有程序操作都在服务器端执行,

网络上传送给客户端的仅是得到的结果,与客户端的浏览器无关,因此,JSP 称为Server-Side

Language。

3-3 JSP 与servlet 的比较

Sun 公司首先发展出Servlet,其功能非常强大,且体系设计也很完善,但是它输出HTML 语法

时,必须使用out.println( )一句一句地输出,例如下面一段简单的程序:

out.println("<html>");

out.println("<head><title>demo1</title></head>");

out.println(" Hello World <br>");

out.println("<body>");

out.println("大家好");

out.println("</body>");

out.println("</html>");

由于这是一段简单的Hello World 程序,还看不出来其复杂性,但是当整个网页内容非常复杂

时,那么你的Servlet 程序可能大部分都是用out.println( )输出HTML 的标签了!

后来Sun 公司推出类似于ASP 的嵌入型Scripting Language,并且给它一个新的名称:

JavaServer Pages,简称为JSP。于是上面那段程序改为:

<html>

<head><title>www.javaworld.com.tw – 台湾Java 论坛</title></head>

<body>

<%

out.println(" Hello World <br>");

out.println("大家好");

%>

</body>

</html>

这样就简化了Web 网页程序员的负担,不用为了网页内容编排的更动,又需要由程序员来做修

改。

3-4 JSP 的执行过程

在介绍 JSP 语法之前,先向读者说明一下JSP 的执行过程(见图3-1)。

(1) 客户端发出Request (请求);

(2) JSP Container 将JSP 转译成Servlet 的源代码;

(3) 将产生的Servlet 的源代码经过编译后,并加载到内存执行;

(4) 把结果Response (响应)至客户端。

图 3-1 JSP 的执行过程

一般人都会以为JSP 的执行性能会和Servlet 相差很多,其实执行性能上的差别只在第一次的

执行。因为JSP 在执行第一次后,会被编译成Servlet 的类文件[玉玉2],即为XXX.class,当再重

复调用执行时,就直接执行第一次所产生的Servlet,而不用再重新把JSP编译成Servlet。因此,

除了第一次的编译会花较久的时间之外,之后JSP 和Servlet 的执行速度就几___________乎相同了。

在执行 JSP 网页时,通常可分为两个时期:转译时期(Translation Time)和请求时期(Request

Time)(见图3-2)。

转译时期:JSP 网页转译成Servlet 类。

请求时期:Servlet 类执行后,响应结果至客户端。

补充

转译期间主要做了两件事情:将JSP 网页转译为Servlet 源代码(.java),此段称为转译时

期(Translation time);将Servlet 源代码(.java)编译成Servlet 类(.class),此段称为

编译时期(Compilation time)。

图 3-2 转译时期与请求时期程序图

当 JSP 网页在执行时,JSP Container 会做检查的工作,若发现JSP 网页有更新修改时,JSP

Container 才会再次编译JSP 成Servlet;JSP 没有更新时,就直接执行前面所产生的Servlet。

笔者在这里以Tomcat 为例,看看Tomcat 如何将JSP 转译成Servlet。首先笔者写一个简单的

JSP 网页—— HelloJSP.jsp:

HelloJSP.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>CH3 - HelloJSP.jsp</title>

</head>

<body>

<h2>JSP 将会被转译为 Servlet</h2>

<%!

int k = 0;

%>

<c:out value="Hi" />

<%

String name = "browser";

out.println("大家好 !!");

%>

<%= name %>

</body>

</html>

当执行HelloJSP.jsp 时,Tomcat 会将它先转译为Servlet。这个Servlet 程序是放在

{Tomcat_Install}\apache Software Foundation\Tomcat 5.0\ work\Catalina\localhost\JSPBook\

org\apache\jsp\CH3目录下的HelloJSP_jsp.java和HelloJSP_jsp.class。其中HelloJSP_jsp.java

就是HelloJSP.jsp 所转译的Servlet 源代码,它的程序如下:

HelloJSP_jsp.java

package org.apache.jsp.CH3;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class HelloJSP_jsp extends org.apache.jasper.runtime.HttpJspBase

implements org.apache.jasper.runtime.JspSourceDependent {

int k = 0;

private static java.util.Vector _jspx_dependants;

private org.apache.jasper.runtime.TagHandlerPool _ jspx_tagPool_c_out_value;

public java.util.List getDependants() {

return _jspx_dependants;

}

public void _jspInit() {

_jspx_tagPool_c_out_value =

org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(

getServletConfig());

}

public void _jspDestroy() {

_jspx_tagPool_c_out_value.release();

}

public void _jspService(HttpServletRequest request, HttpServletResponse

response) throws java.io.IOException, ServletException {

JspFactory _jspxFactory = null;

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

try {

_jspxFactory = JspFactory.getDefaultFactory();

response.setContentType("text/html;charset=GB2312");

pageContext = _jspxFactory.getPageContext(this, request, response,

null, true, 8192, true);

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\r\n");

out.write("\r\n\r\n");

out.write("<html>\r\n");

out.write("<head>\r\n ");

out.write("<title>CH3 - HelloJSP.jsp");

out.write("</title>\r\n");

out.write("</head>\r\n");

out.write("<body>\r\n\r\n");

out.write("<h2>JSP 将会被转译为 Servlet");

out.write("</h2>\r\n\r\n");

out.write("\r\n");

if (_jspx_meth_c_out_0(pageContext))

return;

out.write("\r\n");

String name = "browser";

out.println("大家好 !!");

out.write("\r\n");

out.print( name );

out.write("\r\n\r\n");

out.write("</body>\r\n");

out.write("</html>");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

out.clearBuffer();

if (pageContext != null) pageContext.handlePageException(t);

}

} finally {

if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);

}

}

private boolean _jspx_meth_c_out_0(PageContext pageContext)

throws Throwable {

JspWriter out = pageContext.getOut();

// c:out

org.apache.taglibs.standard.tag.rt.core.OutTag _jspx_th_c_out_0 =

(org.apache.taglibs.standard.tag.rt.core.OutTag) _jspx_tagPool_c_out_value.

get( org.apache.taglibs.standard.tag.rt.core.OutTag.class);

_jspx_th_c_out_0.setPageContext(pageContext);

_jspx_th_c_out_0.setParent(null);

_jspx_th_c_out_0.setValue(new String("Hi"));

int _jspx_eval_c_out_0 = _jspx_th_c_out_0.doStartTag();

if (_jspx_th_c_out_0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)

return true;

_jspx_tagPool_c_out_value.reuse(_jspx_th_c_out_0);

return false;

}

}

当 JSP 被转译成Servlet 时,内容主要包含三部分:

public void _jspInit() {

…. 略

}

public void _jspDestroy() {

…. 略

}

public void _jspService(HttpServletRequest request, HttpServletResponse

response) throws java.io.IOException, ServletException {

…. 略

}

_jspInit( ):当JSP 网页一开始执行时,最先执行此方法。因此,我们通常会把初始化的工作写在

此方法中。

_jspDestroy( ):JSP 网页最后执行的方法。

_jspService( ):JSP 网页最主要的程序都是在此方法中。

接下来笔者将HelloJSP.jsp 和HelloJSP_jsp.java 做一个简单的对照:

<%@ page contentType="text/html;charset=GB2312" %>

response.setContentType("text/html;charset=GB2312");

<%! int k = 0; %>

int k = 0; // 此为全局变量

<html>

<head>

<title>CH3 - HelloJSP.jsp</title>

</head>

<body>

<h2>JSP 将会被转译为 Servlet</h2>

out.write("\r\n");

out.write("\r\n\r\n");

out.write("<html>\r\n");

out.write("<head>\r\n ");

out.write("<title>CH3 - HelloJSP.jsp");

out.write("</title>\r\n");

out.write("</head>\r\n");

out.write("<body>\r\n\r\n");

out.write("<h2>JSP 将会被转译为 Servlet");

out.write("</h2>\r\n\r\n");

out.write("\r\n");

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:out value="Hi" />

if (_jspx_meth_c_out_0(pageContext))

return;

…. 略

private boolean _jspx_meth_c_out_0(PageContext pageContext)

throws Throwable {

JspWriter out = pageContext.getOut();

// c:out

org.apache.taglibs.standard.tag.rt.core.OutTag _jspx_th_c_out_0 =

(org.apache.taglibs.standard.tag.rt.core.OutTag) _jspx_tagPool_c_out_value.

get(org.apache.taglibs.standard.tag.rt.core.OutTag.class);

_jspx_th_c_out_0.setPageContext(pageContext);

_jspx_th_c_out_0.setParent(null);

_jspx_th_c_out_0.setValue(new String("Hi"));

int _jspx_eval_c_out_0 = _jspx_th_c_out_0.doStartTag();

if (_jspx_th_c_out_0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)

return true;

_jspx_tagPool_c_out_value.reuse(_jspx_th_c_out_0);

return false;

}

<%

String name = "browser";

out.println("大家好 !!");

%>

<%= name %>

String name = "browser";

out.println("大家好 !!");

out.write("\r\n");

out.print( name );

3-5 JSP 与ASP 和ASP+的比较

JSP 与ASP 的比较

一般说来,Sun 公司的JavaServer Pages(JSP)和Microsoft的Active Server Pages(ASP)

在技术方面有许多相似之处。两者都为动态网页的技术,并且双方都能够替代CGI 技术,使网站的

开发时程能够大大缩短,在性能上也有较高的表现,更重要的一点是,两者都能够为程序员提供组

件设计的功能,通过组件设计,将网页中逻辑处理部分交由组件负责处理(ASP 使用COM 组件、JSP

则有JavaBean 组件),而和网页上的排版、美工分离。

尽管JavaServer Pages 技术和Active Server Pages(ASP)在许多方面都很相似,但仍然存

在很多不同之处,其中本质上的区别在于:两者是来源于不同的技术规范组织。以下就来比较两大

技术有哪些不同点,而又为各自带来哪些优势。

平台和服务器的弹性

ASP (Active Server Pages)技术主要在微软(Microsoft)公司的Windows 平台上运行,其中包括

Windows 2000、Windows XP 和Windows 2003,并且搭配其WEB 服务器IIS (Internet Information

Services)。但是,在其他的平台运行时,不是性能低落,就是根本不支持,因此,当在开发网站系

统时,选择NT+IIS+ASP的体系结构时,未来当系统无法负荷时,也只能继续选择Windows 平台的

服务器,无法改写在性能表现相当优异的UNIX 平台上。

JSP (JavaServer Pages)技术主要运行在操作系统上的一个Java Virtual Machine (JVM)虚拟机器上,

因此,它能够跨越所有的平台,例如:NT、Windows 2000、Solaris、linux、OS/390、AIX、HP-UX ,

等等,除了能在各式各样的操作系统上执行,并且能搭配现有的WEB服务器:Apache、IIS、Netscape

Enterprise Server ,等等,将静态的HTML网页交由执行速度较快的Web Server 处理,而动态产生

网页的部分,就交由JSP Container 来执行。由上述可知,JSP (JavaServer Pages)技术在跨平台的表现

比ASP来得更有弹性。

WEB 网页程序员未来在开发电子商务平台时,就不需要再考虑客户厂商的操作系统平台,可更专

心于系统功能的开发。相应地,厂商在使用JavaServer Pages 技术开发的系统平台时,不再需要担

心未来在扩充软、硬件时,是否产生不兼容的问题。光这一点,就能为企业省下一大笔的费用,这

是JSP 的主要优点。

语法结构

ASP语法结构上,是以"<%"和"%>"作为标记符号,而JSP也是使用相同标记符号作为程序的区

段范围的。但不同的是,标记符号之间所使用的语言:ASP为javascript或VBScript;而JSP为Java。

Java 是有严格规划、强大且易扩充的语言,远优于VBScript语言。

Java 使程序员的工作在其他方面也变得一样容易、简单。例如:当ASP应用程序在Windows NT

系统可能会造成系统Crash (当机)时,由于JSP是在JVM上执行程序,且提供强大的异常事件处理

机制,因此,不会因为程序撰写的疏忽,而导致服务器操作系统的损毁。

并且Java 语言提供防止直接存取内存的功能,存取内存产生的错误,通常也正是造成服务器损

毁的最主要原因之一。最后,最重要的原因,Java语言是一个有严谨规范、有系统组织的语言,对

一个专业的Java 程序员来说,也真正达到 Learn Once,Write Anywhere(学一次,皆可开发)的境

界。

开放的开发环境

自从1995 年,Sun 公司已经开放技术与国际Java 组织合作开发和修改Java 技术与规范。针对

JSP 的新技术,Sun 公司授权工具供货商(如Macromedia)、同盟公司(如Apache、Netscape)、协

力厂商及其他公司。最近,Sun公司将最新版本的Servlet 2.4和JSP 2.0的源代码发放给Apache,以

求JSP与Apache紧密地相互发展。Apache、Sun和许多其他的公司及个人公开成立一个咨询机构,

以便任何公司和个人都能免费取得信息。(详见:http://jakarta.apache.org)

JSP应用程序接口(API)毫无疑问已经取得成功,并随着Java 组织不断扩大其应用的范围,目

前全力发展Java 技术的厂商不胜枚举,例如:最近IBM 公司强力推广的WebSphere 家族,正是完

全支持J2EE 标准而开发。数据库厂商Oracle 也发展自己的Application Server 来和自己公司本身数

据库产品Oracle 9i 做一紧密的结合。那也更不用提Amazon 系统的供货商BEA 公司,它的产品

WebLogic也是完全支持JavaServer Pages技术和J2EE 规范的。

相反,ASP 技术仅依靠微软本身的推动,其发展建立在独占、封闭的基础之上,并且微软本

身的技术又只允许在微软相关平台的服务器上执行,因此,在标准方面显得有点力不从心。

语法的延展性

ASP 和JSP 都使用标签与Scripting Language来制作动态WEB 网页,JavaServer Pages 2.0新

规范中,能够让程序员自由扩展JSP 标签来应用。JSP开发者能自定义标签库( Tag Library ),所

以网页制作者能充分利用与XML 兼容的标签技术强大的功能,大大减低对Java语法的依赖,并且也

可以利用XML强大的功能,做到数据、文件格式的标准化。相关标签库请参考“第十五章:JSP Tag

Library”,其中有更加完整的说明。

执行性能表现

ASP 和JSP 在执行性能的表现上,有一段显著的差距,JSP 除了在一开始加载的时间会比较久

外,之后的表现就远远比ASP 的表现来得好。原因在于:JSP 在一开始接受到请求时,会产生一份

Servlet 实体( instance ),它会先被暂存在内存中,我们称之为持续( Persistence ),当再有

相同请求时,这实体会产生一个线程(thread)来服务它。如果过了一段时间都不再用到此实体时,

Container 会自动将其释放,至于时间的长短,通常都是可以在Container 上自行设定的。

而 ASP在每次接收到请求时,都必须要重新编译,因此,JSP 的执行比每次都要编译执行的ASP

要快,尤其是程序中存在循环操作时,JSP 的速度要快上1 到2倍。不过,ASP在这部分的缺陷,将

随ASP+的出现有所改观,在新版的ASP+技术中,性能表现上有很大的突破。

JSP ASP+的比较

1. [玉玉3]面向对象性

c#为一种面向对象语言,从很多方面来看,c#将给ASP+带来类似于Java 的功能,并且它和

Windows 环境紧密结合,因此,具备更快的性能。笔者认为,C#是微软在市场上击败Java的主要工

具。

2. 数据库连接

ASP 最大的优点是它使用ADO 对象,因此,ASP Web数据库应用开发特别简单。ASP+发展了更多

的功能,因为有了ADO+,ADO+带来了更强大更快速的功能。JSP 和JDBC 目前在易用性和性能上和

ASP/ADO 相比已有些落后,当新版本ASP+/ADO+出现后这样的差别会更明显,这部分希望Sun 尽快追

赶ASP+/ADO+的组合。

3. 大型网站应用

ASP+将对大型网站有更好的支持。事实上,微软在这方面付出了巨大的努力, ASP+可以让你考

虑到多服务器(multiple servers)的场合,当你需要更强大的功能时,仅仅只需要增加一台服务器。

ASP+现在可以在大型项目方面与JSP 一样具有等同的能力,而且ASP+还有价格方面的优势,因为所

有的组件将是服务器操作系统的一部分。

结论:

除了上述ASP、ASP+和JSP 之外,笔者再提供一篇在网络上ASP 和JSP 比较的文章:

http://www.indiawebdevelopers.com/technology/Java/jsp.asp,希望能带给读者更客观的评论。

除了ASP 之外,php 和ColdFusion 皆为近年来常用来开发WEB 动态网页内容的工具,各开发工

具皆有其优、缺点。ASP 和PHP 最大的好处就是开发中、小型网站非常快速,市面上的书籍也较多,

学习起来能较快上手。尤其因为PHP 的环境大都为UNIX的环境,因此,在规划、构建时,所花需的

成本为最低,但PHP 并未将Presentation Layer和Business Layer 做一个适当的处理,因此,往

往一个系统越来越庞大、越来越复杂时,维护起来就会越来越吃力,并且本身并没有一个强而有力

的技术在支持它,当开发系统要求为分布式的体系结构时,那么PHP 可能就英雄无用武之地了。

3-6 JSP 2.0 新功能

J2EE 1.4 正式发布之后,Servlet 和JSP 同时也做___________了一些变动,Servlet 从2.3 更新至2.4;而JSP

从1.2 更新至2.0。两者平心而论,JSP的变动较Servlet来得多,其中JSP 2.0较JSP 1.2新增的功能

如下:

(1) Expression Language;

(2) 新增Simple Tag 和Tag File;

(3) web.xml 新增<jsp-config>元素。

3-6-1 Expression Language

JSP 2.0 之后,正式将EL 纳入JSP 的标准语法。EL 主要的功用在于简化JSP 的语法,方便Web

开发人员的使用。例如:

使用JSP 传统语法:

<%

String str_count = request.getParameter("count");

int count = Integer.parseInt(str_count);

count = count + 5;

out.println("count:" + count);

%>

使用EL 语法:

count:${param.count + 5}

对于 EL 的部分,本书的“第六章:Expression Language”有详尽的介绍。

3-6-2 新增Simple Tag Tag File

JSP 2.0提供一些较为简单的方法,让开发人员来撰写自定义标签。JSP 2.0提供两种新的机制,

分别为Simple Tag和Tag File。

Simple Tag Handler和其他Tag Handler(如:Body Tag Handler、Tag Handler和Iteration Tag Handler)

不同之处在于:Simple Tag Handler 并无doStartTag( )和doEndTag( ),它只有doTag( ),因此,实现

标签能比以往更为方便。

Tag File 就更为简单,你可以把它当做直接使用JSP 的语法来制作标签。例如:

Hello.tag

<%

out.println("Hello from tag file.");

%>

我们先制作一个名为Hello.tag 的Tag File,然后将它放置在WEB-INF/tags/ 目录下。在JSP

网页使用Hello.tag 的方法如下:

<%@ taglib prefix="myTag" tagdir="/WEB-INF/tags" %>

<myTag:Hello />

最后执行的结果如下:

Hello from tag file.

有关Simple Tag Handler和Tag File 的部分,在“第十六章:Simple Tag与Tag File”有更

详细的说明。

3-6-3 web.xml 新增<jsp-config>元素

<jsp-config> 元素主要用来设定JSP 相关配置, <jsp-config> 包括<taglib> 和

<jsp-property-group> 两个子元素。其中<taglib>元素在JSP 1.2 时就已经存在;而

<jsp-property-group>是JSP 2.0 新增的元素。

<jsp-property-group>元素主要有八个子元素,它们分别为:

<description>:设定的说明;

<display-name>:设定名称;

<url-pattern>:设定值所影响的范围,如:/CH2 或 /*.jsp;

<el-ignored>:若为true,表示不支持EL 语法;

<scripting-invalid>:若为true,表示不支持<% scripting %>语法;

<page-encoding>:设定JSP 网页的编码;

<include-prelude>:设置JSP 网页的抬头,扩展名为.jspf;

<include-coda>:设置JSP 网页的结尾,扩展名为.jspf。

图 3-3所示的所谓JSP网页的抬头为网页最上方的This banner included with <include-prelude>;

结尾为网页最下方的This banner included with <include-coda>。

图 3-3 Tomcat 上的<include-prelude>范例程序

其中抬头的源程序为:

prelude.jspf

<hr>

<center>

This banner included with &lt;include-prelude&gt;

</center>

<hr>

结尾的源程序为:

coda.jspf

<hr>

<center>

This banner included with &lt;include-coda&gt;

</center>

<hr>

下面是一个简单的<jsp-config>元素完整配置:

<jsp-config>

<taglib>

<taglib-uri>Taglib</taglib-uri>

<taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>

</taglib>

<jsp-property-group>

<description>

Special property group for JSP Configuration JSP example.

</description>

<display-name>JSPConfiguration</display-name>

<url-pattern>/jsp/* </url-pattern>

<el-ignored>true</el-ignored>

<page-encoding>GB2312</page-encoding>

<scripting-invalid>true</scripting-invalid>

<include-prelude>/include/prelude.jspf</include-prelude>

<include-coda>/include/coda.jspf</include-coda>

</jsp-property-group>

</jsp-config>

第四章 JSP 语法

4-1 Elements 和Template Data

JSP 网页主要分为Elements 与Template Data 两部分。

Template Data:JSP Container 不处理的部分,例如:HTML 的内容,会直接送到Client 端执

行。

Elements:必须经由JSP Container 处理的部分,而大部分Elements都以XML 作为语法基础,

并且大小写必须要一致

Elements 有两种表达式,第一种为起始标签(包含Element 名称、属性),中间为一些内容,最

后为结尾标签。如下所示:

<mytag attr1="attribute value" …..>

body

</mytag>

另一种是标签中只有Element 的名称、属性,称为Empty Elements。如下所示:

<mytag attr1="attribute value" ./>

Elements 有四种类型:Directive Elements、Scripting Elements、Action Elements 和EL

Elements,接下来的章节会针对前三种类型的Elements加以说明。至于EL Elements是JSP 2.0新

增的功能,笔者将在“第六章:Expression Language”中详细介绍它。

4-2 批注 (Comments)

一般批注可分为两种:一种为在客户端显示的批注;另外一种就是客户端看不到,只给开发程

序员专用的批注。

● 客户端可以看到的批注

<!-- comment [ <%= expression %> ] -->

例如:

<!-- 现在时间为: <%= (new java.util.Date()).toLocaleString() %> -->

在客户端的HTML 源文件中显示为:

<!--现在时间为:January 1, 2004 -->

这种批注的方式和HTML 中很像,它可以使用“查看源代码”来看到这些程序代码,但是惟一有

些不同的是,你可以在批注中加上动态的表达式(如上例所示)。

● 开发程序员专用的批注:

<%-- comment --%>

或者

<% /** this is a comment **/ %>

接下来看下面这个范例:

<%@ page language="java" %>

<html>

<head><title>A Comment Test</title></head>

<body>

<h2>A Test of Comments</h2>

<%-- 这个批注不会显示在客户端 --%>

</body>

</html>

从用户的浏览器中,看到的源代码如下:

<html>

<head><title>A Comment Test</title></head>

<body>

<h2>A Test of Comments</h2>

</body>

</html>

之前加上去的批注在客户端的浏览器上看不出来,并且用此批注的方式,在JSP 编译时会被忽

略掉。这对隐藏或批注JSP 程序是实用的方法,通常程序员也会利用它来调试(Debug)程序。

JSP Container不会对 <%--和--%>之间的语句进行编译,它不会显示在客户端的浏览器上,也

无法从源文件中看到。接下来介绍Quoting 和Escape 的规则。

4-3 Quoting 和Escape 规则

Quoting 主要是为了避免与语法产生混淆所使用的转换功能,和HTML 的标签语法类似,JSP 是

以<%标签作为程序的起始、%>标签作为程序的结束,所以当你在JSP 程序中要加上 <%或是%>这

些符号时,应该这么做:

Quoting.jsp

<%@ page contentType="text/html;charset=GB2312 " %>

<html>

<head>

<title>CH4 - Quoting.jsp</title>

</head>

<body>

<h2>Quoting 范例程序</h2>

<%

out.println("JSP %>作为结束符号");

%>

</body>

</html>

程序执行时,JSP 在执行到%>时,JSP Container 就直接告诉你程序有错误,如图4-1所示:

图4-1 Quoting.jsp 的执行结果

通常为了避免产生这样的结果,因此程序中遇到显示%>时,要改写为%\>,所以上面的程序代码

应改写为:

Quoting1.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH4 - Quoting1.jsp</title>

</head>

<body>

<h2>Quoting 范例程序 2</h2>

<%

out.println("JSP %\>作为结束符号");

%>

</body>

</html>

Quoting1.jsp 的执行结果如图4-2 所示:

图 4-2 Quoting1.jsp 的执行结果

除了 %> 要改为 %\>,当遇到<%、'、"、\ 时都要做适当修改,如下所示:

单引号 ' 改为 \'

双引号 " 改为 \"

斜线 \ 改为 \\

起始标签 <% 改为 &lt;%

结束标签 %> 改为 %\>

最后再举个例子,如下:

Quoting2.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH4 - Quoting2.jsp</title>

</head>

<body>

<h2>Quoting 范例程序 3</h2>

<%

out.println("JSP 遇到 \'、\"、\\、&lt;%、%\> 时需要做适当的修改");

%>

</body>

</html>

执行的结果如图4-3 所示:

图 4-3 Quoting2.jsp 的执行结果

4-4 Directives Elements(1)

指令(Directives)主要用来提供整个JSP 网页相关的信息,并且用来设定JSP网页的相关属性,

例如:网页的编码方式、语法、信息等。

起始符号为: <%@

终止符号为: %>

内文部分就是一些指令和一连串的属性设定,如下所示:

<%@ directive { attribute ="value" } * %>

什么叫做一连串的属性设定?举例来说,当我们设定两个属性时,可以将之合二为一,如下:

<%@ directive attribute1 = "value1" %>

<%@ directive attribute2 = "value2" %>

亦可以写成:

<%@ directive attribute1 = "value1" attribute2 = "value2" %>

在 JSP 1.2 的规范中,有三种指令:page、include 和taglib,每一种指令都有各自的属性。

接下来,我们会依序为各位读者介绍这三种指令。

JSP 2.0 新增Tag File 的功能,Tag File 是以.tag 作为扩展名的。在Tag File 中,__________因为它

并不是JSP 网页,所以不能使用page 指令,但是它可以使用include 和taglib 指令。除此之外,

Tag File 还有自己本身的指令可以使用,如:tag、attribute 和variable。

有关Tag File 的指令,笔者在“第十六章:Simple Tag 与Tag File”中再做详细介绍,本章

先暂时不谈论它。

4-4-1 page 指令

page 指令是最复杂的JSP指令,它的主要功能为设定整个JSP 网页的属性和相关功能。page指

令的基本语法如下:

<%@ page attribute1="value1" attribute2= "value2" attribute3=…%>

page 指令是以<%@ page 起始,以%>结束。像所有JSP 标签元素一样,page 指令也支持以XML

为基础的语法,如下所示:

<jsp:directive.page attribute1="value1" attribute2= "value2" />

page 指令有11 个属性,如表4-1 所示:

表 4-1

属性 定 义

language =

"scriptingLanguage"

主要指定JSP Container要用什么语言来编译JSP 网页。

JSP 2.0 规范中指出,目前只可以使用Java语言,不过未

来不排除增加其他语言,如C、C++、perl 等等。默认值

为Java

extends = "className" 主要定义此JSP 网页产生的Servlet 是继承哪个父类

import = "importList" 主要定义此JSP 网页可以使用哪些Java API

session = "true | false" 决定此JSP 网页是否可以使用session 对象。默认值为

true

buffer = "none | size in

kb"

决定输出流(output stream)是否有缓冲区。默认值为

8KB 的缓冲区

autoFlush = "true |

false"

决定输出流的缓冲区是否要自动清除,缓冲区满了会产生

异常 (Exception)。默认值为 true

isThreadSafe = "true | 主要是告诉JSP Container,此JSP 网页能处理超过一个

false" 以上的请求。默认值为true,如果此值设为false,

SingleThreadModel 将会被使用。SingleThreadModel 在

Servlet 2.4 中已经声明不赞成使用(deprecate)

info = "text" 主要表示此JSP 网页的相关信息

errorPage = "error_url" 表示如果发生异常错误时,网页会被重新指向那一个URL

isErrorPage = "true |

false"

表示此JSP Page 是否为处理异常错误的网页

contentType = "ctinfo" 表示MIME 类型和JSP 网页的编码方式

pageEncoding = "ctinfo" 表示JSP 网页的编码方式

isELIgnored = "true |

false"

表示是否在此JSP 网页中执行或忽略EL 表达式。如果为

true 时,JSP Container将忽略EL表达式;反之为false

时,EL 表达式将会被执行

下面的范例都合乎语法规则:

<%@ page info = "this is a jsp page"%>

<%@ page language = "java" import = "java.net.* " %>

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

下面的范例也是page 指令,不过并不合乎语法规则,因为session 属性重复设定两次:

<%@ page language = "java" import = "java.net.* " session = "false" buffer = "16kb"

autoFlush = "false" session = "false" %>

注意:只有import 这个属性可以重复设定,其他则否。

<%@ page import = "java.net.* " %>

<%@ page import = "java.util.List " %>

另外再举个较常见的错误例子:

<%@ page language="java" contentType="text/html";charset ="Big5" %>

应该改为:

<%@ page language="java" contentType="text/html;charset=Big5" %>

通常我们都以为只要把charset 设为Big5的编码方式,就能够顺利显示出所需要的中文,不过

有时候在显示一些特别的中文字时,例如:碁,会变成乱码。如下范例:

Big5.jsp

<%@ page contentType="text/html;charset=Big5" %>

<html>

<head>

<title>CH4 - Big5.jsp</title>

</head>

<body>

<h2>使用 Big5 編碼,無法正確顯示某些中文字</h2>

<%

out.println("宏?砦q 脑公司");

%>

</body>

</html>

Big5.jsp 的执行结果如图4-4 所示:

图 4-4 Big5.jsp 的执行结果

那么我们应该如何解决这个问题?很简单,只要把之前的charset = Big5 改为charset = MS950

的编码方式就能够解决这个问题,我们看下面这个范例:MS950.jsp。

MS950.jsp

<%@ page contentType="text/html;charset=MS950" %>

<html>

<head>

<title>CH4 - MS950.jsp</title>

</head>

<body>

<h2>使用 MS950 編碼,能正確顯示"碁"</h2>

<%

out.println("宏碁電腦公司");

%>

</body>

</html>

MS950.jsp 的执行结果如图4-5:

图 4-5 MS950.jsp 的执行结果

使用最基本的page 指令的范例程序 (二):Date.jsp

Date.jsp

<%@ page contentType="text/html;charset=GB2312 " %>

<%@ page import="java.util.Date" %>

<html>

<head>

<title>CH4 - Date.jsp</title>

</head>

<body>

<h2>使用 java.util.Date 显示目前时间</h2>

<%

Date date = new Date();

out.println("现在时间:"+date);

%>

</body>

</html>

因为Date.jsp 要显示出现在的时间,所以要先导入(import) java.util 这个套件,才可以使

用Date( )类。执行结果如图4-6 所示:

图 4-6 Date.jsp 的执行结果

如果在一个JSP 网页同时须要导入很多套件时:

<%@ page import="java.util.Date" %>

<%@ page import="java.text.*" %>

亦可以写为:

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

直接使用逗号“,”分开,然后就可以一直串接下去。

4-4-2 include 指令

include 指令表示:在JSP 编译时插入一个包含文本或代码的文件,这个包含的过程是静态的,

而包含的文件可以是JSP 网页、HTML 网页、文本文件,或是一段Java 程序。

注意

包含文件中要避免使用<html>、</html>、<body>、</body>,因为这将会影响在原来JSP 网

页中同样的标签,这样做有时会导致错误。

include 指令的语法如下:

<%@ include file = "relativeURLspec" %>

include 指令只有一个属性,那就是file,而relativeURLspec 表示此file 的路径。像所有

JSP 标签元素一样,include 指令也支持以XML 为基础的语法,如下所示:

<jsp:directive.include file = "relativeURLspec" />

注意

<%@ include %>指令是静态包含其他的文件。所谓的静态是指file 不能为一变量URL,例

如:

<% String URL="JSP.html" ; %>

<%@ include file = "<%= URL %>" %>

也不可以在file 所指定的文件后接任何参数,如下:

<%@ include file = "javaworld.jsp?name=browser" %>

同时,file 所指的路径必须是相对于此JSP 网页的路径。

笔者写了一个Include.jsp范例程序,它include一份HTML的网页,网页文件名叫做Hello.html,

看看执行之后,会有什么结果产生。

Include.jsp

<%@ page contentType="text/html;charset=GB2312" %>

<html>

<head>

<title>CH4 - Include_Html.jsp</title>

</head>

<body>

<h2>include 指令</h2>

<%@ include file="Hello.html" %>

<%

out.println("欢迎大家进入JSP 的世界");

%>

</body>

</html>

Hello.html

JSP 2.0 Tech Reference<br>

执行结果如图4-7。

注意

Hello.html 网页内容中有中文时,Tomcat 5.0.16 执行Include.jsp 时,无法正确显示

Hello.html 网页的中文,会产生乱码。但是笔者使用Resin 来执行时,却可以顺利显示中

文。

图 4-7 Include.jsp 的执行结果

4-4-3 taglib 指令

taglib 指令是JSP 1.1新增进来的功能,能够让用户自定义新的标签。这里只是先做一个简单

介绍,在第十五章再为各位读者详细介绍。

taglib 指令的语法如下:

<%@ taglib uri = "tagLibraryURI" prefix="tagPrefix" %>

像所有JSP标签元素一样,taglib指令也支持以XML为基础的语法,如下所示(见表4-2):

<jsp:directive.taglib uri = "tagLibraryURI" prefix="tagPrefix" />

表4-2

属性 定 义

uri = "tagLibraryURI" 主要是说明taglibrary 的存放位置

prefix="tagPrefix" 主要用来区分多个自定义标签

范例

<%@ taglib uri ="/supertags/" prefix="super" %>

……….

<super:doMagic>

……..

……..

</super:doMagic>

4-5 Scripting Elements

Scripting Elements 包含三部分:

1. 声明(Declarations)

2. Scriptlets

3. 表达式 (Expressions)

示例

<%! 这是声明 %>

<% 这是Scriptlets %>

<%= 这是表达式 %>

4-5-1 声明 (Declarations)

在JSP 程序中声明合法的变量和方法。声明是以<%! 为起始;%> 为结尾。

声明的语法:

<%! declaration; [ declaration; ]+ ... %>

范例:

<%! int i = 0; %>

<%! int a, b, c; %>

<%! Circle a = new Circle(2.0); %>

<%! public String f(int i) {if ( i < 3 ) return ("i 小于3")}; %>

使用<%! %>可以声明你在JSP 程序中要用的变量和方法,你可以一次声明多个变量和方法,只

要最后以分号“;”结尾就行,当然这些声明在Java 中要是合法的。

每一个声明仅在一个页面中有效,如果你想每个页面都用到一些声明,最好把它们写成一个单

独的JSP 网页,然后用<%@ include %>或<jsp:include >元素包含进来。

注意

使用<%! %>方式所声明的变量为全局变量,即表示:若同时n 个用户在执行此JSP网页时,

将会共享此变量。因此笔者强烈建议读者,千万别使用<%! %>来声明您的变量。

若要声明变量时,请直接在<% %>之中声明使用即可。

4-5-2 Scriptlets

Scriptlet 中可以包含有效的程序片段,只要是合乎Java 本身的标准语法即可。通常我们主要

的程序也是写在这里面,Scriptlet 是以 <% 为起始;%> 为结尾。

Scriptlet 的语法:

<% code fragment %>

范例:

<%

String name = null;

if (request.getParameter("name") == null) {

%>

………

………

<%

}

else

{

out.println("HI … "+name);

}

%>

Scriptlet 能够包含多个语句,方法,变量,表达式,因此它能做以下的事:

(1) 声明将要用到的变量或方法;

(2) 显示出表达式;

(3) 使用任何隐含的对象和使用<jsp:useBean>声明过的对象,编写JSP 语句 (如果你在使用

Java 语言,这些语句必须遵从Java Language Specification);

(4) 当JSP收到客户端的请求时,Scriptlet 就会被执行,如同Servlet的doGet()、doPost(),

如果Scriptlet 有显示的内容会被存在out 对象中,然后再利用out 对象中的println()方法显示

出结果。

4-5-3 表达式 (Expressions)

Expressions 标签是以<%= 为起始;%> 为结尾,其中间内容包含一段合法的表达式,例如:

使用Java 的表达式。

Expressions 的语法:

<%= expression %>

范例:

<font color="blue"><%= getName() %></font>

<%= (new java.util.Date()).toLocaleString() %>

表达式在执行后会被自动转化为字符串,然后显示出来。

当你在JSP 中使用表达式时请记住以下几点:

1. 不能使用分号“;”来作为表达式的结束符号,如下:

<%= (new java.util.Date()).toLocaleString(); %>

但是同样的表达式用在Scriptlet 中就需要以分号来结尾了。

2.这个表达式元素能够包括任何Java 语法,有时候也能作为其他JSP 元素的属性值。

第四章 JSP 语法

4-6 Action Elements

JSP 2.0 规范中定义一些标准action 的类型,JSP Container 在实现时,也完全遵照这个规范

而制定。Action 元素的语法以XML 为基础,所以,在使用时大小写是有差别的,例如:

<jsp:getproperty>和<jsp:getProperty>是有所差别的,因此在撰写程序时,必须要特别注意。

目前JSP 2.0 规范中,主要有20 项Action 元素:

<jsp:useBean>

<jsp:setProperty>

<jsp:getProperty>

<jsp:include>

<jsp:forward>

<jsp:param>

<jsp:plugin>

<jsp:params>

<jsp:fallback>

<jsp:root>

<jsp:declaration>

<jsp:scriptlet>

<jsp:expression>

<jsp:text>

<jsp:output>

<jsp:attribute>

<jsp:body>

<jsp:element>

<jsp:invoke>

<jsp:doBody>

笔者将这20 个action 元素分为五类:

第一类有3个action元素,它们都用来存取JavaBean,因此这部分将在“第八章:JSP与JavaBean”

详细地介绍。

第二类有6 个action 元素,这部分是JSP 1.2 原有的action 元素,接下来将会介绍它们。

第三类有6 个action 元素,它们主要用在JSP Document 之中。其中<jsp:output>是JSP 2.0

新增的元素。

第四类有3 个action 元素,它们主要用来动态产生XML 元素标签的值,这3 个都是在JSP 2.0

中加入进来的元素。

第五类有2 个action 元素,它们主要用在Tag File 中,这部分将在“第十六章:Simple Tag

与Tag File”再来介绍。

补充

JSP Document:使用XML 语法所写成的JSP 网页。例如:

<jsp:scriptlet>

String name="Mike";

</jsp:scriptlet>

Hi !<jsp:expression>name</jsp:expression>

4-6-1 <jsp:include>

<jsp:include>元素允许你包含动态和静态文件,这两种产生的结果是不尽相同的。如果包含进

来的只是静态文件,那么只是把静态文件的内容加到JSP 网页中;如果包含进来的为动态文件,那

么这个被包含的文件也会被JSP Container 编译执行。

一般而言,你不能直接从文件名称上来判断一个文件是动态的还是静态的,例如:Hello.jsp 就

有可能只是单纯包含一些信息而已,而不须要执行。但是<jsp:include>能够自行判断此文件是动态

的还是静态的,于是能同时处理这两种文件。

<jsp:include>的语法:

<jsp:include page="{urlSpec | <%= expression %>}" flush="true | false " />

<jsp:include page="{urlSpec | <%= expression %>}" flush="true | false" >

<jsp:param name="PN" value="{PV | <%= expression %>}" /> *

</jsp:include>

说明:

<jsp:include>有两个属性:page 和flush。

page:可以代表一个相对路径,即你所要包含进来的文件位置或是经过表达式所运算出的相对路径。

flush:接受的值为boolean,假若为true,缓冲区满时,将会被清空。flush 的默认值为false。

在此需要补充一点:在JSP 1.2 之前,flush 必须设为true。

你还可以用<jsp:param>传递一个或多个参数给JSP 网页。

范例:

<jsp:include page="scripts/Hello.jsp" />

<jsp:include page="Hello.html" />

<jsp:include page="scripts/login.jsp">

<jsp:param name="username" value="browser" />

<jsp:param name="password" value="1234" />

</jsp:include>

4-6-2 <jsp:forward>

<jsp:forward>这个标签的__________定义:将客户端所发出来的请求,从一个JSP 网页转交给另一个JSP

网页。不过有一点要特别注意,<jsp:forward>标签之后的程序将不能执行。笔者用例子来说明:

<%

out.println("会被执行 !!! ");

%>

<jsp:forward page="Quoting2.jsp">

<jsp:param name="username" value="Mike" />

</jsp:forward>

<%

out.println("不会执行 !!!");

%>

上面这个范例在执行时,会打印出“会被执行!!!”,不过随后马上会转入到SayHello.jsp 的

网页中,至于out.println("不会执行 !!! ") 将不会被执行。

<jsp:forward>的语法:

<jsp:forward page={"relativeURL" | "<%= expression %>"} />

<jsp:forward page={"relativeURL" | "<%= expression %>"} >

<jsp:param name="PN" value="{PV | <%= expression %>}" /> *

</jsp:forward>

说明:

如果你加上<jsp:param>标签,你就能够向目标文件传送参数和值,不过这些目标文件必须也是

一个能够取得这些请求参数的动态文件,例如:.cgi、.php、.asp 等等。

<jsp:forward>只有一个属性page。page 的值,可以是一个相对路径,即你所要重新导向的网页

位置,亦可以是经过表达式运算出的相对路径。

范例

<jsp:forward page="/SayHello.jsp" />

或者

<jsp:forward page="/SayHello.jsp">

<jsp:param name="username" value="Mike" />

</jsp:forward>

4-6-3 <jsp:param>

<jsp:param>用来提供key/value 的信息,它也可以与<jsp:include>、<jsp:forward>和

<jsp:plugin> 一起搭配使用。

当在用<jsp:include>或者<jsp:forward>时,被包含的网页或转向后的网页会先看看request

对象里除了原本的参数值之外,有没有再增加新的参数值,如果有增加新的参数值时,则新的参数

值在执行时,有较高的优先权。例如:

一个request 对象有一个参数A = foo;另一个参数A = bar 是在转向时所传递的参数,则网

页中的request 应该会为A = bar,foo。注意:新的参数值有较高的优先权。

<jsp:param>的语法:

<jsp:param name="ParameterName" value="ParameterValue" />

<jsp:param>有两个属性:name 和value。name 的值就是parameter 的名称;而value 的值就

是parameter 的值。

范例:

<jsp:param name="username" value="Mike" />

<jsp:param name="password" value="Mike007" />

4-6-4 <jsp:plugin><jsp:params><jsp:fallback>

<jsp:plugin>用于在浏览器中播放或显示一个对象(通常为Applet 或Bean)。

当 JSP 网页被编译后送往浏览器执行时,<jsp:plugin>将会根据浏览器的版本替换成<object>

标签或者<embed>标签。一般来说,<jsp:plugin>会指定对象Applet 或Bean,同样也会指定类的名

字和位置,另外还会指定将从哪里下载这个Java 组件。

注意

<object>用于HTML 4.0,<embed>用于HTML 3.2。

<jsp:plugin>的语法:

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

[ iepluginurl="URL" ] >

[ <jsp:params>

[ <jsp:param name="PN" value="{PV | <%= expression %>}" /> ] +

</jsp:params> ]

[ <jsp:fallback> text message for user </jsp:fallback> ]

</jsp:plugin>

说明:

● type="bean | applet":

对将被执行的对象类型,你必须指定是Bean 还是Applet,因为这个属性没有默认值。

● code="objectCode":

将被Java Plugin 执行的Java 类名称,必须以.class 结尾,并且 .class 类文件必须存在于

codebase 属性所指定的目录中。

● codebase="objectCodebase":

如果你没有设定将被执行的Java 类的目录(或者是路径)的属性,默认值为使用<jsp:plugin>

的JSP 网页所在目录。

● align="alignment" :

图形、对象、Applet 的位置。align 的值可以为:

bottom、top、middle、left、right

● archive=" archiveList":

一些由逗号分开的路径名用于预先加载一些将要使用的类,此做法可以提高Applet 的性能。

● name=" ComponentName":

表示这个Bean 或Applet 的名字。

● height="height" width="width":

显示Applet 或Bean 的长、宽的值,单位为像素 ( pixel )。

● hspace="hspace" vspace="vspace":

表示Applet 或Bean 显示时在屏幕左右、上下所需留下的空间,单位为像素 ( pixel )。

● jreversion="jreversion":

表示Applet 或Bean 执行时所需的Java Runtime Environment (JRE)版本,默认值是1.1。

● nspluginurl="URL":

表示Netscape Navigator 用户能够使用的JRE 的下载地址,此值为一个标准的URL。

● iepluginurl="URL":

表示IE 用户能够使用的JRE 的下载地址,此值为一个标准的URL。

● <jsp:params>

[ <jsp:param name="PN" value="{PV | <%= expression %>}" /> ] +

</jsp:params>

你可以传送参数给Applet 或Bean。

● <jsp:fallback> unable to start plugin </jsp:fallback>

一段文字用于:当不能启动Applet 或Bean 时,那么浏览器会有一段错误信息。

范例:

<jsp:plugin type="applet" code="Molecule.class" codebase="/html">

<jsp:params>

<jsp:param name="molecule" value="molecules/benzene.mol" />

</jsp:params>

<jsp:fallback>

<p>Unable to start plugin</p>

</jsp:fallback>

</jsp:plugin>

4-6-5 <jsp:element><jsp:attribute><jsp:body>

<jsp:element>元素用来动态定义XML 元素标签的值。

<jsp:element >的语法:

<jsp:element name="name">

本体内容

</jsp:element>

<jsp:element name="name">

<jsp:attribute>

</jsp:attribute>

<jsp:body>

</jsp:body>

</jsp:element>

<jsp:element>只有一个属性name。name 的值就是XML 元素标签的名称。

范例1

<

Servlet 2.4 简介

第二章 servlet 2.4 简介

2-1 servlet 简介

自1997 年3月Sun Microsystems 公司所组成的JavaSoft部门将Servlet API 定案以来,推出

了Servlet API 1.0。就当时功能来说,Servlet 所提供的功能包含了当时的CGI (Common Gateway

Interface)与Netscape Server API (NSAPI)之类产品的功能。

发展至今,Servlet API的最新版本为2.4版。它依旧是一个具有跨平台特性、100% Pure Java

的Server-Side 程序,它相对于在Client 端执行的Applet。Servlet 不只限定于HTTP 协议,开发

人员可以利用Servlet自定义或延伸任何支持Java的Server —— 包括 Web Server、Mail Server、

Ftp Server、Application Server 或任何自定义的Server。

Servlet有以下优点:

● 可移植性(Portability)

Servlet 皆是利用Java 语言来开发的,因此,延续Java 在跨平台上的表现,不论Server 的操

作系统是Windows、Solaris、linux、HP-UX、FreeBSD、Compaq Tru 64、AIX 等等,都能够将我们

所写好的Servlet程序放在这些操作系统上执行。借助Servlet的优势,就可以真正达到Write Once,

Serve Anywhere 的境界,这正是从事Java 程序员最感到欣慰也是最骄傲的地方。

当程序员在开发Applet 时,时常为了“可移植性”(portability)让程序员感到绑手绑脚的,

例如:开发Applet 时,为了配合Client端的平台( 即浏览器版本的不同,plug-in 的JDK版本也

不尽相同 ),达到满足真正“跨平台”的目的时,需要花费程序员大量时间来修改程序,为的就是

能够让用户皆能够执行。但即使如此,往往也只能满足大部分用户,而其他少数用户,若要执行

Applet,仍须先安装合适的JRE (Java Runtime Environment)。

但是Servlet 就不同了,主要原因在于Servlet 是在Server 端上执行的,所以,程序员只要专

心开发能在实际应用的平台环境下测试无误即可。除非你是从事做Servlet Container 的公司,否

则不须担心写出来的Servlet 是否能在所有的Java Server 平台上执行。

强大的功能

Servlet 能够完全发挥Java API 的威力,包括网络和URL 存取、多线程(Multi-Thread)、影像

处理、RMI (Remote Method Invocation)、分布式服务器组件 (Enterprise Java Bean)、对象序列

化 (Object Serialization) 等。若想写个网络目录查询程序,则可利用JNDI API;想连接数据库

则可利用JDBC,有这些强大功能的API 做后盾,相信Servlet 更能够发挥其优势。

性能

Servlet 在加载执行之后,其对象实体(instance)通常会一直停留在Server 的内存中,若有请

求(request)发生时,服务器再调用Servlet 来服务,假若收到相同服务的请求时,Servlet会利用

不同的线程来处理,不像CGI 程序必须产生许多进程 (process)来处理数据。在性能的表现上,大

大超越以往撰写的CGI 程序。最后补充一点,那就是Servlet 在执行时,不是一直停留在内存中,

服务器会自动将停留时间过长一直没有执行的Servlet 从内存中移除,不过有时候也可以自行写程

序来控制。至于停留时间的长短通常和选用的服务器有关。

安全性

Servlet也有类型检查(Type Checking)的特性,并且利用Java的垃圾收集(Garbage Collection)

与没有指针的设计,使得Servlet 避免内存管理的问题。

由于在Java的异常处理(Exception-Handling)机制下,Servlet能够安全地处理各种错误,不会

因为发生程序上逻辑错误而导致整体服务器系统的毁灭。例如:某个Servlet发生除以零或其他不合

法的运算时,它会抛出一个异常(Exception)让服务器处理,如:记录在记录文件中(log file)。

2-2 First Servlet Sample Code

HelloServlet.java

package tw.com.javaworld.CH2;

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class HelloServlet extends HttpServlet {

//Initialize global variables

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

//Process the HTTP Get request

public void doGet(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=GB2312");

PrintWriter out = response.getWriter();

out.println("<html>");

out.println("<head><title>CH2 - HelloServlet</title></head>");

out.println("<body>");

out.println(" Hello World <br>");

out.println("大家好");

out.println("</body>");

out.println("</html>");

out.close();

}

//Get Servlet information

public String getServletInfo() {

return "tw.com.javaworld.CH2.HelloSerlvet Information";

}

}

注意:HelloServlet.java 范例程序位于JSPBook\WEB-INF\src\tw\com\javaworld\CH2,其中

JSPBook 范例程序的安装方法,请参见1-3节“安装JSPBook站台范例” 和1-4 节“安装Ant 1.6”。

一开始我们必须导入(import) javax.servlet.*、javax.servlet.http.*。

javax.servlet.* 存放与HTTP 协议无关的一般性Servlet 类;

javax.servlet.http.* :除了继承javax.servlet.* 之外,并且还增加与HTTP协议有关的功能。

所有Servlet 都必须实现javax.servlet.Servlet 接口(Interface),但是通常我们都会从

javax.servlet.GenericServlet 或javax.servlet.http.HttpServlet 择一来实现。若写的Servlet

程序和HTTP 协议无关,那么必须继承GenericServlet 类;若有关,就必须继承HttpServlet 类。

javax.servlet.* 里的ServletRequest和ServletResponse接口提供存取一般__________的请求和响应;

而javax.servlet.http.* 里的HttpServletRequest 和HttpServletResponse 接口,则提供HTTP

请求及响应的存取服务。

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

这个例子中,一开始和Applet 一样,也有init( )的方法。当Servlet 被Container 加载后,

接下来就会先执行init( )的内容,因此,我们通常利用init( )来执行一些初始化的工作。

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException,IOException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

out.println("<html>");

out.println("<head><title>CH2 - HelloServlet</title></head>");

out.println("<body>");

out.println("Hello World <br>");

out.println("大家好");

out.println("</body>");

out.println("</html>");

out.close();

}

Servlet 可以利用HttpServletResponse 类的setContentType( )方法来设定内容类型,我们要

显示为HTML 网页类型,因此,内容类型设为"text/html",这是HTML 网页的标准MIME 类型值。之

后,Servlet用getWriter( )方法取得PrintWriter类型的out对象,它与PrintSteam 类似,但是

它能对Java 的Unicode 字符进行编码转换。最后,再利用out 对象把"Hello World" 的字符串显

示在网页上。

public void destroy( ){

…………

…………. Servlet 结束时,会自动调用执行的程序

………….

}

若当Container 结束Servlet时,会自动调用destroy( ),因此,我们通常利用destroy( )来

关闭资源或是写入文件,等等。

编译 HelloServlet.java 的方法:

(1) 将servlet-api.jar 加入至CLASSPATH之中,直接使用javac 来编译HelloServlet.java。

其中servlet-api.jar 可以在{Tomcat_Install}\common\lib 找到。

(2) 直接使用Ant 方式编译HelloServlet.java,请参见1-4 节“安装Ant 1.6”。

编译好HelloServlet.java 之后,再来设定web.xml,如下:

<servlet>

<servlet-name>HelloServlet</servlet-name>

<servlet-class>tw.com.javaworld.CH2.HelloServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>HelloServlet</servlet-name>

<url-pattern>/HelloServlet</url-pattern>

</servlet-mapping>

最后,HelloServlet.java 的执行结果如图2-1 所示。

图 2-1 HelloServlet.java 的执行结果

顺利完成第一个Servlet 程序后,不知道读者有没有发现,在HelloServlet.java 主程序中,

其实大部分都是一些用来显示HTML 的out.println("….")程序代码,这就是Servlet 用在开发

Web-based 系统时最麻烦的地方。假若Servlet 要显示表格统计图时,我想那时候程序员一定会疯

掉,因为你会发现,其实你所有的时间都在out.println( ),因此,Servlet 适合在简单的用户接

口(User Interface)系统中。不过,幸好有JSP 技术来解决这项极为不方便的问题。

2-3 Servlet 的生命周期

当 Servlet加载Container 时,Container可以在同一个JVM 上执行所有Servlet,所以Servlet

之间可以有效地共享数据,但是Servlet 本身的私有数据亦受Java 语言机制保护。

Servlet 从产生到结束的流程如图2-2 所示。

(1) 产生Servlet,加载到Servlet Engine中,然后调用init( )这个方法来进行初始化工作。

(2) 以多线程的方式处理来自Client 的请求。

(3) 调用destroy( )来销毁Servlet,进行垃圾收集 (garbage collection)。

Servlet 生命周期的定义,包括如何加载、实例化、初始化、处理客户端请求以及如何被移除。

这个生命周期由javax.servlet.Servlet 接口的init ( )、service( )和destroy( )方法表达。

图 2-2 Servlet 从产生到结束的流程

1. 加载和实例化

当Container一开始启动,或是客户端发出请求服务时,Container会负责加载和实例化一个Servlet。

2. 初始化

Servlet 加载并实例化后,再来Container必须初始化Servlet。初始化的过程主要是读取配置

信息(例如JDBC连接)或其他须执行的任务。我们可以借助ServletConfig 对象取得Container的

配置信息,例如:

<servlet>

<servlet-name>HelloServlet</servlet-name>

<servlet-class>tw.com.javaworld.CH2.HelloServlet</servlet-class>

<init-param>

<param-name>user</param-name>

<param-value>browser</param-value>

</init-param>

</servlet>

其中user 为初始化的参数名称;browser 为初始化的值。因此,可以在HelloServlet程序中使

用ServletConfig 对象的getInitParameter("user")方法来取得browser。

3. 处理请求

Servlet 被初始化后,就可以开始处理请求。每一个请求由ServletRequest 对象来接收请求;

而ServletResponse 对象来响应该请求。

4. 服务结束

当 Container 没有限定一个加载的Servlet 能保存多长时间,因此,一个Servlet 实例可能只

在Container 中存活几毫秒,或是其他更长的任意时间。一旦destroy( )方法被调用时,Container

将移除该Servlet,那么它必须释放所有使用中的任何资源,若Container 需要再使用该Servlet

时,它必须重新建立新的实例。

2-4 Servlet 范例程序

为了说明Servlet 和网页是如何沟通的,笔者在此举一个Sayhi 的范例程序。这个范例程序分

为两部分:Sayhi.html 和Sayhi.java。

在 Sayhi.html 中,用户可以填入姓名,然后按下【提交】后,将数据传到Sayhi.java做处理,

而Sayhi.java 负责将接收到的数据显示到网页上。

Sayhi.html

<html>

<head>

<title>CH2 - Sayhi.html</title>

<meta http-equiv="Content-Type" content="text/html; charset=GB2312">

</head>

<body>

<h2>Servlet 范例程序</h2>

<form name="Sayhi" Method="Post" action="/JSPBook/CH2/Sayhi" >

<p>请访问者输入姓名:<input type="text" name="Name" size="30"></p>

<input type="submit" value="提交">

<input type="reset" value="清除">

</form>

</body>

</html>

Sayhi.html 的执行结果如图2-3 所示。

图 2-3 Sayhi.html 的执行结果

Sayhi.java

package tw.com.javaworld.CH2;

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class Sayhi extends HttpServlet {

//Initialize global variables

public void init(ServletConfig config) throws ServletException {

super.init(config);

}

//Process the HTTP Get request

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=GB2312");

PrintWriter out = response.getWriter();

request.setCharacterEncoding("GB2312");

String Name = request.getParameter("Name");

out.println("<html>");

out.println("<head><title>CH2 - Sayhi</title></head>");

out.println("<body>");

out.println("Hi:"+Name);

out.println("</body>");

out.println("</html>");

out.close();

}

//Get Servlet information

public String getServletInfo() {

return "tw.com.javaworld.CH2.Sayhi Information";

}

public void destroy() {

}

}

从Sayhi.java的程序当中,可以发现Servlet是利用HttpServletRequest类的getParameter( )

方法来取得由网页传来的数据。不过数据通过HTTP协议传输时会被转码,因此在接收时,必须再做转

码的工作,才能够正确地接收到数据。下面这段程序是做转码的动作:

request.setCharacterEncoding("GB2312");

编译Sayhi.java 之后,再来设定web.xml:

<servlet>

<servlet-name>Sayhi</servlet-name>

<servlet-class>tw.com.javaworld.CH2.Sayhi</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Sayhi</servlet-name>

<url-pattern>/CH2/Sayhi</url-pattern>

</servlet-mapping>

执行http://localhost:8080/JSPBook/CH2/Sayhi,结果如图2-4 所示。

图 2-4 Sayhi.html 按下【提交】后,经过Sayhi.java 处理后的结果

2-5 Servlet 2.4 的新功能

2003 年11 月底,J2EE 1.4 规范正式发布,Servlet也从原本的2.3版升级至2.4版。其中主要

新增的功能有以下三点:

(1) web.xml DTD 改用XML Schema;

(2) 新增Filter 四种设定;

(3) 新增Request Listener、Event 和Request Attribute Listener、Event。

2-5-1 web.xml 改用XML Schema

Servlet 在2.4 版之前,web.xml 都是使用DTD(Document Type Definition)来定义XML 文件内

容结构的,因此,Servlet 2.3 版 web.xml 一开始的声明如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

…………

</web-app>

到了Servlet 2.4 版之后,web.xml 改为使用XML Schema,此时web.xml 的声明如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

…………

</web-app>

由DTD 改为Schema,主要加强两项功能:

(1) 元素可不依照顺序设定;

(2) 更强大的验证机制。

下面的范例,在Servlet 2.3 版是不合规则的web.xml 文件:

<web-app>

...

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletA</servlet-name>

<url-pattern>/ServletA/*</url-pattern>

</servlet-mapping>

<servlet>

<servlet-name>ServletB</servlet-name>

<servlet-class> tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletB</servlet-name>

<url-pattern>/ServletB /*</url-pattern>

</servlet-mapping>

...

</web-app>

因为<servlet-mapping>元素必须在<servlet>元素之后,因此,上述的范例要改为:

<web-app>

...

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet>

<servlet-name>ServletB</servlet-name>

<servlet-class> tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletA</servlet-name>

<url-pattern>/ServletA/*</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>ServletB</servlet-name>

<url-pattern>/ServletB /*</url-pattern>

</servlet-mapping>

...

</web-app>

不过在Servlet 2.4版之后,原来的范例也算是一个合法的web.xml文件,不再须注意元素的顺序。

除此之外,Servlet 2.4 版web.xml 的Schema 更能提供强大的验证机制,例如:

(1) 可检查元素的值是否为合法的值。例如:<filter-mapping>的<dispatcher>元素,其值只

能为REQUEST、FORWARD、INCLUDE 和ERROR,如下所示:

<filter-mapping>

<filter-name>Hello</filter-name>

<url-pattern>/CH11/*</url-pattern>

<dispatcher>REQUEST</dispatcher>

<dispatcher>FORWARD</dispatcher>

</filter-mapping>

若<dispatcher>元素的值不为上述四种时,此web.xml 将会发生错误。

(2) 可检查如Servlet、Filter 或EJB-ref 等等元素的名称是否惟一。例如:

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletA</servlet-class>

</servlet>

<servlet>

<servlet-name>ServletA</servlet-name>

<servlet-class>tw.com.javaworld.servlet.ServletB</servlet-class>

</servlet>

(3) 可检查元素值是否为合法文字字符或数字字符。例如:

<filter-mapping>

<filter-name>Hello</filter-name>

<url-pattern>/CH11/*</url-pattern>

</filter-mapping>

2-5-2 新增Filter 四种设定

Servlet 2.3 版新增了Filter的功能,不过它只能由客户端发出请求来调用Filter,但若使用

RequestDispatcher.forward( )或RequestDispatcher.include( )的方法调用Filter 时,Filter

却不会执行。因此,在Servlet 2.4版中,新增Filter的设定<dispatcher>来解决这个问题。有关

Filter 的部分在本书“第十一章:Filter 与Listener”有更详细的介绍。

Servlet 2.4 版新增的Filter 四种设定为:REQUEST、FORWARD、INCLUDE 和ERROR。假若你有

一个SimpleFilter,它只允许由客户端发出请求或由RequestDispatcher.include( )的方式来调用

执行SimpleFilter,此时SimpleFilter 的设定如下:

<filter>

<filter-name>SimpleFilter</filter-name>

<filter-class>tw.com.javaworld.CH11.SimpleFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>SimpleFilter</filter-name>

<url-pattern>/CH11/*</url-pattern>

<dispatcher>REQUEST</dispatcher>

<dispatcher>INCLUDE</dispatcher>

</filter-mapping>

2-5-3 新增Request ListenerEvent Request Attribute ListenerEvent

在Servlet 2.3 版中,新增许多的Listener 接口和Event 类(见表2-1):

表 2-1

Listener 接口Event 类

ServletContextListener ServletContextEvent

ServletContextAttributeListener ServletContextAttributeEvent

HttpSessionListener HttpSessionEvent

HttpSessionActivationListener

HttpSessionAttributeListener

在Servlet 2.4版陆续又多新增Request Listener、Event和Request Attribute Listener、Event

(见表2-2):

表 2-2

Listener 接口Event 类

ServletRequestListener ServletRequestEvent

ServletRequestAttributeListener ServletRequestAttributeEvent

这部分在“第十一章:Filter 与Listener”中有更详细的介绍。

2-5-4 Servlet 2.4 的其他变更

Servlet 2.4 其他较显著的变更如:

(1) 取消SingleThreadModel接口。当Servlet实现SingleThreadModel 接口时,它能确保同时间

内,只能有一个thread 执行此Servlet。

(2) <welcome-file-list>可以为Servlet。例如:

<servlet>

<servlet-name>Index</servlet-name>

<servlet-class>tw.com.javaworld.IndexServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Index</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>Index</welcome-file>

</welcome-file-list>

(3) ServletRequest 接口新增一些方法,如:

public String getLocalName( );

public String getLocalAddr( );

public int getLocalPort( );

public int getRemotePort( );