您正在查看: siddim 发布的文章

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( );

jsp 环境配置

第一章 安装执行环境

1-1 安装J2SDK 1.4.2

第一步:执行j2sdk-1_4_2_03-windows-i586-p.exe(见图1-2);

图1-2 执行j2sdk-1_4_2_03-windows-i586-p.exe

先按【Next】,选择【I accept the terms in the license agreement】后,再按【Next】。

第二步:选择安装路径及安装内容(见图1-3);

图 1-3 选择安装路径及安装内容

一般来说,我们通常都默认安装在C:\j2sdk1.4.2_03 的目录下,假若你要安装在其他路径时,

请按【Change】,改变J2SDK 安装路径。确认无误后,再按【Next】。

随后开始安装Java Plug-in至浏览器上,一般都选择【Microsoft Internet Explorer】。再按

下【Install】,正式开始执行安装程序,假若安装成功后,你会看到如图1-4。

图1-4 成功安装J2SDK 1.4.2_03

第三步:设定J2SDK 1.4.2_03(见图1-5);

从【开始】→【设置】→【控制面板】→【系统】→【高级】→【环境变量】→【系统变量】,

然后到【新建】。

JAVA_HOME = C:\j2sdk1.4.2_03

PATH = %JAVA_HOME%\bin

CLASSPATH = C:\j2sdk1.4.2_03\lib\tools.jar;C:\j2sdk1.4.2_03\

lib\dt.jar;

注意

1. CLASSPATH 的设定中,分号(;)用来分开两路径,切勿任意空格;

2. CLASSPATH 的设定中,分号的最后还有一个点“.”。

图 1-5 设定J2SDK 之CLASSPATH

补充

不论 Windows 2000 或Windows XP 皆可依上述方法设定。

第四步:测试J2SDK。

撰写一个HelloWorld.java 程序,放置在C:\HelloWorld.java 中。

HelloWorld.java

public class HelloWorld {

public static void main(String[] args) {

System.out.println("Hello World");

}

}

打开命令提示符,在C:\ 下输入javac HelloWorld.java,然后再输入java HelloWorld,执行

HelloWorld 程序,假若顺利成功,则会显示“Hello World”,如图1-6 所示。

图 1-6 编译且执行HelloWorld 程序

成功安装J2SDK 1.4.2_03 之后,紧接下来安装Tomcat 5.0.16。

1-2 安装Tomcat 5.0.16

Tomcat 目前版本为5.0.16,它是由JavaSoft 和apache 开发团队共同提出合作计划( apache

Jakarta Project )下的产品。Tomcat 能支持servlet 2.4 和JSP 2.0 并且是免费使用。

Tomcat 5.0.16 可以从http://jakarta.apache.org/tomcat/index.html 网站自行免费下载,或

者可以直接使用本书CD 光盘中的Tomcat 5.0.16,软件名称为:jakarta-tomcat-5.0.16.exe。

第一步:执行 jakarta-tomcat-5.0.16.exe(见图1-7);

先按【Next】,选择【I Agree】后,再按【Next】。

第二步:选择安装路径及安装内容(见图1-8);

通常我们会选择完全安装(Full),即如图1-8。在图1-9【Tomcat】的选项中,主要有:Core、Service、

Source Code和Documentation,假若选择安装Service时,尔后我们可以利用Windows 的服务(控制

面板 |管理工具 |服务)来设定重新开机启动时,Tomcat能够自动启动。

图 1-7 执行jakarta-tomcat-5.0.16.exe

图 1-8 选择安装内容

图 1-9 选择安装Service

选择完全安装后,按【Next】。开始选择安装路径,如图1-10 所示。

图 1-10 选择安装路径

第三步:设定Tomcat Port 和Administrator Login(见图1-11);

图 1-11 设定Tomcat Port 和Administrator Login

第四步:设定Tomcat 使用的JVM(见图1-12);

图 1-12 选择Tomcat 使用的JVM

确认无误后,按下【Install】,正式开始执行安装程序。安装成功后,会看到如图1-13的结果。

假若你勾选了【Run Apache Tomcat】,按下【Finish】之后,会直接启动Tomcat 5.0.16,然后

在你计算机的右下角,会出现绿色箭头的符号,如图1-14。

图 1-13 成功安装Tomcat 5.0.16

图 1-14 Tomcat 图标

第五步:测试Tomcat。

打开浏览器,如IE,输入http://localhost:8080,假若Tomcat安装成功,则会看到如图1-15的

情形。

图 1-15 连接http://localhost:8080/,测试Tomcat 5.0.16

本书“第十二章:JSP 执行环境与开发工具”,对于Tomcat 的使用及设定有更详细的介绍。

1-3 安装JSPBook 站台范例

读者可以在CD 光盘中找到本书的范例,程序文件名为JSPBook.war。

第一步:安装 JSPBook.war;

安装的方法很简单,只要将JSPBook.war 移至{Tomcat_Install}\webapps\目录下(例如:

C:\Program Files\Apache Software Foundation\Tomcat 5.0\webapps\JSPBook.war) , 然后

JSPBook.war 会自动被Tomcat 解压缩成JSPBook 的目录,如图1-16。

图 1-16 安装JSPBook.war

第二步:设定JSPBook 站台;

在 Tomcat 上建立一个JSPBook 站台时,我们须修改Tomcat 的server.xml 文件,server.xml 位

于{Tomcat_Install}\conf\server.xml(例如:C:\Program Files\Apache Software Foundation\

Tomcat 5.0\conf\server.xml)。

server.xml

………

<!-- Tomcat Root Context -->

<!--

<Context path="" docBase="ROOT" debug="0">

-->

<Context path="/JSPBook" docBase="JSPBook" debug="0"

crosscontext="true" reloadable="true" >

</Context>

</Host>

</Engine>

</Service>

</Server>

这部分主要是设定JSPBook 站台, 其中path="/JSPBook"代表网域名称, 即

http://IP_DomaninName/JSPBook ; docBase="JSPBook" 代表站台的目录位置, 即

{Tomcat_Install}\webapps\JSPBook;debug 则是设定debug level,0 表示提供最少的信息,9 表

示提供最多的信息;reloadable则表示Tomcat在执行时,当class、web.xml被更新过时,都会自

动重新加载,不需要重新启动Tomcat。

注意

<Context>…</Context> 的位置必须在 <Host>…</Host> 之间,不可任意更动位置。

第三步:执行JSPBook 站台(见图1-17);

图 1-17 执行JSPBook 站台

第四步:JSPBook 站台目录结构。

JSPBook 目录下包含:

(1) 各章节的html/JSP 程序;

(2) dist 目录:存放在JSPBook 站台压缩后的JSPBook.war;

(3) build.xml:Ant 文件;

(4) WEB-INF 目录:包含\classes、\lib、\tags 和\src;

(5) src 目录:存放范例的源程序,如:JavaBean、Filter、servlet,等等;

(6) Images 目录:存放范例程序的图片。

图1-18 为JSPBook 站台目录结构。

图 1-18 JSPBook

1-4 安装Ant 1.6

修改JSP 程序时,Tomcat 会自动将JSP 重新转译为Servlet,并且编译Servlet。但是,假

若修改Servlet、JavaBean 或Filter 时,我们就必须自行先编译它们,然后再将它们重新部署

至WEB-INF\classes 下。

为了方便编译这些程序,笔者提供JSPBook 站台的build.xml文件,因此,建议读者先安装Ant

1.6,并且学习使用Ant。

目前Ant 的最新版本为1.6,读者可以自行至http://ant.apache.org 下载最新版本,如

图1-19 所示, 或者直接使用本书CD 光盘中的Ant 1.6 , 软件名称为:

apache-ant-1.6.0-bin.zip。

第一步:将 apache-ant-1.6.0-bin.zip 解压缩;

解压缩apache-ant-1.6.0-bin.zip 之后,在apache-ant-1.6.0-bin 目录下会有一目录

apache-ant-1.6.0,然后将apache-ant-1.6.0整个目录搬移至C:\ 底下。

第二步:设定Ant 1.6(见图1-20);

从【开始】→【设定】→【控制面板】→【系统】→【高级】→【环境变量】→【系统变量】,

然后到【新建】。

ANT_HOME = C:\apache-ant-1.6.0

PATH = %ANT_HOME%\bin

第三步:测试Ant 1.6;

图 1-19 http://ant.apache.org

图 1-20 设定Ant 1.6

打开命令提示符,输入 ant –version,假若执行成功,则会有如图1-21 所示的结果。

图 1-21 测试Ant 1.6

第四步:使用Ant 1.6 编译JSPBook\WEB-INF\src 中的程序。

要编译修改过的JSPBook\WEB-INF\src 中的程序时,首先打开命令提示符,移至JSPBook 站台

的所在目录,例如:C:\Program Files\Apache Software Foundation\Tomcat 5.0\webapps\JSPBook。

然后执行ant,它会先自动找到JSPBook\build.xml文件,根据build.xml的设定,编译C:\Program

Files\Apache Software Foundation\Tomcat 5.0\webapps\JSPBook\WEB-INF\src目录下所有的Java

源文件,然后将产生的类文件存放至C:\Program Files\Apache Software Foundation\Tomcat

5.0\webapps\JSPBook\WEB-INF\classes 目录下。图1-22 为执行ant 指令的结果。

图 1-22 执行ant 指令

jsp 网站开发

3. java环境配置

如果你满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 1 章 开始编写jsp。

1. 没用过java,没配置过java环境的朋友。

2. 用过java,但没有使用过类似Tomcat服务器的朋友。

按照我们的直接感官认识,如果我们要写jsp,就需要一个支持jsp的程序来运行它,我们在这里选用的是Tomcat,而tomcat需要安装java开发环境。反过来就是我们准备的顺序,先是java然后tomcat。

3.1. 安装jdk

jdk是Java Development toolkit(Java开发工具包),我们需要的就是下载,安装,进行需要的配置。

本教程支持版本号:JDK 6 Update 5

下载JDK 6 Update 5。网址 http://java.sun.com/javase/downloads/index.jsp。

选择右侧的download。

选择accept接受sun的下载协议。

接受协议后,就可以选择71.39M的下载包,进行下载。

下载完毕,得到jdk-6u5-windows-i586-p.exe。

双击jdk-6u5-windows-i586-p.exe进行安装,默认的安装路径是:C:\Program Files\Java\jdk1.6.0_05\。

安装完成后,C:\Program Files\Java\jdk1.6.0_05\目录结构如下。 9 / 148

接下来设置系统的环境变量(Windows xp)

我的电脑(单击鼠标右键)->属性->高级->环境变量

选择新建,添加两个环境变量,JAVA_HOME和PATH。

其中JAVA_HOME的值是刚刚的安装路径,在这里是C:\Program Files\Java\jdk1.6.0_05\,如果安装到其他路径下,需要按实际情况进行修改。

PATH环境变量要引用刚才设置的JAVA_HOME,使用%JAVA_HOME%的写法,这一部分会自动替换成JAVA_HOME对应的值。

添加完成后的效果,可以看到path中的%JAVA_HOME%自动替换成安装路径了。

在cmd中执行javac,出现下图中的使用说明,表示设置成功。

注意

如果觉得自己完全是照上面的步骤进行了设置,但是运行javac总是报错的同志,重新按照一下几点进行检查。

 修改了环境变量以后,记得重开一个cmd窗口进行测试,老的cmd窗口不会使用新修改的环境变量。

 windows中设置目录要使用“\”,而不是“/”。如果使用了“/”会导致找不到路径的错误。

 如果还是不行,就去jdk安装目录下去找一个叫javac.exe的可执行文件,把这个文件所在的目录路径写到path中,把这个路径的上一级目录设置成JAVA_HOME的值。

3.2. 配置tomcat服务器

简要说几句,tomcat是apache基金会开发的java服务器,因为它是免费的,很多公司里都使用它。所以用它学会了jsp,以后工作上也用得到。

本教程支持版本号:apache-tomcat-5.5.26。

下载apache-tomcat-5.5.26.zip。网址 http://tomcat.apache.org/download-55.cgi#5.5.26

下载core里的zip项,这是绿色免安装版本,如果你希望使用安装版本,也可以选择Windows Excutable。

将下载文件,解压至c:\apache-tomcat-5.5.26目录,目录结构如下:

请确认安装了jdk,并配置了对应的JAVA_HOME环境变量。

双击运行bin目录下的startup.bat

看到:“server startup in 4062 ms”,说明tomcat启动成功。

关闭tomcat,可以直接关闭cmd窗口,或使用shutdown.bat

到此为止,准备工作都已结束,我们可以在tomcat下编写jsp了。

第 1 章 开始编写jsp

注意

因为本章包含了对http部分原理的介绍,建议读者不要略过这部分。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 2 章 让jsp说hello。

1. 了解jsp的基本组成,可以编写简单的jsp。

2. 了解tomcat下jsp的放置位置,以及如何访问对应路径下的jsp。

3. 了解http基本原理,以及jsp在http下是如何发挥效用的。

1.1. 开篇第一个jsp

JSP是Java Server Page的缩写,现在先让我们编写一个简单的jsp页面,看看jsp究竟能干什么?

<%=new java.util.Date()%>

随便找一个文本编辑器来编写第一个jsp吧,如果实在没有顺手的编辑器,那么也可以用windows自带的记事本,新建一个test.jsp文件,把上面的代码复制到文件里,保存即可。

如果嫌麻烦,也可以直接使用文档附带的演示代码,位置在lingo-sample/01-01/test.jsp。

注意

对于第一次用记事本写代码的朋友,有一点需要特别注意,默认情况下windows不会显示扩展名,如果直接在记事本里将文件名改为test.jsp,那么另存的文件名会变成test.jsp.txt,记事本会自作聪明的为你加上.txt的扩展名,为了避免这种问题,需要在保存的时候,在文件名两端加上双引号,就像这样"test.jsp"。

建议设置系统文件夹属性,把文件的扩展名显示出来,这样我们更容易看出是否在文件名上出现问题。如果找不到这个设置,请打开“我的电脑” -> 选择上方菜单里的“工具” -> 选择弹出菜单中的“文件夹选项” -> 选择第二个标签“查看”,在下边的配置里有一项“隐藏已知文件类型的扩展名”,将它前面的对勾取消,点击确定,就可以看到文件的全名了。

得到了我们的第一个jsp文件之后,让我们把它复制到tomcat/webapp/ROOT目录下。现在检查一下tomcat是不是已经启动了,如果还没启动,需要先去启动tomcat,在看到tomcat正常启动的提示之后,就可以打开浏览器,输入网址 http://localhost:8080/test.jsp查看执行效果。

页面上的显示应该与下面类似:

Fri Feb 29 00:54:20 CST 2008

看到了这些字样,也就说明我们写的第一个jsp已经成功执行了,现在咱们看到的并不是谁预先写好的文字,而是使用服务器实时计算出的当前时间,如果不信你可以多刷新几次页面,时间内容会不断改变,显示的永远是当前服务器的时间。

好的,现在就引出了一个问题,jsp是如何把我们需要的数据发送给我们的。

1.2. B/S结构,请求与响应

B/S结构,既浏览器(Browser)/服务器(结构),用浏览器查看jsp写的页面就算非常简单的B/S结构了。

先看看在我们访问http://localhost:8080/test.jsp的时候究竟发生了什么:

1. 首先,浏览器解析我们输入的网址,查找服务器的位置。

咱们这里使用了http://localhost:8080/,浏览器就会以http协议,去访问localhost的8080端口,localhost是本机的别名,8080是tomcat的默认端口,即使现在不太理解也没有关系,只要知道http://localhost:8080/是你这台机器上运行的tomcat就可以了。

2. 找到了服务器的位置,浏览器会向服务器发送一个请求(request),这个请求包含着http协议规定格式的数据,现在咱们不需要去计较细节,先把注意力集中在流程上。

3. 接下来,服务器接收请求,分析请求中包含的数据。这个分析过程也是定义在http协议中的,像我们这里请求的是/test.jsp这个jsp页面,服务器就会去webapp/ROOT目录下去查找这个test.jsp,然后对它进行解析,运行。

4. 在服务器的操作结束后,会生成一个响应(response),并把这个响应发送回客户机器的浏览器。

5. 现在浏览器接收了响应,开始进行解析与运行,最后把结果显示给用户,这就成为我们最后看到的结果。

这就是http协议的基本流程了,像我们看到的一样,浏览器与服务器之间完全是依靠请求和响应联系起来的。这就像是在打乒乓球,浏览器发过一个球来,服务器接到球,反手再打回去。双方队员之间不会有其他接触,所有的交流就是那颗小球。

这也暗示了http中另一个重要的特性:短连接,无状态。

 短连接是指:请求响应一次,服务器就关闭与浏览器之间的网络连接。

 无状态是指,任意两次请求响应之间,没有直接的联系。

浏览器发出一个请求,服务器才能返回一个响应。一个请求对应一个响应,每个过程都是完全独立的。并且服务器端是被动的,只能接收请求,然后向请求的原发地发送响应,如果没有请求,服务器没办法凭空发一个响应出去,因为它不知道客户的机器在什么地方。浏览器和服务器之间也不会拉一根电话线,随时保证畅通,每次请求处理完之后,服务器就会立刻忘掉上次请求的信息。这样做的好处是处理简单,连接用完就断,不会浪费资源,坏处是在进行复杂操作的时候,因为没法保证用户当前的状态,只好把表示状态的信息不断的在浏览器和服务器之前传来传去,造成了操作的复杂。

一个请求一个响应,构筑了http协议的基础,jsp则是专门管辖服务器的部分,这样我们就可以把jsp的功能锁定在第二步和第三步了。处理请求,返回响应,这便是jsp的所有工作。

再重复一遍,jsp只负责服务器的操作,浏览器上的任何东西都与其无关,显示图片,显示文字,点击按钮,弹出窗口,这些都是浏览器的工作职能。并且,因为http的无状态性,jsp一旦返回了响应,就再不管其他的了。它绝对不会也不能直接对浏览器造成什么影响,它所能做是把响应这颗球打出去,剩下的就全看浏览器了。浏览器会发生什么事情,jsp也无从得知,如果想让jsp做些什么事情,唯一的办法就是发送一个请求。

重复第三遍,浏览器和服务器基本就是两个孤岛,两地居民的交流只能通过飞鸽传书来实现。在一封信到达之前,本地人是没办法得知对面的消息的,每当浏览器这边岛上的居民想要什么东西,他们就把需要的东西写到信里,用信鸽传递给服务器那边的岛上,服务器收到信鸽的消息,会按照上边所写的准备好,再放飞回去,等浏览器等到他们要的信鸽,就可以把东西放下来随便用了。两地居民直接接触的只有信鸽,他们从信鸽上那到他们想要的东西,对面的人长什么样子,是人是鬼他们全不在意,只要大家都会用信鸽就好了,http就是这样一只大家都会用的信鸽了。

希望现在大家已经对http有了清晰的认识,如果不了解它的运行原理,带着一脑子糨糊学jsp,那可是太可怕了,最怕的就是把jsp和浏览器混为一谈,认为是浏览器在运行jsp。实际上jsp是完全不知道浏览器的存在,它只是根据http形式的请求,发送http形式的响应,如果对面的浏览器能解析http形式的响应,就能显示出页面来,幸运的是目前市面上的浏览器都能解析http形式的响应,所以我们只要制造符合标准的数据,当作响应发送出去就行了。

其实一切都是这么简单,只要能了解它的运行原理,我们就可以在自己的舞台上大施拳脚,不用为涉及不到的领域胡乱苦恼了。

下一章,我们就可以研究如何使用jsp了。

第 2 章 让jsp说hello

注意

为了保证循序渐进的学习状态,我们这里无可奈何的使用了一些在正式工作时明令禁止的代码,请大家在阅读此章务求领会精神,千万不要死记硬背,以防遗憾终身。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 3 章 请求的跳转与转发。

1. 了解从request中获得参数的方法。

2. 了解一些jsp指令(directive)。

3. 了解中文乱码问题。

2.1. 另一个简单jsp

上一篇举的例子很单纯,无论谁向服务器发送请求,服务器都只计算当前系统时间,然后把这个时间制作成http响应发还给浏览器。

可惜这种单向的响应没办法实现复杂的业务,比如像这样:

客户在这个页面输入自己的名字,然后提交:

服务器会对你说:“你好啊,XXX。”

返回刚才的页面,再输入另外一个名字:

提交以后就变成这样:

呵呵,神奇吧,服务器知道你叫什么名字,而且还会向你问好。

虽然咱们都知道这只是从请求中获得了用户名,加上欢迎信息再发送回浏览器,但最终用户会感到更亲切。下面就让我们来看一下如何从请求获得参数吧。

首先用户输入页面会是这样的:

<form action="test.jsp">

username : <input type="text" name="username" />

<br />

<input type="submit">

</form>

这里是一个简单的form,里面只有一个名叫username的文本框,点击提交之后它的值就会提交到后台服务器上的test.jsp。那么在jsp里,咱们如何得到用户输入的username呢?

Hello <%=request.getParameter("username")%>

呼呼,原来就这么简单,前面的Hello 是一成不变的,后面紧接<%%>包含的java代码。

只要是<%%>中间的部分就会被当成java代码执行,咱们就来看看jsp里是通过什么途径获得请求中的参数吧。

首先是request,翻译过来就是请求,它被称作jsp九大默认对象之一,与http请求相关的操作都是通过request实现的,你完全可以把它看作是浏览器发送过来的http请求。

请求里就包含了我们需要获得的参数,你想要知道username的值吗?好的,只要调用getParameter("username")就可以获得了,你交给request一个参数名,它会返回对应的参数值,因为http协议的限制,获得的参数值都是字符串,不

过在参数不存在的时候,也会返回null,所以一定要记住在使用之前判断是否为null,免得频繁出现NullPointerException。

好了,现在我们把得到的参数值输出即可,你可以对语句前面的等号感到疑惑,它的意思就是把参数值输出到页面上,你也可以使用另一种方法:

Hello <%out.print(request.getParameter("username"));%>

上一种方法可以看作是它的简化形式,请注意如果使用第一种方法,是不需要在java代码最后加上分号的,而第二种方法必须加上分号。因为第一种方法是特殊的简化写法,第二种方法是标准的java代码,稍微留意一下就可以区分了。

例子在lingo-sample/02-01/,将目录复制到tomcat的webapp目录下,启动tomcat后可以访问http://localhost:-01/进行测试。

2.2. 中文乱码

你有没有发现,我们的例子中没有中文?

并不是我们不想使用中文,那是因为在jsp中使用中文是一个历史悠久的大问题,虽然等你解决了之后会觉得它是那么简单,但对于没经验的新手来说,中文乱码问题足以搞得他们焦头烂额了。

现在我们就要带领你闯过这道关,消灭掉请求和响应中可能出现的乱码,还中文英雄本色。

2.2.1. 先解决响应中的乱码

何为响应中的乱码?把页面中的“username”改成“用户名”你就知道了。

所谓响应中的乱码,就是显示页面上的乱码,因为页面数据是从服务器一端放入响应(response)中,然后发送给浏览器,如果响应中的数据无法被正常解析,就会出现乱码问题。

为什么英文就没有问题呢?因为在iso-8859-1,gb2312, utf-8以及任意一种编码格式下,英文编码格式都是一样的,每个字符占8位,而中文就麻烦了,在gb2312下一个中文占16位,两字节,而在utf-8下一个中文要占24位,三字节。浏览器在不知道确定编码方式的情况下,就会把这些字符从中间截断,再显

示的时候就乱掉了。所以,想要解决乱码问题,就是要告诉浏览器我们到底使用了什么样的编码方式。

为了获得正常显示的中文,需要注意以下几步:

1. 因为服务器要先从本地读取jsp文件,然后经过处理后写入响应,所以我们首先要知道的就是jsp文件的编码格式。从问题的源头着手解决。

在咱们用的windowxp下,文件默认的编码格式是gb2312。

2. 我们要在http的响应(response)中添加编码信息,使用如下方式:

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

这段要放在jsp页面的第一行,用来指定响应的类型和编码格式,contentType为text/html就是html内容,charset表示编码为gb2312。这样浏览器就可以从响应中获得编码格式了。

这种<%@ %>的形式叫做jsp指令(directive),现在接触到的是page指令,还有include和taglib指令,我们会在后面陆续讲到。

3. 还需要在html中指定编码格式。

<head>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />

<title>title</title>

</head>

meta部分用来指定当前html的编码格式,注意这一段要放在head标签中,并且放到head标签的最前面,如果不是最前面ie下可能会出现问题,尤其是在title中有中文的情况下。

完成了以上三段检验,我们才能保证输出的jsp页面会正常显示中文。

例子在lingo-sample/02-02/下,正常显示的页面index.jsp,还留了一个乱码页面index_wrong.jsp。

2.2.2. POST乱码

先把form里加上method="POST",让form提交的时候使用POST方式。

发送请求的时候,使用的编码是iso-8859-1,意味着只有英文是有效字符,这个限制是因为当初指定http标准的成员都来自英语国家,所以如果使用默认的方式从请求获取数据,中文一定会全部变成乱码。

如果不信,你可以在刚才的例子里输入中文,然后提交:

提交结果就会变成这样:

怎么解决呢?我们要jsp最前面加上一条java语句,设置请求的字符编码。

<%

request.setCharacterEncoding("gb2312");

%>

于是,那些乱码都正常了:

例子在lingo-sample/02-03/下。

2.2.3. GET乱码

直接点击超链接,form的默认提交方式都是GET。

POST方式下的解决方式还算简单,因为POST方式下提交的数据都是以二进制的方式附加在http请求的body部分发送,只需要在后台指定编码格式就足矣解决。

GET方式下会将参数直接附加到url后面,这部分参数无法使用request.setCharacterEncoding()处理,结果就是get形式的所有中文都变成了乱码。

这时再也没有简便方法了,只能对这些中文一个一个进行转换,使用new String(bytes, "gb2312")进行转码。


<%

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

byte[] bytes = username.getBytes("iso-8859-1");

String result = new String(bytes, "gb2312");

out.print(result);

%>


如我们所见,先从request中获得参数,接着把字符串按照iso-8859-1编码打散成byte数组,然后用gb2312编码组合成新字符串,最后打印出来就是正常的中文了。

写在一起就变成了:

<%=new String(new String(request.getParameter("username").getBytes("iso-8859-1"), "gb2312")%>

这样做的缺点,是从请求中取得的所有中文都需要转码,非常烦琐。

所以大家千万不要像这样<a href="test.jsp?username=测试">测试</a>,把中文参数写到超链接中,form尽量使用method="POST",这样只需要设置request.setCharacterEncoding()就可以应付中文乱码问题。

例子在lingo-sample/02-04/下。

经历了这些只有非英语体系国家才能遇到的波折之后,我们的jsp终于可以接受请求中的参数,并正常响应了。下面将要进行更复杂的请求响应流程。

第 3 章 请求的跳转与转发

注意

应用广泛,而又没几个人明白的东西上场了,这章就只讨论这一个问题。

如果你确认自己明白下面这个问题,请直接进入下一章:第 4 章 四个作用域。

1. forward, redirect的区别。

3.1. 范例

这次用户可以在首页选择自己喜欢的颜色,进入对应的页面。

选择绿色,会进入绿色界面

选择红色,会进入红色界面

好的,这里我们会看到四个页面:

1. index.jsp中选择颜色,点击按钮后提交到test.jsp。

2. test.jsp取得用户选择的颜色,根据颜色值显示对应的页面。

3. 如果选择了红色,就显示red.jsp。

4. 如果选择了绿色,就显示green.jsp。

在这里例子里,index.jsp,red.jsp,green.jsp中的内容都是一样的,所有的玄机都在test.jsp中。

现在面临的问题是如何在test.jsp决定实现red.jsp或者green.jsp,我们可以在forward和redirect中任选其一。

3.2. 如果用forward

test.jsp中需要这样写:

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

<%

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

if ("red".equals(color)) {

request.getRequestDispatcher("red.jsp").forward(request, response);

} else if ("green".equals(color)) {

request.getRequestDispatcher("green.jsp").forward(request, response);

}

%>

略过取得参数与比较参数值不提,只关注forward的部分:

request.getRequestDispatcher("red.jsp").forward(request, response);

首先调用request的getRequestDispatcher()方法,获得对应red.jsp的转发器,然后调用forward()方法执行请求转发。结果用户看到的就是red.jsp中的结果了,一个红色的页面。

这里请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

于是,无论转发至red.jsp还是green.jsp,地址栏上显示的都是test.jsp。

这是为什么呢?通过下面的流程图会让我们容易理解:

1. 浏览器向test.jsp发送请求。

2. test.jsp计算客户选择的颜色,将请求转发至red.jsp。

3. red.jsp返回响应给浏览器。

28 / 148

这下知道为什么浏览器的地址没有变化了吧?因为浏览器只是执行了对test.jsp的请求,test.jsp到red.jsp的部分是在服务器内执行的,浏览器并不知道服务器里到底发生了什么,它只知道自己获得的响应是test.jsp发回来的,甚至不知道服务器还有个red.jsp。

这就是请求转发forward了。例子见lingo-sample/03-01/。

3.3. 如果用redirect

test.jsp中需要这样写:

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

<%

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

if ("red".equals(color)) {

response.sendRedirect("red.jsp");

} else if ("green".equals(color)) {

response.sendRedirect("green.jsp");

}

%>

略过取得参数与比较参数值不提,只关注redirect的部分:

response.sendRedirect("red.jsp");

response翻译过来就是响应,代表着http响应。调用response的sendRedirect("red.jsp")方法,将页面重定向到red.jsp。

再请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

与forward不同,url地址一直在变化,红色的时候显示red.jsp,绿色的时候显示green.jsp。

再看一下流程图吧:

1. 浏览器向test.jsp发送请求。

2. test.jsp计算客户选择的颜色,向浏览器发送一个页面重定向(redirect)的响应,响应中包含red.jsp的url地址。

30 / 148

3. 浏览器根据页面重定向(redirect)响应中的red.jsp地址,再次向服务器发送请求,这次请求的就是red.jsp了。

4. red.jsp执行,返回响应。

31 / 148

redirect会触发另一个请求响应流程,第二次请求的时候是由浏览器发起对red.jsp的请求,所以url地址改变了。

这就是页面重定向redirect了。例子见lingo-sample/03-02/。

3.4. forward和redirect的问题

3.4.1. 绝对路径与相对路径

1. 如果咱们使用的URL网址是以“/”开头的,那么这个网址就叫做绝对路径。

2. 如果咱们使用的URL网址不是“/”开头的,那么这个网址就叫做相对路径。

3.4.1.1. 相对路径

在相对路径上,两者的表现是相同的。

看看lingo-sample/03-03/这个例子,如果我们去请求relative/forward.jsp或redirect.jsp,然后从这里再跳转向它下面的result/result.jsp会怎样呢?

1. forward的例子:

<%request.getRequestDispatcher("result/result.jsp").forward(request, response);%>

这里的相对路径就是result/result.jsp。

因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行forward的时候会寻找当前路径下的result/result.jsp,找到之后便转发请求。

2. redirect的例子:

<%response.sendRedirect("result/result.jsp");%>

这里的相对路径也是result/result.jsp。

因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行redirect的时候会把当前路径加上result/result.jsp,把结果作为重定向的地址发送给浏览器,浏览器再去请求/03-03/relative/result/result.jsp,从而得到响应。

3.4.1.2. 绝对路径

问题出现了,绝对路径在forward和redirect中出现了差别,还是刚才的情况,但使用绝对路径的时候写法便不同了。

1. forward的例子:

<%request.getRequestDispatcher("/relative/result/result.jsp").forward(request, response);%>

这里的绝对路径就是/relative/result/result.jsp。

在本地测试时,forward把http://localhost:-03/当作根路径,在它的基础上计算绝对路径。

这是由jsp的部署方式决定的,webapp里可以放好多项目,为了让这些项目可以互不影响、独立运行,不能让请求从一个项目直接在服务器内部转移到另一个项目。为了防止出现这种情况,在执行forward的时候干脆把项目的路径当作根目录,开发者看不到其他项目,也就不会出现问题了。

2. redirect的例子:

<%response.sendRedirect("/03-03/absolute/result/result.jsp");%>

这里的绝对路径却是/03-03/absolute/result/result.jsp。

在本地测试时,redirect把http://localhost:8080/当作根路径,在它的基础上计算绝对路径。

因为redirect会让浏览器重新发起一个新请求,所以不会搅乱服务器里多个项目之间的关系,也就不需要对它做限制,如果需要在多个项目之间进行跳转,就只能使用redirect。不过因为重新发起了新的请求,上次请求的那些数据都会丢失,如果有什么重要的数据,记得要重新设置。

3.4.2. forward导致找不到图片

找不到图片,找不到js脚本,找不到css样式表,都属于这个问题。

要演示这个问题,是非常容易的,只需要满足两个条件:

1. forward前后的jsp页面不在一个目录下。

2. forward后的jsp页面里使用相对路径引用一些资源,图片,js脚本,css样式表什么的。

03-04里就模拟了这样一个环境,你进入http://localhost:-04/,选择“有问题的”:

打开03-04可以看到如下的目录结构:

|--+ 03-04

|--- index.jsp

|--- test.jsp

|--+ result

|--- success.jsp

|--- failure.jsp

|--- lingo.png

刚才咱们看到的页面是failure.jsp,它里边显示图片的部分是:

<img src="lingo.png" />

这时候就有疑问了,lingo.png和failure.jsp明明在同一个目录下,为什么无法显示。

现在请在无法显示的图片上,点击鼠标右键,选择属性,让我们看一下图片的请求地址:

图片的位置本来在http://localhost:-04/result/lingo.png,但请求的地址却是http://localhost:-04/lingo.png。问题就是丢掉了中间的/result。

再试一次index.jsp上的“没问题的”:

这次我们看到的页面是success.jsp,它里边显示图片的部分是:

<img src="result/lingo.png" />

结果手工加上result这段路径后就可以显示图片了。

这个问题还要追溯到浏览器对html的处理方式,在html里包含的图片,css样式表,js脚本,视频等等外部资源,都需要浏览器再次向服务器发起请求。

如果这些外部资源使用了相对路径,浏览器就会在当前请求路径的基础上,加上相对路径拼接出完整的http请求,发送给服务器。这个例子中,我们请求http://localhost:-04/test.jsp,浏览器得到的当前路径就是http://localhost:-04/,failure.jsp中图片的相对路径是lingo.png,那么拼接的结果是http://localhost:-04/lingo.png。

不要怪浏览器太傻,是因为使用forward的时候浏览器并不清楚这些改变。它一直认为,既然自己请求的是test.jsp,返回的自然就是test.jsp的内容,那么再使用test.jsp当作当前路径去计算相对路径当然没有问题。是我们欺骗了浏览器,在服务器偷偷改变了请求流向,返回了其他页面的内容。

清楚了以上的请求流程,就知道如何应对这种问题了。

1. 第一种方法:不要在不同目录之间使用forward做请求转发,保证当前路径不发生变化。

2. 第二种方法:像上例一样修改图片路径,或全部改为绝对路径。

请根据实际需要进行选择。

第 4 章 四个作用域

注意

这里介绍的主要是作用域,但实际中是不允许在jsp写些么多代码的,如果你工作了还这样写,要么这个公司的水平有问题,要么你就要被大骂一顿了。请务必领会精神,不要死记硬背。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 5 章 结合javabean实现CRUD。

1. 了解四个作用域(scope)的用处。

2. 了解el(Expression Language)。

4.1. 何为作用域

先让我们看看效果:

大概流程是这样的,我们访问04-01/index.jsp的时候,分别对pageContext, request, session, application四个作用域中的变量进行累加。(当然先判断这个变量是不是存在,如果变量不存在,则要把变量初始化成1。)计算完成后就从index.jsp执行forward跳转到test.jsp。在test.jsp里再进行一次累加,然后显示出这四个整数来。

从显示的结果来看,我们可以直观的得出结论:

1. page里的变量没法从index.jsp传递到test.jsp。只要页面跳转了,它们就不见了。

2. request里的变量可以跨越forward前后的两页。但是只要刷新页面,它们就重新计算了。

3. session和application里的变量一直在累加,开始还看不出区别,只要关闭浏览器,再次重启浏览器访问这页,session里的变量就重新计算了。

37 / 148

4. application里的变量一直在累加,除非你重启tomcat,否则它会一直变大。

而作用域规定的是变量的有效期限。

1. 如果把变量放到pageContext里,就说明它的作用域是page,它的有效范围只在当前jsp页面里。

从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。

2. 如果把变量放到request里,就说明它的作用域是request,它的有效范围是当前请求周期。

所谓请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,在这些页面里你都可以使用这个变量。

3. 如果把变量放到session里,就说明它的作用域是session,它的有效范围是当前会话。

所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个请求响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量,就可以在当前会话的所有请求里使用。

4. 如果把变量放到application里,就说明它的作用域是application,它的有效范围是整个应用。

整个应用是指从应用启动,到应用结束。我们没有说“从服务器启动,到服务器关闭”,是因为一个服务器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。

application作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。

与上述三个不同的是,application里的变量可以被所有用户共用。如果用户甲的操作修改了application中的变量,用户乙访问时得到的是修改后的值。这在其他scope中都是不会发生的,page, request, session都是完全隔离的,无论如何修改都不会影响其他人的数据。

我们使用public Object getAttribute(String name)获得变量值,使用public void setAttribute(String name, Object value)将变量值保存到对应作用域中。举个pageContext的例子就是:

// page

Integer countPage = (Integer) pageContext.getAttribute("countPage");

if (countPage == null) {

pageContext.setAttribute("countPage", 1);

} else {

pageContext.setAttribute("countPage", countPage + 1);

}

这里先从pageContext中取出名为countPage的整数,因为返回的都是java.lang.Object类型,所以需要强制转换成我们需要的整形。这里取得的变量如果不存在就会返回null,通过判断countPage == null来辨别变量是否存在,如果不存在就设置为1,如果存在就进行累加,最后使用setAttribute()方法将修改后的变量值放入pageContext。

将其中的pageContext换成request, session, application就可以操作其他三个作用域中的变量。

在显示这些变量值的时候,我们没有写<%=pageContext.getAttribute("countPage")%>而是使用了${countPage}的形式,这种${}的形式叫做el表达式,是jsp-2.0规范的一部分,tomcat里正好可以使用。

使用el有以下几个好处:

1. 代码量小,并且不需要使用尖括号。

2. 支持从pageContext, request, session, application中取值,它会自动检查四个作用域,不需要特别指定。

3. 如果变量不存在,会输出空字符串"",而不是null,省去了手工判断的工作。

4.2. 例子:在线列表

我们做一个新手级的在线用户列表,原理是这样:

1. 用户登录,并把登录使用的用户名保存到session中,通过session中是否存在用户名判断用户是否已登录。

session可以在整个会话过程中保存用户信息,不必每次刷新页面都重新登录。

2. 用户登录后,将用户名添加到application中的在线用户列表。

用户注销时,讲用户名从application中的在线列表删除。

只要服务器还在运行着,application就会保存所有登录用户的信息,所有用户都可以看到这个在线用户列表。

可以尝试一下lingo-sample/04-02/中的例子:

1. 进入登录页面,登陆一个用户。

2. 登录成功既看到已登录的用户名,和当前的在线用户列表。

3. 再登录一个用户.

4. 然后就可以看到在线用户列表增加了,可以看到里面包含上次登录的用户和当前登录的用户。

这时,如果第一个用户刷新页面,也会看到在线用户列表中变成两个人。

5. 现在任何一个用户点击注销,将返回登录页面。另一个用户刷新页面会发现在线用户列表减少了。

让我们从登录页面index.jsp开始,复习一下目前学到的知识。

index.jsp中显示的是用户登录表单,为了显示index.jsp中包含的中文,需要加上<%@ page contentType="text/html; charset=gb2312"%>,这里使用的文件编码是默认的gb2312。

<form action="login.jsp" method="post">

用户名:<input type="text" name="username" />

<br />

<input type="submit" value="登录" />

</form>

在这个form里我们可以输入一个username的值,提交的url是login.jsp,使用post方法是为了更简单的解决中文问题。在填写了用户名之后,点击登录按钮,将数据提交到login.jsp。

login.jsp中进行的是对用户名的操作,包括获得请求中的用户名,将用户名添加到session和在线用户列表中。

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

<%

request.setCharacterEncoding("gb2312");

// 取得登录的用户名

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

// 把用户名保存进session

session.setAttribute("username", username);

// 把用户名放入在线列表

List onlineUserList = (List) application.getAttribute("onlineUserList");

// 第一次使用前,需要初始化

if (onlineUserList == null) {

onlineUserList = new ArrayList();

application.setAttribute("onlineUserList", onlineUserList);

}

onlineUserList.add(username);

// 成功

response.sendRedirect("result.jsp");

%>

中文编码设置和获得请求参数都已经熟识了。在获得在线用户列表时,先获得application中的onlineUserList,强制转换成List类型。如果onlineUserList并不存在,我们还需要先对它做初始化,并添加到application里。这时有一个小技巧,因为onlineUserList已经放在application中了,将username添加进去后,不必再使用setAttribute()也可以达到修改在下用户列表的效果。

因为此处用到的List和ArrayList都是定义在java.util包内的工具类,如果不希望写成全类名java.util.List, java.util.ArrayList的形式,就需要使用<%@ page import="java.util.*"%>做声明,当然也可以写成<%@ page import="java.util.List,java.util.ArrayList"%>,具体情况就任君选择了。

登录成功后,使用redirect的方式跳转到result.jsp页面,result.jsp页面中显示的是当前登录用户和在线用户列表的信息。

先看一下页面中使用的jsp指令(directive),<%@ page contentType="text/html; charset=gb2312" import="java.util.*"%>,为了处理中文和使用import,可以把这两部分写在一起。

显示当前登陆名时,使用了el表达式:

<h3>您好:${username} [<a href="logout.jsp">注销</a>]</h3>

显示在线用户列表的时候使用了循环:

<%

List onlineUserList = (List) application.getAttribute("onlineUserList");

for (int i = 0; i < onlineUserList.size(); i++) {

String onlineUsername = (String) onlineUserList.get(i);

%>

<tr>

<td><%=onlineUsername%></td>

</tr>

<%

}

%>

这里的循环体可能会令人感到费解,其实它与下面的写法是等价的:

<%

List onlineUserList = (List) application.getAttribute("onlineUserList");

for (int i = 0; i < onlineUserList.size(); i++) {

String onlineUsername = (String) onlineUserList.get(i);

out.println(" <tr>");

out.println(" <td>" + onlineUsername + "</td>");

out.println(" </tr>");

}

%>

只需要理解代码的含义就可以了,从application里获得onlineUserList,然后循环输出所有的用户名。application是公用的,所以可以看到每个登录的用户。

点击注销的时候,会跳转到logout.jsp,这里负责用户注销和从在线用户列表去除已登录用户。

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

<%

// 取得登录的用户名

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

// 销毁session

session.invalidate();

// 从在线列表中删除用户名

List onlineUserList = (List) application.getAttribute("onlineUserList");

onlineUserList.remove(username);

// 成功

response.sendRedirect("index.jsp");

%>

这次我们从session中获得登录名,因为请求中没有包含任何数据。session.invalidate()这个方法给我们提供了一条销毁session的捷径,不需要一条一条删除session中的数据,invalidate()会直接销毁session,session里边所有的数据也就消失了。

在线用户列表的操作很直观,从application中获得onlineUserList,然后remove(username)就可以从中去除当前登录用户。最后使用redirect跳转到index.jsp这个登录页面。整个应用的流程也就结束了。

整个应用的功能很单纯,之所以把它叫做“新手级”,是因为它只能用于演示。等待用户去点击注销才去操作在线用户列表存在着很大的漏洞,实际使用中,用户很可能因为个人或网络原因没有进行注销就退出系统,这样会导致用户列表不能删除,就这样一直增长下去。

解决这个问题的方法超出了目前掌握的知识,我们将在后面的章节进行介绍。

第 5 章 结合javabean实现CRUD

注意

这里介绍的是在jsp中使用自己写的javabean,不过这种写法也仅仅适用于小型应用,只打算学两下jsp玩玩的朋友可以到此为止了,完成了这章就不必继续下去了,此后难度会加大不少。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 6 章 贴近servlet

1. 了解如何在jsp中使用自定义的javabean。

2. 了解一些jsp动作(action)。

3. 使用jdbc操作数据库

5.1. 概念和命名方式

需要提及的两个名词概念:

1. CRUD是Create(创建)、Read(读取)、Update(更新)和Delete(删除)的缩写,一般应用有这四项也就足够了。

我们这里的例子是对联系人信息进行CRUD操作。

2. javabean是把一些操作集合在一起写成一个java类,想要进行什么操作直接调用这个类里的方法就行。

咱们这里使用javabean的地方有两处,一个是链接数据库并进行CRUD操作,另一个把每条数据都写 成一个类。

对于CRUD应用,有一些大家默认的命名来表示不同的操作。

1. list.jsp。读取所有信息并显示到页面上,这个是CRUD中Read(读取)。

2. create.jsp。进入添加联系信息的页面,等待用户输入信息。

save.jsp。接收用户提交的信息,添加到数据库中。

这两步对应CRUD中的Create(创建)。

3. edit.jsp。进入修改联系信息的页面,等待用户修改信息。

update.jsp。接收用户提交的信息,修改数据库中对应的信息。

这两步对应CRUD中的Update(更新)。

4. remove.jsp。删除用户选择的信息。这步对应CRUD中的Delete(删除)。

下面我们将按照用户浏览的顺序对这些页面进行介绍。

5.2. Read(读取)

启动服务器,访问http://localhost:-01/就会看到联系信息列表。

看一下05-01目录里边的7个jsp页面,其中6个页面都已经介绍了,只剩index.jsp。

index.jsp是tomcat默认的索引页面,在用户访问http://localhost:-01/的时候会自动执行index.jsp,但我们更希望用户能直接进入list.jsp页面看到所有的联系方式,所以在index.jsp里使用forward跳转到list.jsp。

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

<jsp:forward page="list.jsp"/>

第一行是我们曾经讲过的设置中文编码。第二行叫做jsp action(jsp动作),它的写法和html标签很相像,有了它们我们可以节省很多java代码。比如,这个jsp动作就与下面的代码功能相同。

<%

request.getRequestDispatcher("list.jsp").forward(request, response);

%>

从长度来看,jsp动作明显占有绝对优势,在单独使用forward的时候,建议大家优先考虑<jsp:forward page="list.jsp"/>的写法。

现在来看list.jsp里的内容,我们是如何获得这些联系信息,并把这些联系信息显示到页面上。

为了便于操作,我们将所有对数据库的操作都封装到anni.ContactDao中,这就是所谓的javabean了。现在我们想要获得所有联系信息时,只要创建一个ContactDao的实例,然后调用contactDao.getAll()获得装满联系信息的List列表就好了。

在创建ContactDao实例的时候,我们使用了另一个jsp动作:jsp:useBean,它就写在list.jsp的第二行。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

看到这里,可能有朋友提问了,既然只是创建一个对象的实例,为什么不用new呢,那要比这样写的代码少许多,也更容易理解,为什么我们还要执意使用jsp:useBean?只是为了尝试新技术吗?

这里我提醒大家注意一下标签中的scope="application",application正是我们介绍过的四个作用域之一,既然有了这个属性就说明事情没有new这么简单了,实际上正因为scope的属性,这段jsp:usebean实际上等价于下面的代码。

<%

anni.ContactDao contactDao = (anni.ContactDao) application.getAttribute("contactDao");

if (contactDao == null) {

contactDao = new anni.ContactDao();

application.setAttribute("contactDao", contactDao);

}

%>

它会先去scope定义的作用域application中取得contactDao对应的对象,这个contactDao正是标签中定义的id,转换的对象类型则是标签中class属性的值。

好的,我们先从application中获得contactDao对应的对象,然后判断得到的是否为null,如果为null说明此变量还没有初始化,这时就要使用new创建一个对象实例并放入application中。最后我们得到的就是这个contactDao实例。

现在我们得到了一个contactDao实例,并把它放到application作用域中,供所有用户公用,通过使用jsp:useBean,我们下面就可以直接使用它获得需要的数据。

<%

List list = contactDao.getAll();

for (int i = 0; i < list.size(); i++) {

pageContext.setAttribute("contact", list.get(i));

pageContext.setAttribute("row", i % 2 != 0 ? "odd" : "even");

%>

<tr class="${row}" onmouseover="this.className='highlight';" onmouseout="this.className='${row}';">

<td>${contact.username}</td>

<td>${contact.sex}</td>

<td>${contact.email}</td>

<td>${contact.qq}</td>

<td>${contact.descn}</td>

<td><a href="edit.jsp?id=${contact.id}">修改</a> | <a href="remove.jsp?id=${contact.id}">删除</a></td>

</tr>

<%

}

%>

首先我们调用contactDao.getAll()获得联系信息的List列表,然后使用for循环将这些信息都输出到页面。

下面的操作比较有趣,我们每获得一条信息就把它放入pageContext中。之所以这样做,是为了在下面使用el表达式显示信息。

el表达式的一个特点就是必须放到作用域里才能调用,如果使用Contact contact = (Contact) list.get(i);而不放到pageContext中,后面的${contact.username}就无法找到contact了。

这里还要提及el表达式的进一步用法,${contact.username}实际上得到的是contact.getUsername()返回的结果。这里有一个默认的转换规则,假设有一个getUsername()方法,我们先要去掉开头的get,然后将get后的那个字母小写,得到的username就是与el表达式中对应的部分。

有趣的是${contact.username}仅仅与getUsername()方法对应,无论contact有没有String username这个变量。如果我们想使用${contact.nameAndSex},只要写一个public String getNameAndSex()方法返回我们想要的数据即可,不需要添加String nameAndSex;变量。

另一个放到pageContext中的row就很直观了,我们根据行数的奇偶来决定当前行使用的css样式,这样就可以显示出斑马线的效果了,这项功能并非必要,只是为了娱乐。

5.3. Create(创建)

选择list.jsp中的“添加联系信息”,即进入create.jsp添加信息页面。

create.jsp中没有包含java,它提供给用户一个输入信息的表单,用户填写过信息后就能点击提交按钮,将数据提交给save.jsp处理。

save.jsp中与create.jsp相反,里边只有处理数据的java代码,没有显示的内容。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<jsp:useBean class="anni.Contact" id="contact"/>

<jsp:setProperty name="contact" property="*"/>

<%

contactDao.save(contact);

response.sendRedirect("list.jsp");

%>

jsp:useBean的用处我们已经了解了,先从application中取出contactDao,再创建一个contact。在创建contact的时候没有指定scope,默认情况下只会使用new创建这个局部变量,不会对任何作用域产生影响。

jsp:setProperty是新事物了,它的作用就是为某个javabean设置数据。之前我们已经使用jsp:useBean创建了一个contact实例,现在我们通过name="contact"设置这个实例的数据,property可以指定一个属性,比如property="username",也可以使用星号(*)批量设置所有可以找到的属性,这个jsp动作实际上与下面的代码等价。

contact.setUsername(request.getParameter("username"));

contact.setSex(request.getParameter("sex"));

contact.setEmail(request.getParameter("email"));

contact.setQq(request.getParameter("qq"));

contact.setDescn(request.getParameter("descn"));

这些数据都是由create.jsp提交过来的,只要它们的名称与contact中的方法对应(这次是set开头的方法了),jsp:setProperty就可以自动为它们进行赋值,转换的规则与get的方法名是类似的。

通过这一系列的jsp动作,我们得到的contact中已经设置好了用户刚刚填写的数据,现在只要进行保存就好了。

contactDao.save(contact);

response.sendRedirect("list.jsp");

contactDao.save()也已经封装好了,直接调用便完成添加功能。操作完成后记得要调用sendRedirect,将页面重定向到list.jsp,查看添加后的结果。

5.4. Update(更新)

点击列表右侧的“修改”,对这一行显示的联系信息进行修改。

虽然页面的布局与create.jsp基本相同,但为了预先显示需要修改的数据,我们需要根据请求中的id值,去数据库中查找对应的联系信息,再显示到jsp中。

点击“修改”的时候,id便附加到url后了。

<a href="edit.jsp?id=${contact.id}">修改</a>

edit.jsp中获得id的值,根据id从contactDao获得对应的联系信息contact,再将contact放到pageContext供后面的el表达式使用。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<%

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

Contact contact = contactDao.get(Long.parseLong(id));

pageContext.setAttribute("contact", contact);

%>

在html中显示信息的时候,直接使用el表达式,如果属性值不存在,el也会自动输出空字符串"",这对我们来说都是非常便捷的。

<input type="text" name="username" value="${contact.username}" /> 51 / 148

edit.jsp中还有一点儿隐藏的玄机,表单中定义了一个名叫id的隐藏属性,这样服务器才能知道我们需要修改那一条数据。

<input type="hidden" name="id" value="${contact.id}" />

既然是隐藏的,我们便不能在页面上看到它,但在提交的时候它还会与其他数据一起发送到服务器。

修改信息之后,点击提交发送请求。

update.jsp中与save.jsp相似,唯一不同的是这次我们调用的是contactDao.update()而不是contactDao.save()。

contactDao.update(contact);

update()会根据id的值修改数据库中对应的数据,而不是添加一条新数据,这从跳转后的list.jsp可以看出来。

5.5. Delete(删除)

点击列表右侧的“删除”,就会删除这条数据。

remove.jsp与修改数据时一样,都需要传递一个id来指定要操作哪一条记录。

<a href="remove.jsp?id=${contact.id}">删除</a>

remove.jsp依然没有任何显示,仅仅使用java操作。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<%

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

contactDao.remove(Long.parseLong(id));

response.sendRedirect("list.jsp");

%>

因为只需要id和contactDao,remove.jsp的代码十分单纯,首先从request中获得id,然后删除id对应的记录,最后页面重定向到list.jsp。删除便成功了。

5.6. 用jdbc操作数据库

虽然有人说JDBC是Java Database Bridge Connection(java数据库桥接)的缩写,但sun公司一直没有承认这种解释。不过jdbc确实是一种桥接方式,所有服务器厂商都为jdbc提供对应自己数据库的驱动,我们只要学会使用jdbc中的类和方法,就可以通过它操作任何一款数据库了。

这次我们使用的是一个名叫hsqldb的嵌入型数据库,它是使用java编写的,把hsqldb-1.8.0.7.jar放到WEB-INF/lib/目录下就可以使用了。

现在看我们是如何连接数据库的,hsqldb-1.8.0.7.jar中已经为我们提供了jdbc驱动,为了方便调用我们将jdbc的配置封装在anni.DbUtils中。

1. Class.forName("org.hsqldb.jdbcDriver");加载jdbc驱动。

Class.forName()是惯用写法,可以强制加载指定的类,org.hsqldb.jdbcDriver是hsqldb驱动的名称,只需要记忆即可。

2. 与数据库建立连接。

DriverManager.getConnection("jdbc:hsqldb:res:/hsqldb/contact", "sa", "");

三个参数分别是连接数据库使用的url,登录用户名和密码。

url以jdbc:hsqldb:开头,表明它将使用hsqldb的驱动,后面的res:/hsqldb/contact是hsqldb的一种连接方式,它将去classpath下的hsqldb目录中读取名为contact的数据库文件作为初始配置,在这里classpath就是指的WEB-INF/classes/,你可以在WEB-INF/classes/hsqldb/下看到两个数据库文件,contact.properties和contact.script。

3. 数据库连接十分消耗系统资源,一定要记得在使用完成后关闭,一旦忘记关闭,资源很快就会耗尽,你会得到一连串无法连接数据库的错误。

anni.DbUtils中我们提供了一个close()方法来关闭连接。

完整的anni.DbUtils代码在WEB-INF/src/DbUtils.java。

数据库contact的表结构写在WEB-INF/sql/import.sql中。

create table contact (

id bigint,

username varchar(100),

sex varchar(100),

email varchar(100),

qq varchar(100),

descn varchar(200)

);

与之对应的anni.Contact的结构大致如下。

package anni;

public class Contact {

private Long id;

private String username;

private String sex;

private String email;

private String qq;

private String descn;

// getter and setter

}

javabean中的属性与数据库中的字段一一对应,习惯上将属性定义为private,并配上对应的getter与setter方法,这样就构成了一个典型的javabean。anni.Contact的源代码在WEB-INF/src/Contact.java。

anni.ContactDao是完成CRUD操作的主体,在这里集合了anni.Contact和anni.DbUtils为jsp提供调用的方法。

1. Read(读取)。

/**

* 获得所有联系簿.

*

* @return contact列表

*/

public List<Contact> getAll() throws Exception {

Connection conn = null;

Statement state = null;

ResultSet rs = null;

List<Contact> list = new ArrayList<Contact>();

try {

conn = DbUtils.getConn();

state = conn.createStatement();

rs = state.executeQuery("select * from contact");

while (rs.next()) {

Contact contact = new Contact();

contact.setId(rs.getLong("id"));

contact.setUsername(rs.getString("username"));

contact.setSex(rs.getString("sex"));

contact.setEmail(rs.getString("email"));

contact.setQq(rs.getString("qq"));

contact.setDescn(rs.getString("descn"));


list.add(contact);

}

} finally {

DbUtils.close(rs, state, conn);

}

return list;

}

第一步,使用DbUtils.getConn()建立与数据库的连接Connection。

第二步,从Connection创建一个Statement。

第三步,使用Statement执行sql查询语句,返回查询结果集ResultSet。

第四步,将ResultSet中的数据转换成Contact队列。

第五步,关闭数据库的连接,并返回Contact队列作为结果。


Connection -> Statement -> ResultSet -> close()是一个查询功能的基本结构。

这里使用的sql语句会获得数据库中所有的联系信息,所以我们循环读取ResultSet最后得到一个Contact队列。另一个方法public Contact get(Long id)中会根据指定的主键获得一条对应记录,虽然依然返回ResultSet,但这次ResultSet中只包含一条数据,所以最终只会获得一个Contact对象。

2. Create(创建)

/**

* 向数据库插入一条数据.

*

* @param contact 联系信息

*/

public void save(Contact contact) throws Exception {

Connection conn = null;

PreparedStatement state = null;

try {

conn = DbUtils.getConn();

state = conn.prepareStatement("insert into contact(username,sex,email,qq,descn) values(?,?,?,?,?)");

state.setString(1, contact.getUsername());

state.setString(2, contact.getSex());

state.setString(3, contact.getEmail());

state.setString(4, contact.getQq());

state.setString(5, contact.getDescn());


state.executeUpdate();

} finally {

DbUtils.close(null, state, conn);

}

<p

Recordset (ADO/WFC 语法)

Recordset (ADO/WFC 语法)

包 com.ms.wfc.data

构造函数

publicRecordset()
public Recordset(Object r)

方法

public void addNew(Object[] fieldList, Object[] valueList)
public void addNew(Object[] valueList)
public void addNew()

public void cancel()

public void cancelBatch(int affectRecords)
public void cancelBatch()

public void cancelUpdate()

public Object clone()
public Object clone(int lockType)

public void close()

public int compareBookmarks(Object bookmark1, Object bookmark2)

public void delete(int affectRecords)
public void delete()

public void find(String criteria)
public void find(String criteria, int SkipRecords)
public void find(String criteria, int SkipRecords, int searchDirection)
public void find(String criteria, int SkipRecords, int searchDirection, Object bmkStart)

public Object[][] getRows(int Rows, Object bmkStart, Object[] fieldList)

public void move(int numRecords)
public void move(int numRecords, Object bmkStart)

public void moveFirst()
public void moveLast()
public void moveNext()
public void movePrevious()

public Recordset nextRecordset()
public Recordset nextRecordset(int[] recordsAffected)

public void open()
public void open(Object source)
public void open(Object source, Object activeConnection)
public void open(Object source, Object activeConnection, int cursorType)
public void open(Object source, Object activeConnection, int cursorType,
int lockType)
public void open(Object source, Object activeConnection, int cursorType,
int lockType, int options)

public void requery()
public void requery(int options)

public void resync()
public void resync(int affectRecords, int resyncValues)

public void save(String fileName)
public void save(String fileName, int persistFormat)

public boolean supports(int cursorOptions)

public void update()
public void update(Object[] valueList)
public void update(Object[] fieldList, Object[] valueList)

public void updateBatch()
public void updateBatch(int affectRecords)

属性

public int getAbsolutePage()
public void setAbsolutePage(int page)

public int getAbsolutePosition()
public void setAbsolutePosition(int pos)

public Command getActiveCommand()

public Connection getActiveConnection()
public void setActiveConnection(String conn)
public void setActiveConnection(com.ms.wfc.data.Connection c)

public boolean getBOF()

public boolean getEOF()

public Object getBookmark()
public void setBookmark(Object bmk)

public int getCacheSize()
public void setCacheSize(int size)

public int getCursorLocation()
public void setCursorLocation(int cursorLoc)

public int getCursorType()
public void setCursorType(int cursorType)

public String getDataMember()
public void setDataMember(String pbstrDataMember)

public Iunknown getDataSource()
public void setDataSource(IUnknown dataSource)

public int getEditMode()

public Object getFilter()
public void setFilter(Object filter)

public int getLockType()
public void setLockType(int lockType)

public int getMarshalOptions()
public void setMarshalOptions(int options)

public int getMaxRecords()
public void setMaxRecords(int maxRecords)

public int getPageCount()

public int getPageSize()
public void setPageSize(int pageSize)

public int getRecordCount()

public String getSort()
public void setSort(String criteria)

public String getSource()
public void setSource(String query)
public void setSource(com.ms.wfc.data.Command command)

public int getState()

public int getStatus()

public boolean getStayInSync()
public void setStayInSync(boolean pbStayInSync)

public com.ms.wfc.data.FieldgetField(int n)
public com.ms.wfc.data.Field getField(String n)

public com.ms.wfc.data.Fields getFields()

public AdoProperties getProperties()

事件

有关 ADO/WFC 事件的详细信息,请参阅ADO/WFC 中的 ADO 事件.

public void addOnEndOfRecordset(RecordsetEventHandler handler)
public void removeOnEndOfRecordset(RecordsetEventHandler handler)

public void addOnFetchComplete(RecordsetEventHandler handler)
public void removeOnFetchComplete(RecordsetEventHandler handler)

public void addOnFetchProgress(RecordsetEventHandler handler)
public void removeOnFetchProgress(RecordsetEventHandler handler)

public void addOnFieldChangeComplete(RecordsetEventHandler handler)
public void removeOnFieldChangeComplete(RecordsetEventHandler handler)

public void addOnMoveComplete(RecordsetEventHandler handler)
public void removeOnMoveComplete(RecordsetEventHandler handler)

public void addOnRecordChangeComplete(RecordsetEventHandler handler)
public void removeOnRecordChangeComplete(RecordsetEventHandler handler)

public void addOnRecordsetChangeComplete(RecordsetEventHandler handler)
public void removeOnRecordsetChangeComplete(RecordsetEventHandler handler)

public void addOnWillChangeField(RecordsetEventHandler handler)
public void removeOnWillChangeField(RecordsetEventHandler handler)

public void addOnWillChangeRecord(RecordsetEventHandler handler)
public void removeOnWillChangeRecord(RecordsetEventHandler handler)

public void addOnWillChangeRecordset(RecordsetEventHandler handler)
public void removeOnWillChangeRecordset(RecordsetEventHandler handler)

public void addOnWillMove(RecordsetEventHandler handler)
public void removeOnWillMove(RecordsetEventHandler handler)