java web 开发三剑客 -------电子书

2019年11月09日 阅读数:181
这篇文章主要向大家介绍java web 开发三剑客 -------电子书,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

Internet,人们一般称为因特网,是当今世界上覆盖面最大和应用最普遍的网络。根据英语构词法,Internet是Inter + net,Inter-做为前缀在英语中表示“在一块儿,交互”,由此可知Internet的目的是让各个net交互。因此,Internet实质上是将世界上各个国家、各个网络运营商的多个网络相互链接构成的一个全球范围内的统一网,使各个网络之间可以相互到达。各个国家和运营商构建网络采用的底层技术和实现可能各不相同,但只要采用统一的上层协议(TCP/IP)就能够经过Internet相互通讯。css

为了使各个网络中的主机可以相互访问,Internet为每一个主机分配一个地址,称为IP地址,IPv4的IP地址是32位二进制数字,一般人们用4个0~255的数字表示,例如:127.0.0.1,称为“点分十进制”表示法。图1.1是Internet物理结构的示意图。html

图1.1  Internet物理结构示意图java

Internet物理结构如图1.1所示,它将若干个子网经过路由器链接起来,这些子网能够具备不一样类型的网络结构,但子网中的每一个主机必须拥有全局惟一的IP地址;路由器是用于转发子网之间数据的设备,路由器上有若干个端口,每一个端口拥有一个IP地址,一个端口能够链接一个子网。Internet上的数据能够从一个主机发送到另一个主机,数据以数据包的形式传送;源主机在发送数据包时会在数据包前面加上目的主机的IP地址,路由器经过识别IP地址将数据包发送到适当的子网中;当数据在子网中传播时,拥有该IP地址的主机就会接收该数据包。不少计算机网络教材都使用邮政寄信的例子形象地说明了这个Internet中数据包的传送过程。程序员

Internet底层的组织和传输原理是很复杂的,感兴趣的读者能够选择相关的计算机网络教材进行深刻学习。但做为开发Web应用的软件工程师,一般只是从Internet的应用层面考虑Internet的原理;从应用层面的角度考虑,能够认为Internet是链接全部主机的一个庞大的网络体系,每一个主机拥有一个IP地址,主机之间经过IP地址相互传递信息和数据。Web应用实质上是一种特殊的应用,它能够在Internet的主机之间相互交流具备预约义格式的信息和数据。web

典型的Web应用是B/S模式(浏览器/服务器模式),即Internet上的两台主机,一台充当服务器,另外一台充当客户机,客户机经过本机的浏览器与服务器进行通讯,如图1.2所示。正则表达式

在图1.2中,客户机向服务器发出请求,服务器接收并处理请求,而后将对该请求的响应传送给客户机。以访问希赛网主页为例,读者在浏览器中键入希赛网的主页地址“www.csai.cn”,回车后浏览器就会向希赛网的服务器发送一个请求而且将本身的IP地址连同请求一块发送,该请求要求浏览希赛网的主页,希赛网的服务器接收到该请求而且取出客户机的IP地址,而后将希赛网的主页做为数据包发出,而且以客户机的IP地址做为目的地址。当数据包传送到客户机后,读者的浏览器就能够显示希赛网的主页了。算法

图1.2  B/S模式示意图spring

一般Web应用是运行在服务器中的一个应用程序,在上例中希赛网Web服务器中处理客户机响应的程序就是一个典型的Web应用;接收请求、分析请求、构造响应、发送响应都是由该Web应用完成的,这几项工做也是大多数Web应用的主要工做。所谓接收请求就是监听服务器的特定端口,当有请求到达端口时就读取该请求,这一般都是由Web容器(例如Tomcat)完成的;所谓分析请求就是解析收到的请求,从中得到请求的内容;所谓构造响应就是根据客户的请求,在进行适当的动做后,构造适当的响应数据;所谓发送响应就是将构造好的响应数据发送给客户机,这一般也是由Web容器自动完成的。因此,Web应用的核心就是如何分析请求、完成相应动做并构造响应。而这其中的分析请求和构造响应都是与Internet的一种传输协议——HTTP——紧密相关的,由于它规定了Web应用中的数据在网络中的传输方式和传输格式。
     ① IPv4是IP的第4版,很长时间以来该IP版本一直是Internet中使用的标准版本,但随着Internet的发展和扩大,IPv4开始展示出一些弊端,因此开始出现IPv6,并将替代IPv4。IPv6的IP地址是128位二进制数字。数据库

 
 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日express

 

HTTP

 

HTTP的全称是HyperText Transfer Protocal,即超文本传输协议。它是Internet的应用层协议,它定义了客户机的浏览器与服务器的Web应用之间如何进行通讯,以及通讯时用于传递数据的数据包的格式等内容。目前使用的HTTP是HTTP1.1版。

HTTP是采用请求/响应模式的无状态协议。客户机浏览器和服务器Web应用采用HTTP协议进行通讯时,通讯由浏览器发起;浏览器向Web应用发送一个请求,Web应用接收并处理该请求,而后向浏览器发回响应。在请求/响应过程当中,Web应用不保存与任何一个客户机通讯的状态,它只对到来的当前请求进行处理,处理完返回对应于该请求的响应;任何两个请求的处理都是独立的,不管这两个请求是来自同一个客户机仍是不一样的客户机。

图1.3为Web服务器同时响应多个客户机浏览器请求的示意图。当同时有多个客户机向同一个Web应用发出请求时,Web服务器就为每个请求建立一个服务进程/线程用以处理这一请求;即便是同一个客户机发送的两个请求,Web服务器也会建立两个服务进程/线程用于处理两个请求。

图1.3  Web服务器与客户浏览器交互示意图

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HTTP请求与响应

 

经过以上对HTTP通讯方式的介绍能够发现,HTTP请求和HTTP响应在HTTP通讯中起到了相当重要的做用,由于浏览器和Web应用之间的全部通讯都是依靠请求和响应完成的。一个典型的HTTP请求消息的内容以下:

GET / HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd. ms-excel, application/vnd.ms-powerpoint, application/msword, */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)

Host: www.csai.cn

该消息用于请求http://www.csai.cn的主页。对请求的响应消息以下(HTML页面内容部分用“…”省略):

HTTP/1.1 200 OK

Server: Microsoft-IIS/5.0

Content-Location: http://www.csai.cn/index.htm

Date: Mon, 24 Dec 2007 08:31:08 GMT

Content-Type: text/html

Accept-Ranges: bytes

Last-Modified: Mon, 24 Dec 2007 02:48:20 GMT

Content-Length: 60744

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">

<head>

<title>希赛网_中国IT技术门户_为企业和IT技术人员提供最全面的服务平台</title>

</body>

</html>

这一对请求/响应消息是使用IE浏览器访问希赛主页时产生的HTTP消息流。在IE的地址栏中键入希赛网主页的地址http://www.csai.cn,单击回车后,IE浏览器便会将这一段请求消息以文本的形式发送出去,通过网络传递到希赛网的Web服务器上,Web服务器通过分析发现该客户端请求的是希赛网的主页,因而将希赛网的主页放在响应消息中发送回客户机的浏览器。下面对HTTP请求和响应消息分别进行详细介绍。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HTTP请求消息

 

HTTP请求消息由Request-Line(请求行)、Header Field(头域)和Message-Body(消息体)组成,如图1.4所示。

图1.4  HTTP请求消息格式

Request-Line在HTTP请求消息的第一行,通常格式是:

Request-Line = Method[SP]Request-URI[SP]HTTP-Version CRLF

其中Method称为HTTP方法(HTTP Method),它表示该请求所要进行的操做类型;Request-URI称为请求URI,它表示与该请求有关的Web服务器中的资源定位符;HTTP-Version表示该请求使用的HTTP协议的版本号,通常是HTTP/1.0或HTTP/1.1,目前使用的HTTP版本大部分都是HTTP/1.1。[SP]表示空格,CRLF表示回车换行,它们都是格式信息,用于分隔各部分信息。例如:

GET /index.htm HTTP/1.1

就是一个典型的Request-Line,其中GET是HTTP方法、/index.htm是Request-URI、HTTP/1.1是HTTP版本号。

头域紧跟在Request-Line的后面,每一个域一行,本节后面部分将会详细介绍头域。消息体在头域后面,与头域隔一个空行,不过并非全部HTTP请求消息都有消息体,有些就没有消息体,这是由该HTTP请求消息的HTTP方法类型决定的。

1.HTTP方法

HTTP请求消息经过使用不一样的HTTP方法来向接收到请求的主机说明其请求所指望执行的操做。HTTP/1.1总共定义了OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE和CONNECT八种HTTP方法,其中GET方法和POST方法是最多见的也是使用最多的HTTP方法,其余方法使用得不多,甚至有些方法在不少服务器中都会被屏蔽或者忽略,因此本书将只重点针对GET方法和POST方法进行详细介绍。

平时读者在上网浏览网页时基本上都是用GET方法,GET方法向服务器申请请求URI指定的资源。请求URI可能指向的是一个服务器Web路径下的一个文件,接收到请求后Web服务器会将该文件的内容做为HTTP响应的内容返回给浏览器;请求URI也可能指向一个数据处理过程(好比一个Servlet),那么Web服务器会执行该过程并将该过程执行结束后向客户端反馈的结果信息加入到HTTP响应中返回。可见在使用GET方法进行的请求响应过程当中,数据流向主要是从服务器向客户机,因此GET请求消息的消息体一般不包含任何内容。通常在以下场景会使用GET方法:

在浏览器中键入网页地址,从Web服务器上获取网页中的全部内容,例如HTML、图片、Flash、JavaScript等。请求每一项内容时都会将一个GET请求提交给服务器,而后服务器会处理每个请求并将请求的内容做为响应返回给浏览器。

单击网页上的一个图片连接打开一个图片。浏览器会将图片的URI构形成一个请求消息,并将请求消息提交给服务器,服务器接收到请求消息,解析请求URI,而后将URI指向的图片返回给浏览器。

POST方法则刚好与GET方法相反,POST方法主要用于向服务器提交数据内容;因此通常来讲,POST消息的消息体中会包含提交的数据内容。POST消息中请求URI也能够是一个文件位置或者数据处理过程,假如指向的是一个文件位置,那么Web服务器会将POST消息体中携带的数据做为一个文件保存在指定的位置;若是指向的是一个数据处理过程,那么Web服务器会将POST消息体中携带的数据传递给该数据处理过程,并启动该数据处理过程对数据进行处理。一般POST方法会被使用到以下场景:

提交登陆信息。当输入完用户名和密码、单击登陆按钮时,浏览器就会将登陆信息(用户名和密码,为了安全起见,不少系统会对密码加密)做为POST消息的消息体提交给Web服务器。

在论坛中发帖子。帖子的标题和内容会做为POST消息的消息体提交给Web服务器。

发送E-mail。E-mail的各项信息(发件人、收件人、抄送、密送、标题、正文等)会组织成必定的格式,而后做为POST消息的消息体提交给Web服务器。 

2.Request-URI

Request-URI称为请求URI,它是一个不含空白字符的字符串,符合URI(资源定位符)的格式规范,表示Web服务器上的一个资源位置,能够是如下四种格式:

Request-URI = "*" | absoluteURI | abs_path | authority

* 表示该Request-URI并不指向某个特定的位置,说明该HTTP请求消息所请求的操做是针对整个Web服务器、而不是针对某个特定资源的。固然并非全部的HTTP方法都可以使用 * 做为Request-URI,只有某些特定的HTTP方法才能够,好比OPTIONS。

absoluteURI是一个用绝对形式表示的URI,即以协议开头的URI,好比:“http://www.csai.cn/image/bg.png”,这种表示形式单独就能指定一个惟一的网络资源位置。

abs_path是一个用相对形式表示的URI,但它必须是一个Web服务器上的绝对路径,必须以一个 / 开头,例如:/image/bg.png。这种表示形式指定了一个从Web服务器根目录开始的相对路径。Web服务器根目录是服务器设置的全部Web资源的顶层目录。假设,域名为“csai.cn”的Web服务器设置的根目录是“D:\webroot”,那么URL“http://www.csai.cn/index.htm”就是请求Web服务器上的文件“D:\webroot\index.htm”。可见,使用abs_path的Request-URI只是指定了Web服务器内部的路径,并无指定Web服务器的主机地址,因此它不能单独用于指定一个网络位置。用这种Request-URI的HTTP请求消息都会有一个名为Host的头域,它的值就用于指定一个主机的地址,好比:Host头域值为“www.csai.cn”,Request-URI为“/image/bg.png”的HTTP请求消息所指定资源位置也是“http://www.csai.cn/image/bg.png”。

authority仅能被用于CONNECT方法。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HTTP响应消息

 

HTTP响应消息是Web服务器在处理完HTTP请求消息后返回给客户机浏览器的消息,它也由状态行、头域和消息体组成,如图1.5所示:


图1.5  HTTP响应消息格式

状态行的通常格式以下:

Status-Line = HTTP-Version[SP]Status-Code[SP]Reason-Phrase CRLF

其中,HTTP-Version、SP和CRLF的意义与请求消息中的同样。Status-Code是响应状态码,它是3位十进制数,HTTP/1.1预约义了不少状态码,用于表示服务器处理请求的状态;Reason-Phrase是一个简短的文字,它对响应码进行文字性说明。Status-Code根据首位数字的不一样可分为以下五大类:

1.1xx:信息响应类,表示接收到请求而且继续处理。例如“100 Continue”表示服务器已接收并开始处理请求,要求客户机继续发送请求的剩余部分,若是请求已被发送彻底,客户机能够忽略该消息。

2.2xx:处理成功响应类,表示动做被成功接收、理解和接受。例如“200 OK”表示请求的操做已成功完成,对于GET请求则表示请求的资源已附在响应消息中,对于POST请求则表示提交的内容已被处理。

3.3xx:重定向响应类,为了完成指定的动做,必须接受进一步处理。例如“301 Moved Permanently”表示请求的资源已被永久移往另一个URI,日后对该资源的请求应该都替换成新的URI,新的URI将由响应消息的Location头域说明;“302 Found”表示请求应该暂时被重定向为另一个URI,之后对该资源的请求应该仍是使用当前的URI。

4.4xx:客户端错误类,客户请求包含语法错误或者是不能被正确执行。例如“400 Bad Request”表示客户端提交的请求没法被服务器理解,客户端须要对请求从新改动后再提交请求;“403 Forbidden”表示服务器已理解客户端的请求,可是服务器拒绝执行客户端请求的操做;“404 Not Found”表示客户端请求中Request-URI指定的资源位置不存在。

5.5xx:服务端错误类,服务器不能正确执行一个正确的请求。例如“500 Internal Server Error”表示服务器遭遇一个非预期错误而致使没法完成请求的操做。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Header Field

 

如前面所述,在HTTP请求消息和响应消息中都包含Header Field,这些头域用于说明一些辅助信息,以便于丰富客户机和服务器之间的通讯。有些头域用于说明一些通用信息,称为General Header Field(通用头域),便可以用于请求消息也能够用于响应消息;有些头域只被用于请求消息,称为Request Header Field(请求头域);有些头域只被用于响应消息,称为Response Header Field(响应头域);有些头域用于说明传输内容的信息,它们能够被用于请求消息也能够被用于响应消息。整个头域由多条头域项组成,每条头域项占一行。头域项的通常格式为:

Field-Name: Field -Value

其中Field -Name是头域名,Field -Value是头域值。

1.General Header Field

这类头域既能够出如今请求消息中也能够出如今响应消息中,它们只描述了传递消息的一些属性,而不能用于描述传送文件的信息。常见的有以下几种。

Cache-Control:用于指定一种缓冲机制,这种缓冲机制在整个请求/响应过程当中必须被遵照。该头域中指定的缓冲机制将覆盖默认的缓冲机制。例如:

Cache-Control: no-cache

Date:表示消息生成时的日期时间,该域所使用的日期格式必须符合HTTP日期格式,例如:

Date: Tue, 13 Nov 2007 08:12:31 GMT

Pragma:用于指定一些实现相关的参数,在HTTP协议中并无规定该头域所携带参数的意义,例如:

Pragma: “string”

其中“string”表示一个由引号括起的字符串,各类对HTTP协议的不一样实现(例如不一样的浏览器和服务器)能够利用该头域定义用于传递特定信息的一系列字符串。

Transfer-Encoding:若是该头域被指定,那就说明消息体采用了所指定的传输类型进行传输。例如最多见的:

Transfer-Encoding: chunked

表示消息体采用分块传输的方式进行传输。

2.Request Header Field

这类头域只出如今请求消息中,它们一般被客户机用于向服务器传递一些客户机的信息或者请求消息的信息。常见的有以下几种。

Accept:能够被用来讲明客户机浏览器可以接受的媒体格式,例如:

Accept: text/html, text/plain, image/*

表示客户机浏览器接受HTML和纯文本以及各类图片格式。

Accept-Charset:能够被用来讲明客户机浏览器可以接受的字符编码方式,例如:

Accept-Charset: iso-8859-1, gb2312

表示客户机浏览器接受的字符编码格式有ISO—8859—1(也就是ASCII编码)和gb2312(一种简体中文编码)。

Accept-Encoding:能够被用来讲明客户机浏览器可以接受的内容编码方法,一般用来指定内容的压缩方法,例如:

Accept-Encoding: gzip, identity

表示客户机浏览器接受gzip压缩方式和不压缩。

Accept-Language:能够被用来讲明客户机浏览器可以接受的语言,例如:

Accept-Language: zh-CN

表示客户机浏览器接受简体中文。

From:表示提交该请求的终端用户的电子邮件,例如:

From: user@company.com

表示提交该请求的终端用户的电子邮件地址为user@company.com。

Host:指示Internet上的一个主机和端口号,主机一般是域名或者IP地址,例如:

Host: www.csai.cn

表示该请求访问的主机的域名为www.csai.cn。

If-Match:若是HTTP请求中含有该头域或者后面将要提到的If-ModifiedSince,If-None-Match,If-Range和If-Unmodified-Since头域时,那么该请求就变成了“条件请求”,即只有知足上述描述的条件时请求的操做才要被执行,这样能够减小没必要要的资源浪费。该域的值是一个匹配字符串,若是该匹配字符串匹配成功则执行操做,不然不执行操做。在匹配字符串中*表示任意。例如:

If-Match: *

表示匹配任何资源。

If-None-Match:意义与If-Match刚好相反,表示匹配不成功则执行,不然不执行。

If-Modified-Since:值是一个日期,表示请求的资源若是从给定的日期后修改过则执行操做,不然不执行。例如

If-Modified-Since: Tue, 13 Nov 2007 08:12:31 GMT

表示:若是请求的文件在2007-11-13 08:12:31后被更改过,则执行操做。

If-Unmodified-Since:意义与If-Modified-Since刚好相反,表示:请求的资源若是从给定的日期后没有被修改过则执行操做,不然不执行。

If-Range:假如客户机的缓冲池中已有了资源实体的一部分,而指望得到剩余部分,则客户机的请求能够使用该头域。它表示:“若是指定的资源实体没有被更改则将缺乏的发给我,不然发给我整个资源实体”。

Max-Forwards:在TRACE和OPTIONS方法中使用,用于限制消息在网络中传播的跳数,即消息被代理或者网关转发的次数,以此来限制消息的生命期。

Range:用于指定一个范围,它表示请求的资源实体的范围,能够使用字节数指定。If-Range须要的范围就是经过该头域指定的。

Referer:客户机用该域告诉服务器,请求中的Request-URI是如何得到的。例如

Referer: http://www.csai.cn/index.htm

表示当前请求资源的URI是从页面http://www.csai.cn/index.htm中得到的。

User-Agent:能够被用来讲明客户机浏览器的型号,例如

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)

表示客户机是使用Mozilla/4.0兼容浏览器、IE6.0等。

3.Response Header Field

这类头域只出如今响应消息中,它们一般被服务器用于向客户机传递一些服务器的信息或者响应消息的信息。常见的有如下几类。

Accept-Ranges:服务器用于指示它所接受的Range类型,好比

Accept-Ranges: bytes

表示服务器接受以byte形式指示的Range。

Accept-Ranges: none

表示服务器不接受任何形式的Range。

Age:顾名思义,在响应消息中该头域表示响应消息的“年龄”,也就是服务器估计的该响应消息产生后的时间长度。

Location:当响应消息的响应码为3xx时,该头域会被响应消息用于指示重定向后新的URL。

Retry-After:一般用于响应码为503的响应消息,503响应消息表示服务器当前不可用,该头域估计了一个服务器不可用的时间。头域值能够是一个HTTP日期或者是一个数字。例如:

Retry-After: Tue, 13 Nov 2007 08:12:31 GMT

表示服务器在2007-11-13 08:12:31以前不可用,请在该时间之后重试。

Retry-After: 120

表示服务器当前不可用,请在120秒后重试。

Server:表示运行在服务器上用于处理请求的软件的信息。

4.Entity Header Field

该类头域描述了消息体中携带的数据的元数据(即对数据的长度、类型、修改时间等属性的描述信息),请求消息和响应消息中均可以包含这类头域。常见的有如下几类。

Allow:表示Request-URI指定的资源实体所支持的HTTP方法列表,在响应码为405的响应消息中必须包含该头域。例如:

Allow: GET, HEAD, PUT

表示Request-URI指定的资源实体仅支持GET、HEAD和PUT三种HTTP方法。

Content-Encoding:指示消息内容的编码方法,一般指示内容的压缩算法。例如:

Content-Encoding: gzip

表示消息中数据采用gzip算法编码。

Content-Language:表示消息内容所采用的天然语言。例如:

Content-Language: en

表示消息体中数据表示的内容是英文的。

Content-Length:表示消息长度。头域值是十进制数,表示字节数。例如:

Content-Length: 2353

表示消息体中数据的长度为2353字节。

Content-Location:表示除了Request-URI指定的位置外,其余能够访问到消息内容的位置。

Content-MD5:表示消息体中数据的MD5校验码,用来实现端到端的消息完整性检查。

Content-Range:当传递的数据是整个资源实体的一部分时,用该域说明当前传递的数据是资源实体的哪一部分。例如:

Content-Range: 0-500/1023

表示资源实体总共范围为0-1023,而当前传递的是0-500。

Content-Type:指示消息体中内容的媒体格式。例如:

Content-Type: text/html; charset=iso-8859-1

表示消息体中携带的内容是HTML文档,它的媒体格式是text大类中的HTML子类,文档的字符编码是ISO—8859—1;

Expires:指定了一个日期,表示消息体中的内容在该日期以前有效,过了该日期则消息内容就过期了。

Last-Modified:表示消息中携带的内容实体的最后修改时间。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HTML

 

HTTP协议规定了如何在客户机浏览器和服务器Web应用之间交换信息和传送数据,可是并无定义交换数据的格式以及浏览器在获取数据后如何按照数据建立者的意愿有效地显示数据。假如全部交换的数据都只是二进制数的文件,那么HTTP就和文件传输没什么区别了,并且如今的Web也就不会如此绚丽多彩了。正是HTML的出现,才使得Web逐渐发展得像如今这样的丰富多彩。

HTML的全称是HyperText Markup Language,即超文本标记语言。它是一种规范,这个规范定义了一系列标记以及这些标记的结构。浏览器能够将任何符合该规范的文档(一般为HTML或HTML文档)进行解析而且按照HTML文档的结构进行格式化展现。客户机浏览器和Web服务器能够经过互相交换HTML文档实现具备丰富格式信息的数据传送。以下是一个HTML文档的简单框架:

示例1.1

<html>

<head>

    <title>这是HTML标题</title>

</head>

<body>

    这是HTML内容

    </body>

</html>

HTML文档使用一系列标签将文本组织成特定的结构,而且能够经过特定的标签使得文档在浏览器中展现时能够引入丰富的颜色、图片、字体等信息。HTML文档的结构是由标签包含关系标示的一种层次结构,顶层标签是<html>。

读者能够将编写的HTML文档保存到本地硬盘(后缀名为.htm或者.html),而后使用浏览器打开就能够看到HTML展现出来的效果。对于示例1.1中所示的HTML文档,读者能够将其保存为test.htm,而后用浏览器打开该文件,就能够看到如图1.6所示的效果:

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

标签和属性

 

HTML文档的内容经过一系列标签进行格式化,如示例1.1所示,<html>、<head>、</head>、</body>等都是HTML标签。HTML标签分为开始标签和结束标签,开始标签由一对尖括号括起来,尖括号中的文字是标签的名称,结束标签与开始标签有相同的名称,而且在左尖括号和标签名称之间加了一个 / ;HTML中的大部分标签都是成对的,例如<html>和</html>、<head>和</head>;一对标签之间能够包含文字也能够包含其余标签。另外,有一种特殊的写法<tag/>,就是将 / 写在右尖括号的前面,这是<tag></tag>的简写形式,它表示<tag>标签中不包含任何内容。

图1.6  test.htm页面效果

HTML标签除了能够组织内容以外,大多数的HTML标签还能够定义一系列的属性用于补充说明标签的一些附加信息,属性都写在开始标签中,例如:

<body bgcolor="red">

            ...

</body>

表示将该HTML页面的背景色设置为红色。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

经常使用标签

 

HTML规范中定义了许多标签以及标签所可以定义的属性,有些标签用于说明一种格式信息,好比<br>、<p>等;有些标签用于说明必定的动做信息,好比<a>等;另外一些标签用于插入指定的对象,好比<img>等。下面将对HTML中一些经常使用的标签及其经常使用的属性进行介绍。

1.页面标签

示例1.1给出了一个HTML文档的基本结构,其中用<html>、<head>、<title>和<body>规定了文档的总体结构,<head>标签中是头部信息,其中能够定义一些辅助信息,这些信息不会显示在浏览器页面的正文中,例如<title>定义了页面的标题,它显示在浏览器的标题栏上。<body>标签中的内容是HTML文档的主体,须要显示在浏览器页面正文中的内容所有写在该标签中。

<head>中除了能够包含<title>外,还能够包含其余的标签,其中常见的有如下两种。

link:能够用于连接一些其余文档,最多见的是使用该标签连接样式表(Style Sheet),例如:

<link rel="stylesheet" type="text/css" href="theme.css" />

表示连接theme.css,用它定义的样式做为本页的格式。样式表中定义了一系列文档中使用的样式格式,例如文字的颜色、字体、大小,页面的宽度等。

meta:用于定义页面的一些元数据信息,最多见的是使用该标签订义页面的媒体格式和字符编码方式,例如:

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

表示该页面的类型是text/html,字符编码格式是ISO—8859—1。

<body>标签的内容包含了html文档所要显示的绝大多数内容,全部须要在浏览器页面正文中显示的内容都必须定义在该标签中;并且,<body>标签的属性也能够用于规定整个页面的展现方式。<body>标签常见的属性如表1.1所示。

表1.1  body标签属性

2.格式标签

在HTML文件中文字的位置、文字之间的回车换行和空格等都不会被最终显示到浏览器上,要控制HTML文档中的文字最终如何在浏览器中布局,须要使用HTML的格式标签。HTML定义了丰富的用于定义格式的标签,例如,<p>、<br>等。

(1)文字的控制

文字有不少属性能够设置,例如大小、颜色、字体、是否加粗、是否斜体等。HTML中提供了一个通用的标签用于设置文字的属性,即<font>,也有一些标签能够方便地设置文字的一种属性,例如<hx>(一系列标签<h1>、<h2>、<h3>、…的总称)能够方便地定义不一样大小的文字。

<font>标签是一个用于设置文字字体的通用方法,它经过不一样属性来设置文字的不一样方面:size属性用于设置文字的大小、face属性用于设置字体、color属性用于设置文字的颜色;

<hx>标签是一组标签的总称,x能够是一、二、三、…它们都表示页面的标题,不一样的x表示的标题级别不同,x越大级别越低,所包含文字的字体也会越小;每一个标题占一行。

<b>和<strong>标签表示将文字加粗;

<i>和<em>标签表示将文字变成斜体;

<u>标签表示给文字加下画线;

<s>和<strike>标签都表示给文字加一个中画线;

<sup>标签表示将文字做为上角标;

<sub>标签表示将文字做为下角标。

各类控制文字的标签示例如表1.2所示。

表1.2  各类控制文字的标签示例 

(2)行的控制

<p>表示在该标签中的文字造成一个单独的段落,一般段落与段落之间有一个空行;

<br>表示换行,即该标签以前是一行,该标签以后是另一行,如表1.3所示

表1.3  控制行的标签示例 

(3)布局的控制

假如只能控制行,那获得的页面将只能像文本文件同样很是枯燥,HTML提供了更多的标签以及标签的属性用于定义丰富的布局格式。

align属性一般用于规定标签内容的对齐方式,<hx>、<p>、<div>标签都有该属性,能够经过将该属性的值指定为center、left或right以用于将内容居中、居左或居右对齐。

列表是一种常用的布局方式,HTML的<ul>标签用于定义无序的列表,<ol>标签用于定义有序的列表。<li>表示列表的一项,并且能够经过定义<li>标签的start属性指定有序列表的起始序号,定义<li> 标签的type属性指定序号的形状。

除此以外,HTML还有一个标签<pre>能够定义预格式化的文本,即该标签内的文字将不按HTML规范进行解析,而是将其中的内容原封不动、保持格式显示在浏览器中,如表1.4所示。

表1.4  控制布局的标签示例 

3.表格

表格是HTML中使用最多也是最重要的一种技巧,一般大部分网页设计师用表格控制页面内容在整个页面中的分布,而且能够经过使用嵌套的表格将页面进行任意的划分。表格都用顶层标签<table>进行定义,<th>标签用于定义表头,<tr>标签用于定义一行,<td>标签用于定义一行中的一列,如表1.5所示。

表1.5  控制表格的标签示例 

4.表单

表单在HTML中是很是重要的,它提供了一系列能够展示在浏览器中而且可以提供交互的功能组件,例如:文本框、密码框、文本域、按钮等。能够使用表格来组织表单中的组件,如表1.6所示。

表1.6  表单示例

Form标签的action属性指向一个连接,当表单被提交时就会连接到该连接所指向的地址。button类型、reset类型和submit类型的input组件都是按钮,button按钮是普通的按钮,reset按钮能够将表单中的内容清空,submit按钮能够提交表单。

5.其余

除了以上介绍的这些标签外,HTML还有许多很重要和很经常使用的标签,例如:

<a>标签主要用于定义一个超连接,其href属性用于指定超连接的地址;

<img>标签用于在网页中以连接的方式加入一个图片,其src属性用于指定待连接图片的位置;

<hr>标签能够在页面上加一个水平的分隔线,如表1.7所示。

表1.7  其余标签示例 

HTML有不少的标签,大部分标签也都定义了不少的属性,熟悉掌握它们对于Web应用开发是很是重要的。本章因为篇幅有限只能介绍很是有限的内容,但愿读者在学习完本节后本身再收集一些资料自行学习。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Cookie和Session

 

读者可能在平时上网、阅读某些Internet方面的资料或者在一些能够开发Web应用的开发语言中已经不止一次地看到过Cookie和Session这两个概念,由于这两个概念在Web中确实起到了举足轻重的做用。正如前面在介绍HTTP协议时提到的,HTTP协议是无状态协议,协议自己不会保存任何对方的状态信息,可是在许多时候服务器会但愿保存一些必要的客户端信息或者但愿可以在客户端保存一些信息,以便在客户端下次访问时携带上这些信息。这就是Cookie机制和Session机制出现的缘由。这两种技术都不是HTTP协议规定的内容,可是它们被普遍使用于Web系统中。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Cookie

 

Cookie是Web服务器要求客户机浏览器保存在客户机本地的一个很是小的文本文件,该文件的内容由服务器指定;同时服务器还指定了一个URL集合,当浏览器下次访问这些URL中的任何一个时都会携带上Cookie中的内容。Cookie中的内容实质上是一系列属性,属性由属性名和属性值组成,属性名和属性值都是由Web服务器指定的。

Cookie的实现很是简单,却有着旺盛的生命力和普遍的应用。一个简单应用Cookie的例子是页面能够记录用户登陆名:当用户登陆某网站时,会输入用户名和密码,当用户登陆成功后,Web服务器会将用户的登陆名使用Set-Cookie头域设置到客户机本地硬盘,并要求客户机在用户下一次打开登陆页面时携带用户的登陆名信息,Web服务器就能够取出用户的登陆名信息而且将其设置到用户名输入框中。这个过程的HTTP消息序列如表1.8所示。

表1.8  Cookie工做过程

在这个例子中,最关键的就是第4步中的Set-Cookie头域和第6步的Cookie头域。第4步中服务器告诉客户机浏览器要将username=zhangsan这么一 个名值对记录到本地Cookie中,第6步浏览器在下次访问该网页时将该名值对携带在HTTP消息中以便于服务器得到该信息。在Set-Cookie头域的值中,除了定义须要携带的属性信息外,还定义了expires属性、domain属性和path属性。其中expires表示过时时间,即表示这个Cookie设置到该日期之后就无效了。domain属性和path属性合起来规定了一系列URL,即只要访问的URL的域名是example.com,Request-URI是以/login开始的话,那么就将该Cookie携带在HTTP请求头中。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Session

 

考虑一个简单的情形:邮件服务器中每一个用户的邮件内容应该是受密码保护的,在没有使用正确的用户名和密码登陆后是不能访问的。用户的邮箱中有许多资源,例如邮件列表、邮件内容、通信录等,因为每一个资源都应该受密码保护,因此用户在每一次请求任何一个资源时都必须输入用户名和密码,不然别人就有可能经过构造该资源的URL访问到该资源。这是由于HTTP是无状态协议,一次请求中的登陆信息没法被其余请求使用。可是,如此频繁的输入用户名和密码对于用户来讲是没法容忍的。

这种情形在Web应用中是广泛存在的,由于有不少Web应用都须要身份验证。Session机制是一种服务器端的机制,它能够解决上面提到的情形:当用户登陆成功时,服务器经过一种特殊的算法生成一个不会重复而且很难找到规律的字符串,称为Session ID,而且将Session ID保存在本地Session ID库中(使用一种相似于散列表的结构来保存信息),同时经过某种机制将Session ID告诉给客户机;当服务器接收到任何访问受保护资源的请求时,只须要查看请求中是否已携带Session ID而且检查携带的Session ID是否已在Session ID库中存在,假如存在则表示该客户机已成功登陆,不然拒绝该请求。

1.Session ID的传递方式

Session ID是Session机制的关键,只有经过将Session ID在服务器和客户机之间传递才可以实现Session的做用。Session ID最经常使用的传递方式是利用Cookie进行传递。

当用户登陆成功后,服务器将Session ID做为一个Cookie的属性发送给客户机,而且适当设置domain和path,使得客户机在访问须要受密码保护的资源时都携带上该Cookie属性。例如:

...

Set-Cookie: sessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99

       zWpBng!-a; expires: Mon, 31-Mar-2008 00:00:00 GMT; domain: example.com; 

       path: /mail

...

...

Cookie: sessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99

       zWpBng!-a

...

除了Cookie的方式,也能够经过URL来传递Session ID。当用户登陆成功后,服务器将在每一个须要访问受密码保护资源的URL中都加入Session ID,例如:

http://www.example.com/mail?sessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-a

2.Session ID的过时

关于Session ID过时的问题,经常会有一种误解:“只要关闭浏览器,Session就消失了”。其实简单考虑就能够明白这是一种误解,Session是一种服务器机制,Session ID是由服务器生成而且保存在服务器本地的,对于任何一个携带Session ID的请求,只要携带的Session ID在服务器本地的Session ID库中存在,就能够访问受保护的资源,也就是说明Session ID是有效的。可见,Session ID是否过时与客户机并无任何关系,而只与服务器本地的Session ID库中有哪些Session ID有关,Session ID库中不存在的Session ID视为无效;而当客户机关闭浏览器时一般并不会通知服务器,因此服务器也不会主动删除该Session ID。偏偏是因为关闭浏览器不会致使Session被删除,故服务器才须要为Session设置一个失效时间,当服务器发现客户端中止活动的时间超过这个失效时间时,就会把对应的Session ID删除。

Session的本意是“会话”的意思,会话表示一组顺序并且相互关联的对话过程。HTTP是无状态协议,假如将HTTP的一次请求/响应过程看做是一次对话,那么无状态就表示每一次对话和其余对话之间都是没有关联的,即本次对话时已经忘记了上次对话的状况甚至已经忘记了之前是否对话过。因此,引入Session机制让Web服务器与一个客户端的交流过程更像是一个对话。

 
 
 
 

第 1 章:Web基础技术做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

本章简单介绍了一些基本的Web技术,主要包括Web的基石——HTTP协议和HTML,以及Web应用中常常遇到的两个概念:Cookie和Session。

HTTP协议是创建在请求/响应机制基础上的一种Internet应用层协议,它规定了客户机浏览器和Web服务器的通讯模式,以及通讯中所使用的HTTP请求消息和HTTP响应消息的格式;HTML是一种格式化的超文本表示语言,它经过定义一些标签能够在浏览器中展现丰富多彩的网页内容;Cookie是Web服务器在客户机本地保存的一个小的文本文件,而且能够在访问适当的Web页面时携带返回给Web服务器;Session是一种服务器机制,它向客户机发送一个Session ID而且要求客户机在访问特定资源时携带该Session ID,以此来甄别客户机请求的合法性。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Java Web开发技术

 

起初,全部的Web应用都是静态的,网页只是做为一个信息发布的平台,客户机请求服务器上的某个资源,服务器将指定的资源返回给客户机;所谓Web开发也只是编写可以有效组织信息的HTML文档,并将其适当地相互连接;能够说,这个时候开发一个Web应用还不能称为真正的Web开发。但实际上,这种模式对于不少Web应用已经足够了,它们只须要经过Web发布和接收一些信息。可是,随着网络的普及和Web应用的愈来愈复杂便使得静态的Web开发技术没法知足要求了。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

CGI技术

 

把Web应用带向动态化的第一个技术就是CGI,即通用网关接口(Common Gateway Interface)。它是外部程序和Web服务器之间的标准编程接口,在物理上它是一段运行在Web服务器上的程序。与静态的Web应用不一样,当客户端请求一个CGI程序时,CGI会执行一个程序,而且执行结束后根据执行的结果将适当的信息反馈给客户机。准确地说,CGI不是一种编程语言,而是一种接口,它定义了一种Web服务器与其余编程语言的接口。CGI程序能够用不少语言编写,好比:C++、Perl等。不过,在建立动态的Web页面时CGI也存在一些安全方面的问题。这是由于采用CGI将容许别人在你的系统上执行程序,大多数状况下这可能没有问题,可是别有用心的用户则极可能会利用这一点让系统运行你原本不想运行的程序。

CGI的访问模式如图2.1所示:

图2.1  CGI访问模式示意图

CGI应用是一个独立的模块,它接受来自Web服务器的请求,对收到的数据进行处理,而后把处理结果返回给服务器,这种结果一般是HTML文档,最后服务器把返回的处理结果返回给浏览器。

尽管CGI将Web应用从静态带进了动态,但它不只存在上面提到的安全问题,并且在工做方式上也有许多有待改进的地方。根据上面对CGI工做模式的介绍能够发现:

每当收到CGI请求时,Web服务器都须要创建一个新的进程。这将致使响应时间变慢,由于对于一个进程来讲,服务器必须建立新的地址空间并进行初始化。多数服务器的配置只能运行有限数量的进程,用户可能面临进程空间耗尽的问题。若是服务器进程空间达到极限,将没法再处理客户机新的请求。

尽管CGI几乎能够用任何语言实现,但最经常使用的与平台无关的语言是Perl。Perl在正文处理方面是很是强的,但对于每一个请求,它都要求服务器启动新的解释程序,这将占用较长的时间才能开始编译代码直至耗尽量的进程和资源。

CGI在服务器上彻底是以独立进程的方式运行的,若是客户机向CGI程序提交一个请求,在Web服务器响应以前该CGI程序又中止了,此时浏览器没有办法知道可以发生什么状况,它只能在那里等待直至超时出现。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet技术

 

在Java问世一年之后,Sun引入了Servlet。Servlet是CGI的替代品,也是Java进军Web开发领域的第一款技术。Servlet彻底基于Java实现,提供了对整个Java应用编程接口(API)的彻底访问和一个用于处理Web应用的完备的库。一个Servlet是运行于Servlet容器中的Java对象,与CGI不一样的是,Servlet为每一个请求启动一个单独的线程进行响应,从而大大的节约了空间和时间,处理过程如图2.2所示:

图2.2  Servlet访问模式示意图

Servlet与CGI相比具备以下优点。

有效性:Servlet的初始化代码仅在Web服务器第一次加载时执行一次。一旦加载了Servlet,在处理新的请求时,只需调用一个新的服务方法;与处理每一个请求都要所有加载一个完整的可执行程序相比,这是一种至关有效的技术。

稳定性:Servlet可以维护每一个请求的状态,一旦加载了Servlet,它即驻留在内存中,对收到的请求提供服务。

可移植性:Servlet是用Java开发的,于是它是可移植的,继承了Java“一次编写,处处运行”的优点。

健壮性:因为Java提供了定义完善的异常处理层次以供错误处理,故Servlet较为健壮。它还有垃圾收集器,可用于防止内存溢出等问题。

可扩充性:Servlet可以经过继承现有对象开发新的对象,从而简化了新的Servlet对象的开发过程。

Servlet的运行须要Servlet容器做为环境,Servlet只是一种特殊的Java对象,将Servlet部署到Servlet容器中后,Servlet容器负责在适当的时候建立Servlet、调用Servlet对象和销毁Servlet。每一个Servlet对象都定义了三个方法,分别用于在被建立、被调用和被销毁时执行。每一个Servlet在被部署到Servlet容器中时都配置了URL映射模式,若是到服务器的某个请求的URL与某个Servlet的映射模式相匹配,则该请求就会被分发到该Servlet。Servlet的核心方法是一个service()方法,当有请求被分发到该Servlet时service()方法就会被执行。

在Servlet执行期间,有关请求和Web应用的属性等在处理中可能会用到的信息均可以经过Servlet提供的API得到,例如请求的URL、请求消息的头域信息和Web应用的上下文路径等。同时,对该请求的响应消息的有关内容和属性也能够经过Servlet的API在Servlet中进行设置,例如响应的编码方式、响应消息的头域信息等。

从Servlet的功能和设计的角度讲,Servlet不只仅能够用来处理HTTP请求和响应,它还能够用来处理其余协议的请求和响应消息。但不能否认Servlet发挥做用最大的领域仍是在Web应用中处理HTTP的请求和响应。

 
 
 
 

 

 

 

 

JSP技术

 

JSP的全称是JavaServer Pages,它是基于Java的动态页面技术,它可用于建立跨平台和跨Web服务器的动态网页。JSP是除Servlet以外的又一个Java Web开发的关键技术。

JSP也须要运行于JSP容器中,可是与Servlet不一样的是,

JSP与HTML同样是JSP以单独的文件形式存在的。

JSP文件的内容很是相似于一个HTML文件,它在HTML文件中经过特殊的标签将Java代码添加到其中。

JSP文件直接存在于Web应用的Web目录中,客户端的请求URL直接指向该JSP文件,当JSP容器发现客户端正在请求某个JSP文件时它就对该JSP文件进行解析,运行其中的Java代码,并将执行完后生成的HTML内容返回给客户端。一个简单的JSP文件内容以下:

示例2.1

<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>

<%@ page import="java.util.Date;"%>

<HTML>

<HEAD>

<TITLE>提示</TITLE>

</HEAD>

<BODY>

如今时间是:<%   = new Date().toString()    %>  !

</BODY>

</HTML>

在有客户端请求该JSP文件,该文件经过Java代码获取当前的系统时间,将该时间放在HTML文件的适当位置,并将生成的HTML文件返回给客户端,因此客户端得到的页面会包含当前的时间,如图2.3所示。

图2.3  访问JSP文件示例

JSP与Servlet同样,能够根据客户端的请求提供动态的响应内容,并且JSP也能够访问到有关请求、Web应用等相关的信息,以及设置响应消息的相关内容。

不只如此,JSP在返回HTML做为响应内容时要比Servlet更方便。假如,服务器要向客户端返回以下的HTML页面做为提示信息:

<HTML>

<HEAD>

<TITLE>提示</TITLE>

</HEAD>

<BODY>

您请求的页面出现错误!

</BODY>

</HTML>

因为Servlet是纯Java对象,Servlet的内容也只能严格按照Java的语法书写,因此在输出HTML文档时,Servlet必须使用输出流的print()方法和println()方法将HTML文档的内容输出到响应消息中,以下所示:

pw.println("<HTML>");

pw.println("<HEAD>");

pw.println("<TITLE>提示</TITLE>");

pw.println("</HEAD>");

pw.println("<BODY>");

pw.println("<H1>页内标题</H1>");

pw.println("</BODY>");

pw.println("</HTML>");

而若是使用JSP,那么只须要将HTML文件的内容直接做为到JSP文件的内容就能够了,JSP文件的内容以下:

<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>

<HTML>

<HEAD>

<TITLE>提示</TITLE>

</HEAD>

<BODY>

您请求的页面出现错误!

</BODY>

</HTML>

JSP文件也能处理动态内容,并且在向客户反馈HTML文档时很是方便;但因为JSP把Java代码和HTML内容放在同一个文件中,假如其中用于内容处理的Java代码过多的话,那么JSP文件的内容就会过于庞杂,格式也会比较混乱,不利于开发和维护。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Struts技术

 

既然JSP文件具备页面展示方面的优点,那就让JSP只负责展示方面的工做,而将Servlet负责控制流程,再实现Java对象或JavaBean以负责数据的建模和持久化,这即是Struts技术的核心思想。

Struts技术的架构采用了著名的MVC模式。MVC是Model-View-Controller的简称,即把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样一个应用被分红三个层——业务逻辑层、表示层、控制层。

视图(View)属于表示层,它表明与用户交互的界面,在基于HTTP协议的开发技术中视图层都是基于HTML的技术。MVC中的视图仅限于向用户展示模型中的数据和接收用户的交互信息。视图不具有任何与业务模型或业务流程相关的知识,只须要负责展示得到的数据和将接收到的用户交互信息提交给控制器。 

模型(Model)属于业务逻辑层,它用于实现具体的业务逻辑、状态管理的功能。模型包括业务模型和数据模型两种:业务模型负责业务流程/状态的处理和业务规则的制定,数据模型是对对象的数据持久化。MVC并无提供模型的设计方法,而只是规定应该组织管理这些模型,以便于模型的重构和复用。

控制器(Controller)属于控制层,接收来自视图的用户请求,将请求转换为数据模型的命令传递给模型。控制器就是一个分发器,根据用户请求选择模型和视图。控制层并不作任何的数据处理。 

模型、视图与控制器的分离,使得一个模型能够对应多个视图。若是用户经过某个视图的控制器改变了模型的数据,全部其余依赖于这些数据的视图都会反映这些变化。所以,不管什么时候发生了何种数据变化,不管控制器选择任何视图,视图都会从模型得到最新的更新。模型、视图、控制器三者之间的关系和各自的主要功能,如图2.4所示。

图2.4  模型-视图-控制器交互示意图

Struts是一个基于Sun J2EE平台的MVC框架,主要是采用Servlet和JSP技术实现的。它是Apache软件基金会旗下Jakarta项目组的一部分,其官方网站是http://struts.apache.org/。因为Struts能充分知足应用开发的需求,简单易用,敏捷迅速,故在Web开发中颇受关注。Struts把Servlet、JSP、自定义标签和消息资源(message resources)整合到一个统一的框架中,开发人员利用其进行开发时不用再本身编码实现全套MVC模式,极大地节省了时间。

Struts开发了一套MVC框架,程序员在使用Struts开发Web应用时根据具体应用的需求实现不一样的模型、视图和控制器,而后经过一些配置文件将这些内容装载到Struts框架中。因此Struts主要包含以下四个部分。

1.模型(Model):Struts使用定义的Action类及程序员经过继承Action实现的子类完成模型的工做。程序员在Action的子类中实现业务逻辑和操做数据模型。

2.视图(View):视图由JSP文件实现。除了JSP定义的内容外,Struts还提供了一整套JSP定制标签库,利用它们能够快速创建应用系统的界面。

3.控制器(Controller):本质上是一个Servlet,根据程序员定义的请求映射关系将客户端请求转发到相应的Action类。

4.配置文件及其解析工具包:Struts经过许多XML文件和properties文件对应用系统进行配置,其中包括定义请求映射关系的struts-config.xml文件,还有描述国际化应用中用户提示信息的配置文件等。

虽然Struts为Java Web开发提供了一种崭新的方式,可是随着Struts的不断发展和新技术的不断出现,Struts也暴露出了一些问题。通过将Struts与另外一个著名的项目WebWork相结合,产生了Struts2。

Struts2的体系与Struts体系的差异很是大,由于Struts2使用了WebWork的设计核心,而不是Struts的设计核心。Struts2中大量使用拦截器(技术上采用Servlet Filter)来处理用户的请求,其体系结构图如图2.5所示。

图2.5  Struts2体系结构图

Struts2的处理流程大体以下:

(1)浏览器发送一个请求;

(2)核心控制器FilterDispatcher根据请求调用合适的Action;

(3)拦截器链自动对请求应用通用功能,如验证等;

(4)调用Action的execute方法,该execute方法根据请求的参数来执行必定的操做;

(5)Action的execute方法处理的结果将被输出到浏览器中,支持多种形式的视图。

Struts和Struts2在Java Web开发领域中取得了很是大的成功,如今有许多Java Web开发团队在使用这两种技术开发Web系统。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Java Web开发工具

 

“工欲善其事,必先利其器”。对于程序员来讲,好的开发工具能够极大地提升开发效率。并且,基于Servlet和JSP技术的Web系统也必须在对Servlet和JSP技术提供支持的Web服务器中进行开发。因此,学习经常使用的Java Web开发工具是学习Java Web开发技术的必修课。

Tomcat是最流行的开源Servlet/JSP容器,是最适合于初学者使用的基于Java技术的Web服务器;Eclipse是最流行的开源集成开发环境,也是基于Java技术实现的。Tomcat和Eclipse的组合是进行Java Web开发最有效的工具集。

 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Tomcat

 

在前面介绍Servlet和JSP技术时都提到了,Servlet须要在Servlet容器中运行,而JSP也须要在JSP容器中运行。并且,若是须要Servlet和JSP能应用到Web应用中,还必须将JSP和Servlet部署到Web服务器上。传统的Web服务器(例如Apache)并不能对JSP和Servlet提供支持,因此将JSP技术、Servlet技术以及基于这两种技术实现的其余Java Web技术(例如Struts技术)应用于实际Web应用中的方法只有以下两种:

(1)实现可以支持JSP和Servlet的Web服务器;

(2)实现JSP和Servlet容器并将其与Web服务器相结合。

Tomcat正是Apache基金会针对JSP和Servlet标准实现的标准的JSP/Servlet容器,并且以上提到的两种方式它都支持。Tomcat是Apache基金会Jakarta项目中的一个核心项目,是一个免费的开源项目。Tomcat由Apache,Sun和其余一些公司及我的共同开发而成,因为有Sun的参与和支持,因此最新的Servlet和JSP规范总能在Tomcat中获得体现。

Tomcat不只仅是一个Servlet容器,它也具备传统的Web服务器的功能:处理HTML页面。与Apache相比,它处理静态HTML的能力不如Apache。但Tomcat提供了一种与Apache集成的途径,经过与Apache集成,可让Apache处理静态Web内容,而让Tomcat处理JSP和Servlet。这种集成只须要在Apache和Tomcat中进行简单配置便可。 

Tomcat中的应用程序是一个Web应用目录或一个WAR(Web Archive)文件。WAR是Sun提出的一种Web应用程序格式,它与JAR相似,也是一个压缩包,压缩包的内部结构符合一个Web应用的目录结构。一般,Web应用的根目录下除了包含诸如HTML和JSP等Web对象文件及其目录外,还会包含一个特殊的目录WEB-INF;在WEB-INF目录下一般有一个web.xml文件、一个lib目录和一个classes目录,web.xml是这个应用的配置文件、lib目录包含一些库文件、classes目录则包含已编译好的class文件,库文件和class文件中在应用中须要使用的Servlet类和JSP/Servlet所依赖的其余类(如JavaBean)。

Tomcat提供了不少种将Web应用部署到其中的途径,其中最简单的能够直接将WAR文件或者Web应用根目录复制到Tomcat的webapp目录下,Tomcat就会自动检测该Web应用,对WAR文件解压,并将Web应用部署到Tomcat中。 

Tomcat经过“服务→虚拟主机→应用”的层次将提供的全部功能分红多个层次和级别进行组织和管理。一个Tomcat服务器能够部署多个服务,每一个服务能够配置多个虚拟主机,每一个虚拟主机能够部署多个应用。这样的结构便于将一个服务器的各类功能进行分类和分层管理。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse

 

Eclipse是一个开放源代码的、基于Java开发的可扩展插件式开发平台。Eclipse自己并不提供任何可被直接使用的功能,它只是一个框架和一组服务,用于经过插件组件构建开发环境。可是,Eclipse框架提供了一个完善的插件结构,它提供给其余开发人员充分的发挥空间,开发人员能够开发出任何符合Eclipse插件结构的插件,这个插件能够是用于完成任何功能的插件。并且向Eclipse中添加插件的方式也很是方便。将插件添加到Eclipse中后,插件能够与Eclipse彻底结合成为一个完整的开发工具。如今,在开源网站上能够找到的比较成熟和流行的各类Eclipse插件不少,例如:用于开发C/C++应用的C/C++开发插件(C/C++ Development Tooling,CDT);用于开发Perl应用的Perl开发插件(Eclipse Perl Integration,EPIC)等。

除此以外,Eclipse在其发布时还附带了一个标准的插件集,其中包括用于开发Java应用的插件集(Java Development Tooling,JDT)和用于开发Eclipse插件的插件集(Plug-in Development Tooling,PDT)。JDT提供了对开发Java工程的强大支持,包括:工程和Java类新建向导、Java工程管理、编辑Java文件时的内容帮助和智能感应、对Java类的重构支持,等等。PDT提供了对开发Eclipse插件的支持,经过PDT程序员能够在Eclipse中开发插件工程,而且其中也提供了丰富的插件开发支持功能。

Eclipse灵活的插件结构为Eclipse的发展奠基了基础,也吸引了大量的开发人员和企业参加到Eclipse插件的开发行列。甚至,不少公司直接对Eclipse开发框架进行改造,大量加入本身的插件,将Eclipse变成另一套新的开发环境。

Eclipse的Web开发工具插件集对Eclipse在开发Web工程方面提供了很是大的扩充,使得Eclipse能够做为一个功能完善的Web应用开发环境。在安装了Web开发工具插件集后,Eclipse在原来的基础上又提供了对如下几个方面的支持。

Web对象和J2EE对象建立向导。

开发Web对象和J2EE对象的工具,包括:开发HTML,CSS,JSP,Web服务,JavaScript,XML等对象的支持。

提供一个通用服务器的扩展点用于将通用服务器添加到Eclipse中。

提供运行/调试Web应用的工具。

 
 
 
 

第 2 章:Java Web开发简介做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

本章从CGI技术讲起,分别介绍了Java Web开发中最基础的Servlet技术和JSP技术以及Java Web开发中最流行的Struts技术。Servlet是运行于Servlet容器中Java对象,它为每一个请求启动一个单独的线程进行响应,Servlet与传统的CGI技术相比,在有效性、稳定性、可移植性、健壮性和可扩充性方面具备优点。Servlet虽然在处理请求方面具备很大的优点,可是在向客户端反馈HTML响应页面方面却略显不足,JSP提供了内嵌于HTML页面的结构语法,将动态处理请求的能力与反馈响应页面的灵活性结合了起来;但若是将复杂的处理过程所有放到JSP页面中,就会使JSP页面显得很是冗长并且难于维护。Struts技术以MVC模式为基础,结合Servlet强大的处理HTTP请求和响应的能力以及JSP强大的页面展示能力,构建了一种Java Web开发的MVC框架,在Java Web开发领域获得了普遍的应用。

Tomcat是应用最广泛的Servlet/JSP容器,它既能够做为单独的Web服务器也能够与其余传统的Web服务器结合使用以提供处理Servlet和JSP的能力。Eclipse是基于全插件结构的通用集成开发环境,经过向Eclipse中添加不一样的插件能够使Eclipse具有开发不一样类型系统的能力;另外,Eclipse的Web开发插件集还为Eclipse增长了开发Web项目和J2EE项目的能力。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse简介

 

选择一个好的集成开发环境会极大地提升系统开发的效率。Eclipse是一款基于Java开发的可扩展插件式开发平台。Eclipse以其优秀的架构和附带的Java开发插件已经成为Java开发领域最流行的集成开发环境;并且Eclipse是开源代码,能够免费得到。Eclipse的Web开发插件集又在Web开发和J2EE开发方面为Eclipse提供了很是大的扩充,使得Eclipse足以成为功能完备的Java Web开发环境。

本章将首先对Eclipse进行简单介绍,介绍Eclipse的发展和架构;而后介绍包含Web开发插件集的Eclipse(WTP - Eclipse)的功能及如何下载和安装。

Eclipse是一个开放源代码的、基于Java开发的可扩展插件式开发平台。Eclipse自己只是一个框架和一组服务,用于经过插件组件构建开发环境。并且,Eclipse自身附带了一个标准的插件集,其中包括用于开发Java应用的开发工具(Java Development Tooling,JDT)。

大多数用户只是将Eclipse看成Java集成开发环境(Integrated Development Environment,IDE) 来使用,但实际上Eclipse的功能远不止于此。Eclipse还支持经过安装C/C++开发插件(C/C++ Development Tooling,CDT)开发C/C++应用、经过安装Perl开发插件(Eclipse Perl Integration,EPIC)开发Perl应用、……并且它们都是免费的。不只如此,Eclipse和JDT同样,自身还附带了一个插件开发环境(Plug-in Development Environment,PDE),利用这个组件程序员能够本身开发任何与Eclipse无缝集成的插件,可让Eclipse开发环境作任何本身但愿的事情。甚至,不少公司直接对Eclipse开发框架进行改造,加入本身的插件,使Eclipse变成另一套新的开发环境,例如,Adobe的Flex Builder。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse的发展

 

20世纪90年代中期,几大商业开发环境之间都进行过激烈的竞争,其中也包括基于Java的集成开发环境。而当时IBM公司的开发工具Visual Age for Java却面临着一个问题,由于它和IBM的另一个集成开发环境WebSphere Studio 很难集成到一块儿,并且底层的技术比较脆弱,很难进一步发展,没法知足业界应用开发的需求。所以,在1998 年,IBM成立了一个项目开发小组开始探索下一代开发工具技术,而且在2000年他们决定给新一代开发工具项目命名为Eclipse。

Eclipse项目的开发人员意识到:Eclipse要吸引开发人员、发展起一个强大而又充满活力的商业合做伙伴社区而且吸引大量活跃的第三方系统这一点很是重要,而采用开放源码的受权和运做模式则是一个很是有效的途径。因而,2001年12月,IBM将价值4千万美圆的Eclipse源码捐赠给开源社区;同时联合八家公司成立了Eclipse协会(Eclipse Consortium),每一个公司都做为协会的成员公司,主要任务是支持并促进 Eclipse 开源项目。但到2003年,IBM又意识到这种会员模式很难进一步扩展,有些事务操做起来很困难,主要是由于Eclipse协会不是一个法律上的实体;此外有些业界成员不肯加入,由于他们认为Eclipse的真正领导者仍是IBM。所以IBM决定建立一个独立于IBM的Eclipse,因而 IBM 与其余成员公司合做起草了管理条例,准备成立Eclipse基金会(Eclipse Foundation)。2004年初,Eclipse基金会正式成立。Eclipse基金会由若干会员组成,会员大可能是业界的大公司或者学校、科研机构等。Eclipse的开发和维护都由Eclipse基金会负责,从此发展方向也由Eclipse基金会的会员共同决定。Eclipse基金会共有四种类型的会员:Associate Members、Add-in Provider Members、Strategic Members、Committer Members。其中Strategic Members对于Eclipse的发展起着最重要的做用,他们为Eclipse基金会提供开发人员和开发资金,同时也决定Eclipse的发展方向和开发计划。截至做者写做本书,Eclipse的Strategic Members有20个,包括:BEA、Borland、IBM、Intel、Motorola、Nokia、ORACLE、SAP、Sybase等。

读者可能会感到不解,Eclipse既然是开源软件,任何公司都没法从Eclipse中得到利润,IBM为何会愿意将花费如此大成本的软件捐赠出来做为开源项目呢?其余这些公司又为何会愿意在Eclipse项目上花费人力和资金呢?这是由于,随着Eclipse的开源,愈来愈多的公司加入到Eclipse基金会、愈来愈多的开发人员转向使用Eclipse,IBM和Eclipse基金会的会员公司能够开发出大量基于Eclipse的插件,Eclipse是免费的,可是这些插件是收费的,会员公司能够经过出售插件来赚取利润,因此Eclipse 的全部成员公司大部分都是商业软件提供商。

最初,Eclipse并无在开发人员中被普遍地使用,直到2003年3月Eclipse 2.1发布后才马上引发了轰动,下载的人蜂拥而至。后来在Eclipse基金会的领导下,Eclipse 3.x相继发布,Eclipse也真正成为了一个成熟、优秀的集成开发环境。愈来愈多的成员加入Eclipse协会和愈来愈多的第三方插件的发布使得Eclipse的发展愈来愈快。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse的下载与安装

 

用于开发普通的Java应用程序,读者能够在Eclipse的官方网站http://www.eclipse.org上下载到Eclipse的最新发布。可是,考虑到本书的读者须要使用Eclipse开发Web应用,因此本书将直接介绍集成了Web开发插件集(Web Tools Platform,WTP)的Eclipse的下载和安装,可将其称为WTP-Eclipse。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse的下载与安装

 

用于开发普通的Java应用程序,读者能够在Eclipse的官方网站http://www.eclipse.org上下载到Eclipse的最新发布。可是,考虑到本书的读者须要使用Eclipse开发Web应用,因此本书将直接介绍集成了Web开发插件集(Web Tools Platform,WTP)的Eclipse的下载和安装,可将其称为WTP-Eclipse。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

WTP简介

 

WTP是Eclipse基金会组织的一个Eclipse一级项目,它起始于IBM Rational WebSphere项目和ObjectWeb Lomboz项目的贡献,主要目的是开发一个Eclipse插件,提供一个丰富并且集成良好的工具集合用于简化复杂Web应用和J2EE应用的开发。WTP工程向其使用者声明了三个主要关注点:

性能:WTP将是很是精简的,它将在不影响任何功能的状况下最小化内存的使用。

易用性:WTP将很是易于使用,须要不多的预备知识,可以为全部开发人员建立完善的应用提供支持。

质量:WTP将是商业级产品,它的API将达到平台级质量。

WTP 包含两个子项目:Web标准工具(Web Standard Tools,WST)和J2EE标准工具(J2EE Standard Tools,JST)。WST工程的目的是为任何基于Eclipse的开发环境提供开发Web应用的公共基础设施。JST工程则是提供对开发J2EE技术相关应用(例如JSP和EJB)的支持。

WTP对Eclipse的扩展包括:

提供建立Web对象和J2EE对象的向导:提供了建立Web工程、J2EE工程以及各类Web对象和J2EE对象的向导。支持的工程有静态/动态Web工程、EMF(Eclipse Modeling Framework)工程、EJB(Enterprise Java Bean)工程、各类J2EE工程、JPA(Java Persistence API)工程等;支持的对象有:HTML、CSS、JavaScript、JSP、Servlet、XML、SQL文件、Web服务对象、EJB对象、多种EMF Model等。

提供开发Web对象和J2EE对象的工具:提供开发Web表现层、业务层和数据层应用以及开发服务器端发布程序的工具,包括为标准语言(例如,HTML,CSS,JSP,Web服务,JavaScript,XML等)提供的编辑器、代码验证器和文档生成器。

提供服务器工具:提供一个通用服务器的扩展点用于将通用服务器添加到工做空间中,以及启动和中止服务器。它用一些服务器扩展了Eclipse平台,将这些服务器做为首选执行环境,包括Web服务器、J2EE服务器以及数据库服务器。

提供运行/调试Web应用的工具:支持在目标服务器环境下发布、运行、启动和中止Web应用代码。

提供基本的数据工具:具备浏览数据库和对数据库执行SQL查询的能力。

还包括一个TCP/IP监视服务器用于调试HTTP消息通讯,尤为是由Web服务生成的SOAP消息。

这个工程的最终目标就是提供高度可复用和可扩展的工具,便于开发者建立可持续提高性能的应用。

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

WTP-Eclipse的下载与安装

 

WTP是Eclipse的一级子项目,它以Eclipse插件的形式出现。所谓WTP-Eclipse是指已经安装了WTP插件的Eclipse,除了增长WTP扩展的相关功能外,其余与普通发布的Eclipse是同样的。在不至于产生混淆的状况下,下面提到的Eclipse都指安装了WTP插件的Eclipse,即WTP-Eclipse。在Eclipse的官方网站中有WTP的主页,连接地址是:http://www.eclipse.org/webtools,页面如图3.2所示:

图3.2  WTP官方主页

在做者写做本书时,WTP最新的Release版本是2.0.1,因此本书就以WTP 2.0.1为参考版本进行介绍。单击WTP 2.0.1进入WTP 2.0.1的主页面,如图3.3所示。

图3.3  WTP 2.0.1主页

单击页面中部的“Download”连接进入下载页面,如图3.4所示。

图3.4  WTP下载页面

如图3.4所示,顶部的“Release Build: R-2.0.1-20070926042742”是该下载版本的版本号,下面是日期,从图中能够发现WTP 2.0.1是2007年9月26日发布的。

在下载页面中分了好几栏,第一栏“Required Prerequisites”是指安装WTP插件的前提,即在安装WTP前须要提早安装的环境和插件;第二栏“Web Tools Platform All-In-One Packages”是指将全部内容整合成一个包的下载方式,即包括Eclipse平台、各类前提插件和WTP插件,这就是本节前面提到的WTP-Eclipse。这里提供了针对三种不一样操做系统的下载连接,对于Windows用户就单击第一种平台后面的下载连接。单击下载连接后会进入镜像选择页面,如图3.5所示。

图3.5  镜像选择页面

本页面列出了全部能够下载的镜像站点,同时页面也自动选择了一个最优下载连接,如图中黑框所示,一般选择该镜像就能够了。单击该连接直接下载,下载得到的文件是一个zip文件,文件名开始是“wtp-all-in-one-sdk”,接着是版本号,最后是应用平台,例如:wtp-all-in-one-sdk-R-2.0.1-20070926042742- win32.zip。

将下载的zip文件解压到本地文件系统,根目录的内容如图3.6所示。

图3.6  Eclipse根目录

双击eclipse.exe既能够打开Eclipse开发环境。

【注意】

运行Eclipse时系统中必须已经正确安装并配置了JDK,假如系统中的JDK没有正确安装和配置,Eclipse在启动时会弹出错误对话框。

Eclipse正确启动后会弹出欢迎菜单,如图3.7所示。

图3.7  Eclipse欢迎界面

欢迎界面在第一次启动Eclipse时自动出现,之后再启动时不会自动出现,可是读者能够经过Eclipse菜单Help → Welcome打开。

欢迎界面上提供了一些图标使用户能够方便地开始学习Eclipse,图标从左向右依次为:

&#61548;Overview: Get an overview of the features.

对Eclipse开发环境的简单介绍。分别提供了对Eclipse基本概念、团队开发支持、Java开发、插件开发的介绍。每部分都会连接到Eclipse帮助。读者能够在这里学习Eclipse的基本概念和基础知识,做为Eclipse开发的起步。

&#61548;What’s new: Find out what is new.

这里介绍了Eclipse当前版本的新特性,包括Eclipse平台的新特性、Java开发工具的新特性和插件开发的新特性;介绍了如何将之前旧版本的Eclipse工程代码迁移到新版本的开发环境下工做;还提供了在线更新和加入Eclipse社区的连接。

&#61548;Samples: Try out the samples.

提供了一些使用Eclipse的样例,包括使用Workbench、Java开发工具的样例以及一些SWT的样例。不过,这些样例都须要在线下载。

&#61548;Tutorials: Get through tutorials.

这里提供了大量的向导,指导用户开始使用Eclipse的各个模块,包括:建立Java Hello World应用、建立SWT Hello World应用、建立一个Eclipse插件、建立RCP(Rich Client Platform)应用、以及如何利用CVS进行团队开发,等等。

&#61548;Workbench: Go to the Workbench.

关闭欢迎页面进入开发界面。

Eclipse的开发界面如图3.8所示。

图3.8  Eclipse的开发界面

 
 
 
 

第 3 章:Eclipse基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

本章对Eclipse进行了简单的介绍,包括Eclipse的历史和Eclipse的架构,而且引导读者逐步下载和安装WTP-Eclipse。

最先Eclipse是IBM的一个内部项目,为了Eclipse的发展,2001年IBM将Eclipse捐赠给开源社区,而且联合业内的公司组成了Eclipse基金会。Eclipse基金会负责Eclipse的开发和维护工做。Eclipse是全插件结构,除了运行时环境外其余所有以插件的形式加入Eclipse开发环境中,Eclipse自带的插件有工做空间、工做台、CVS、JDT、PDT等。

WTP是Eclipse的一级子工程,它提供了Eclipse支持开发Web应用和J2EE应用的能力。WTP-Eclipse是安装了WTP插件的Eclipse,它能够从Eclipse官方网站的WTP主页上下载。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse界面

 

本章将简单介绍Eclipse集成开发环境,包括Eclipse的界面、Eclipse的经常使用配置以及Eclipse插件。在这一章读者将了解到:

什么是视图和透视图,它们的区别是什么;

Eclipse都有哪些菜单以及每一个菜单项的做用;

如何查看和设置Eclipse中的快捷键;

如何配置Eclipse的经常使用功能,如Clean up、代码模板、代码格式化;

如何配置Eclipse的Web开发插件集;

安装Eclipse插件的经常使用方式。

Eclipse做为一种集成开发环境,它可让程序员在它的开发环境中完成基本上全部程序开发的工做,熟练地掌握Eclipse开发界面的组织能够提升程序开发的效率。同其余大多数集成开发环境同样,Eclipse的界面也是一个总体窗口式开发界面。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

视图和透视图

 

在开发Java应用时典型的界面布局图如图4.1所示。

图4.1为编辑Java程序时的Eclipse界面,也是Java程序员最经常使用的界面布局方式。从该图中能够发现,该界面分为七个部分:菜单栏、快捷图标栏、透视图选择栏、浏览区、文件编辑区、提纲显示区和辅助显示区。

与其余集成开发环境不一样的是Eclipse的界面并非一成不变的,它提供了灵活组织界面布置的方式。Eclipse中每个用于显示特定内容的窗口称为视图(View),如图4.1所示,浏览区有Package Explorer视图、Hierarchy视图,辅助显示区有Problems视图、Javadoc视图和Declaration视图,提纲显示区有Outline视图。Eclipse提供了许多种视图,并且不一样的插件还能够定义不一样的视图添加到Eclipse中。除此以外,Eclipse还能够经过定义不一样的透视图(Perspective)对视图按不一样的布局进行组织以适应不一样的开发场景。不一样的透视图能够定义不一样的窗口布局模式以及在各窗口中显示不一样的视图。图4.1中所示的界面风格是Eclipse自带的Java透视图。其中每一个视图的位置是Eclipse默认的位置,其中浏览器、提纲显示区和辅助显示区中的视图能够在这几个区之间被随意拖动。

图4.1  Eclipse界面布局图

在本书所介绍的WTP-Eclipse中总共提供了几十种不一样的视图,它们均可以经过Window → Show View菜单打开,如图4.2所示。

图4.2  视图选择图示

如图4.2所示,子菜单中列出了一些经常使用的视图。单击Other...菜单项能够打开当前Eclipse支持的全部视图,它们被分类组织,如图4.3所示。


图4.3  选择视图对话框

不一样的视图具备不一样的格式,用于显示不一样的内容。经常使用的视图有如下七种。

(1)Package Explorer:包浏览器视图,该视图以树状结构显示当前全部已打开工程的包内容,每一个工程做为一个树的根节点,工程节点下面按照工程中的目录结构显示工程中的包、源代码、引入的库等资源。须要注意的是这个视图中不显示工程中编译输出的文件。

(2)Navigator:导航视图,该视图与Package Explorer相似,也是以树状结构显示工程中的内容,可是该视图显示的是工程目录中的原始文件目录结构,不区分文件是不是编译输出的,都显示。

(3)Outline:文件大纲视图,该视图显示当前编辑文件的内容大纲。典型地,当编辑Java代码时,显示当前Java文件中的类以及类中定义的属性域和方法域;当编辑HTML文件时,显示当前HTML文件中的标签层次结构。

(4)Problems:问题视图,该视图显示当前工做空间中打开的工程中全部的编译错误和警告。

(5)Console:工做台视图,该视图是程序运行时的标准输入/输出窗口。

(6)Ant:Ant视图,该视图对工做空间中的Ant构建脚本进行管理和运行。

(7)Search:搜索视图,该视图在调用搜索功能时会自动打开,它用于显示搜索的结果。

透视图定义了在Eclipse界面上对视图的不一样组织方式,程序员能够经过Window → Open Perspective菜单项选择不一样的透视图,如图4.4的a)图所示:

Open Perspective菜单项的子菜单项提供了三种最经常使用的透视图,单击Other...子菜单项能够打开当前Eclipse支持的全部透视图,如图4.4的b)图所示。

a)选择透视图菜单      

  

b)选择透视图对话框

图4.4  透视图切换

每一种透视图定义了一套视图的显示和组织模式,不一样的透视图用于不一样的开发情景。例如:Java透视图用于编辑Java代码、Debug透视图用于调试程序、Java Browsing透视图用于浏览Java代码、Plug-in Development透视图用于开发插件应用、Java EE透视图用于开发J2EE应用、JPA Development透视图用于开发JPA应用等。前面图示的Eclipse界面是Java透视图下的Eclipse界面,如图4.5所示为Debug透视图下的Eclipse界面。

图4.5  Debug透视图

除此以外,Eclipse还支持用户自定制的Perspective,用户能够经过Window的子菜单Customize Perspective...和Save Perspective As...本身定制和保存透视图,而且这些自定义透视图也能够经过Open Perspective菜单打开。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

菜单

 

Eclipse 3.3.1的菜单栏有十个一级菜单项:File、Edit、Source、Refactor、Navigate、Search、Project、Run、Window和Help。

1.File菜单

File菜单如图4.6所示。

New菜单项用于新建对象,包括文件对象或者概念对象。例如,Project…用于新建任何一种Eclipse支持的工程,包括Java工程、Web工程、J2EE工程等;Package用于新建一个Java包;Class用于新建一个Java类,同时新建与类名同名的文件。在这里没有列出的对象能够经过单击Other…菜单项打开选择框。Open File…菜单项用于打开本地文件系统中的某个文件。

图4.6  File菜单

Close菜单项关闭当前编辑文件;Close All菜单项关闭打开的全部文件。

Save菜单项用于保存当前编辑文件;Save As…会打开一个文件选择窗口,用于将当前编辑文件保存为另外一个文件;Save All菜单项用于保存打开的全部文件;Revert菜单项用于取消当前编辑文件的全部最新编辑内容,将其内容恢复到上一个保存点。

Move…和Rename…菜单项都只有当浏览区中某个文件对象(包括文件、目录和Java包等)被选中时才能使用,Move…是移动文件对象,即将该文件对象移动到另外一个目录或Java包中;Rename…是重命名文件对象;这里须要说明的是,当执行Move…和Rename…操做的是Java对象时,Eclipse会自动更改工程中对该对象的相关应用,使完成操做后工程中不会出现引用错误;Refresh菜单项刷新当前选中对象及其包含的对象,使之与文件系统中的实际内容保持一致,并且当Java对象被刷新时Eclipse会从新对该对象进行编译,一般当工程中的文件在Eclipse外被更新后就须要使用该菜单刷新被更新的对象。Convert Line Delimiters to…菜单项用于更改在编辑文件时使用的换行符,能够将换行符保持与Windows风格、Linux风格或MacOS 9风格一致,默认是Windows风格。

Print…用于打印当前编辑文件。

Switch Workspace…用于切换工做空间。Eclipse用工做空间来管理工程集,一个工做空间中能够包含许多工程,一个工做空间在文件系统中是一个独立的文件夹。浏览区展现的即为当前工做空间中的全部工程。

Inport…用于将不一样形式和不一样位置的文件内容导入到工做空间中,最经常使用的有导入已存在的Eclipse工程、导入本地文件系统中的文件到工程中、从CVS导入工程、从Jar文件导入、从War文件导入,等等。Export…用于将工做空间中的内容导出,最经常使用的有导出为Jar文件、导出为War文件、导出Java文档等。

Properties打开当前编辑文件的文件属性,包括:文件位置、文件大小、文件编码等。

Exit则退出Eclipse开发环境。

另外,File菜单还会提供一些最近访问文件的快捷连接。

2.Edit菜单

Edit菜单如图4.7所示。

图4.7  Edit菜单

Edit菜单主要提供了一些与编辑文件内容相关的操做或设置,具体的菜单项以下。

Undo菜单项用于撤销对上一次的操做,一般菜单上会显示上一次操做的动做。Redo菜单项只有在进行了Undo操做后才能使用,表示从新再作一遍上一次的操做。

Cut/Copy/Paste菜单项的功能和一般所了解的意义是一致的,它们分别表示对选中文字进行剪切、复制和粘贴。Copy Qualified Name菜单项表示复制当前选中或光标所在位置的元素的全名,即包括元素的包全路径和所在类的信息,例如在一个包com.csai.web中的HelloWorld类的main(String[] arg)方法中定义了一个对象s,那么当光标在s上单击该菜单项就会将com.csai.web.HelloWorld.main(String[]).s复制到剪切板中。

Delete菜单项用于删除选中的内容或选中的文件对象。Select All用于选中文件中的全部内容或者浏览区的全部文件对象。Expand Selection To提供了一些方便的方法用于扩充当前选择以包含文件中的一些代码元素,好比增长前面或后面的一条语句、扩充选择以包含当前大括号包围的整个代码块,等等。

Find / Replace…菜单项打开一个搜索工具窗口,用于在当前编辑文件中搜索指定文本。Find Next和Find Previous分别是向后和向前搜索当前搜索串。Incremental Find Next或Incremental Find Previous打开增量搜索模式,在打开该模式后键入待查找内容,查找内容就会出如今状态栏,而且Eclipse会在当前编辑文件中增量式查找并标记与键入内容匹配的第一个位置;当选择Incremental Find Next菜单项时进入向后增量查找模式,当选择Incremental Find Previous菜单项时进入向前增量查找模式。

Add Bookmark…菜单项用于在当前光标所在行或当前选中文本的第一行添加一个书签,便于之后定位。Add Task…菜单项用于在当前光标所在行或当前选中文本的第一行添加一个用于定义的任务。

Smart Insert Mode是一个状态菜单项,用于打开和关闭智能插入模式,当智能插入模式关闭后,Eclipse编辑器的一些智能编辑功能(例如智能缩进、括号自动补齐等)就会被禁用。

Show Tooltip Description菜单项显示当前光标位置对象的快捷描述,与鼠标悬停在该位置时出现的提示同样。

Content Assist菜单项提供对当前编辑位置进行内容帮助,即程序员在输入某名称时只须要输入部份内容,而后激活内容帮助,编辑器就会弹出一个候选输入内容的列表供程序员选择;Word Completion菜单项提供对当前编辑字符串的自动补全。Quick Fix菜单项能够提供自动修复当前编辑文件中错误的方法列表供程序员选择;当编辑文件中有错误时,编辑器会在对应位置的代码下标红;当光标在标红位置时使用该菜单项便可得到Eclipse建议的用于解决该问题的一系列方案,程序员在选择了某建议方案后编辑器就会按照选择的方案自动修复该错误。

Set Encoding…菜单项用于在编辑文本内容的文件时,显示和设置当前文本的字符编码。

3.Source菜单

Source菜单如图4.8所示。

Source菜单提供了与源代码编辑相关的操做,包括源代码自动生成、源代码格式整理等,特别是提供了自动生成一些固定用途Java代码段的功能。

Toggle Comment菜单项注释光标所在行或选中的全部行,即在行前加 //。Add Block Comment菜单项表示当一个代码块被选中时变为可用,它用于注释一个代码块,即用/*…*/注释代码块。Remove Block Comment菜单项用于删除一个注释的代码块,是Add Block Comment的逆操做。 Generate Element Comment菜单项用于为光标所在的代码对象添加注释模板,例如方法或类,添加的模板根据代码对象的不一样格式会有不一样。

Shift Right 、Shift Left和Correct Indentation菜单项都用于调整代码的缩进。Shift Right将光标所在行或选中的全部行的缩进向右调整一格,Shift Left则恰好相反。Correct Indentation按照代码的层次结构调整光标所在行或者选中行的缩进格式。Format菜单项根据Code Formatter preference的设置对选中的全部代码进行格式化,若是没有选择任何代码则对整个文件中的代码进行格式化。Format Element对光标所在的最内层一个代码块进行格式化。

图4.8  Source菜单

Add Import菜单项为当前选择的对象添加Import声明;若是代码中有某个使用的类型没有添加Import声明,编辑器会自动在该位置下标红,将光标放在没有添加Import声明的类型上而后使用该菜单就会自动添加该类型的Import声明;若是可能添加的类型不惟一,则Eclipse会弹出一个备选列表供程序员选择。Organize Imports菜单项对Import声明进行从新组织,包括添加须要的Import声明、删除不须要的Import声明、按照Organize Imports preference的设置将全部的Import语句进行排序和规整。 Sort Members菜单项根据Member Sort Order preference的设置对成员进行排序,使成员按规定顺序排列。Clean up菜单项根据Clean Up preference的设置对代码进行清理。

【注意】

Eclipse开发环境的设置界面能够使用菜单Window → Preferences打开,这里能够设置Eclipse编辑器的属性和插件的属性。全部Java相关的设置都是JDT插件的属性,Code Formatter preference、Organize Imports preference和Clean Up preference都在Java设置中的Code Style中进行设置。Member Sort Order preference在Java设置中的Appearance中进行设置。其中Code Formatter preference设置是比较经常使用的设置,它能够定义Java代码的编码风格(好比if语句的左括号是否与if处在同一行、赋值语句的=前是否留空格、每行代码的最大长度等),定义好后在编辑代码时程序员只须要使用该菜单项就能够格式化代码,使代码保持与用户设置的格式一致,使程序员编写的全部代码保持一种风格,并且Java开发团队也能够经过共享代码格式的设置使团队中全部程序员编写的代码保持一致的风格。关于Eclipse中的经常使用设置会在本章后面部分进行介绍。

Override/Implement Methods…菜单项打开Override Method对话框,提供快捷途径以重写父类或父接口中的方法。Generate Getter and Setter…菜单项打开Generate Getters and Setters对话框,提供快捷途径为类的属性成员生成get和set方法。Generate Delegate Methods…菜单项打开Generate Delegate Methods对话框,提供快捷方式为类的方法(包括从父类继承的方法)建立代理方法。Generate hashCode() and equals()…菜单项打开Generate hashCode() and equals()对话框,提供快捷途径为类建立hashCode()方法和equals()方法。Generate Constructor using Fields…菜单项打开Generate Constructor using Fields对话框,提供快捷途径为类建立构造函数,并且生成的构造函数能够包含参数用于初始化指定的类属性成员。Generate Constructor from Superclass…打开Generate Constructor from Superclass对话框,提供快捷途径为类生成与父类构造函数具备相同参数的构造函数,而且在构造函数体内调用父类的构造函数。

Surround With菜单项提供快捷方式为选定的代码块增长包围的语句,例如:用try/catch块包围选定代码块、将代码块放置在if语句中。

Externalize Strings菜单项打开外部化字符串(Externalize String)向导,该向导提供快捷途径将代码中的全部字符串经过配置文件读入,而且自动生成配置文件和读取配置文件的类;当代码须要国际化时使用该方法能够很方便地提供支持字符串国际化的程序框架。Find Broken Externalized Strings菜单项在指定的属性文件、包、工程或工程集中寻找损坏的外部化字符串。

4.Refactor菜单

Refactor菜单如图4.9所示。

Refactor是“重构”的意思,重构是一种从新组织代码以使代码更容易理解和更容易扩展的技术;重构技术定义了一系列改变代码组织方式和展现方式的步骤。Eclipse在这个版本中增强了对重构技术的支持,该菜单中的菜单项提供了对多种重构步骤的支持。

5.Navigate菜单

Navigate菜单如图4.10所示。

Navigate菜单提供了一些方便的方法用于在工程中的类之间、文件中的行之间、各编辑点之间切换,以及打开文件等功能。经常使用的有:

Go Into菜单项表示进入选中的目录。Go To菜单项提供将焦点在浏览区或文件代码内跳转的功能,例如:Go To→Type…子菜单打开Go To Type对话框,键入类型(类、接口等)名能够将浏览区的焦点跳转到指定的类型;Go To→Next Member子菜单将光标在文件编辑区中跳转到类的下一个成员的位置。

当光标处在某个变量位置或选中某个变量时,Open Declaration菜单项跳转到该变量的声明处(可能在一个文件内也可能在另一个文件中)。当光标处在某个类名的位置或选中某个类名时,Open Type Hierarchy菜单项打开Hierarchy视图,视图中将显示该类的继承层次结构。

Open Type菜单项打开Open Type对话框,在该对话框中输入类型名,能够在文件编辑区打开指定的类型,该类型能够是工做空间中或当前类路径中存在的类或者接口。Open Type in Hierarchy…菜单项打开Open Type in Hierarchy对话框,在该对话框中输入类型名,能够在Hierarchy视图中展现指定类型的类继承层次结构。Open Resource…菜单项打开Open Resource对话框,在该对话框中输入文件名能够在文件编辑区打开任意类型的文件,该文件能够是工做空间中或当前类路径中能够搜索到的任意文件。

图4.9  Refactor菜单     

      
图4.10  Navigate菜单

Show In菜单项能够展现光标所在的方法或类在浏览区中的位置,能够在包浏览器中展现也能够在导航视图中展现。Quick Outline菜单项打开当前编辑文件的内容结构对话框,而且能够经过在对话框中输入成员名将焦点切换到该成员。Quick Type Hierarchy菜单项打开当前编辑文件的继承层次结构对话框。Last Edit Location菜单项将光标和焦点跳转到上一个编辑位置。Go To Line…菜单项打开Go To Line对话框,在对话框中输入行号便可跳转到指定行。

Back菜单项跳转到前一个编辑点,与Last Edit Location菜单项不一样的是,该菜单项能够不断地往前面的编辑点跳转。Forward菜单项只有当使用过Back菜单项后才可用,它跳转到后一个编辑点,与Back相反。

6.Search菜单

Search菜单如图4.11所示。

Search菜单提供了在工做空间中的强大搜索功能。经常使用的菜单项以下。

Search…菜单项打开通用搜索对话框,该对话框提供了文件搜索、Java搜索和插件搜索,文件搜索能够搜索任意字符串而且能够指定待搜索的文件类型;Java搜索提供对Java类型、Java包、Java域、Java方法和Java构造函数的搜索;插件搜索提供对插件、插件Fragment和扩展点的搜索。另外,这三种搜索模式均可以指定待搜索的范围,包括:工做空间、工程、工程集等。File…菜单项提供了打开文件搜索的快捷方式。Java…菜单项提供了打开Java搜索的快捷方式。

图4.11  Search菜单

Text菜单项包括Workspace、Project、File和Working Set…子菜单项,分别表示在工做空间、工程、当前文件和工程集合中搜索光标所在字符串或选中的字符串。

References子菜单项也包括以上四个子菜单项,表示搜索选择字符串的全部引用。Declarations子菜单项表示搜索选择字符串的全部声明。当选择的内容是一个接口时,Implementors子菜单项表示搜索选定接口的全部实现。Read Access子菜单项表示搜索对选定变量读访问的位置。Write Access子菜单项表示搜索对选定变量写访问的位置。

Occurrences in File菜单项在当前文件中搜索选定Java元素的全部出现。

Referring Tests…菜单项搜索全部引用了选定Java元素的JUnit单元测试。

7.Project菜单

Project菜单如图4.12所示。

图4.12  Project菜单

Project菜单提供了查看工程信息和一些对工程和工程集的操做,其具体的菜单项以下。

Open Project和Close Project菜单项只有当选中浏览区的某个工程时才可用。Open Project当选中已关闭的工程时可用,它将打开选中的工程。Close Project当选中打开的工程时可用,它将关闭选中的工程。

Build All、Build Project和Build Working Set的子菜单项只有当Build Automatically菜单项没有被选中时才可用。Build Automatically菜单项表示自动对工程构建,被选中时Eclipse会在适当的时候自动对工程进行构建。当自动构建被关闭后,才须要手动激发构建动做。Build All菜单项激发对全部已打开工程进行构建;Build Project菜单项激发对选中工程进行构建;Build Working Set菜单项激发对设置的工做集进行构建,其子菜单项打开工做集设置对话框。Clean...菜单项清除工程中的构建结果,即将全部的编译输出清除,典型的就是清除全部的class文件;该菜单项打开Clean对话框用于选择待清除的工程或者清除所有工程。

Generate Javadoc...菜单项打开Generate Javadoc对话框,用于为工程中的代码生成Javadoc文档。Convert to a Dynamic Web project...菜单项只有当一个静态Web工程被选中时才可用,它将静态Web工程转换为动态Web工程,实际上就是将静态Web工程的目录结构和配置换成一个典型的动态Web工程的目录结构和配置。

Properties菜单项打开选中工程的属性,包括:该工程的路径、工程的编码、工程的Java构建类路径、工程的特殊编码格式规范等。在打开的属性窗口中能够查看和设置工程的这些属性。

8.Run菜单

Run菜单如图4.13所示。

图4.13  Run菜单

Run菜单提供一些与运行和调试代码的菜单项,编辑状态和调试状态的菜单项会有不一样,图4.13中展现的是编辑状态的Run菜单内容,经常使用的菜单项以下。

Run菜单项运行最近一次的运行记录,若是没有最近一次运行记录则弹出Run As对话框选择运行类型。Debug菜单项以调试模式运行最近一次的运行记录。

Run History菜单项的每个子菜单项都是一条历史运行记录,选择任何一个子菜单项能够再次运行该运行记录。Run AS菜单项的子菜单项是当前文件的可运行类型(例如,Java应用、Java Applet、JUnit等),经过选择子菜单项能够将当前文件做为该种可运行类型运行。Open Run Dialog...菜单项打开Run对话框,在该对话框中能够新建和配置一条运行记录,例如:指定运行的main函数类、添加运行参数、指定运行的类路径、指定运行时的环境变量等。

Debug History菜单项和Debug As菜单项同Run History菜单项和Run As菜单项相似,只不过前者都是以调试模式启动运行的。Open Debug Dialog...菜单项打开Debug对话框用于新建和配置debug运行记录,这里的设置与Run对话框中的同样。

下面从All References...到Step Into Selection的一些列菜单项只在调试代码时才可用。All References...菜单项用一个弹出窗口展现当前虚拟机中全部对选中类型的引用。All Instances...菜单项用一个弹出窗口展现选中类型当前在虚拟机中的全部实例。Watch菜单项在Expression视图中展现选中变量(或表达式)的当前值。Inspect菜单项用弹出窗口展现选中变量(或表达式)的当前值。Display菜单项用弹出窗口展现选中变量(或表达式)的类型。Excute菜单项执行选中的表达式。Force Return菜单项在当前执行处强制返回正在执行的方法。

Toggle Breakpoint菜单项在光标所在位置添加/取消适当类型的断点,根据位置不一样添加的断点类型不一样。Toggle Line Breakpoint菜单项在光标所在位置添加/取消行断点。Toggle Method Breakpoint菜单项在光标所在位置添加/取消方法断点。Toggle Watchpoint菜单项为选中域添加观察点,即当该域被访问(包括修改和读取)时就会产生一个断点。Skip All Breakpoints菜单项是一个状态菜单项,一旦被选中则使全部断点失效(但不删除断点,当该菜单项被选掉时全部断点又会生效)。Remove All Breakpoint菜单项删除工程中的全部断点,该操做不可恢复。Add Java Exception Breakpoint...菜单项打开Add Java Exception Breakpoint对话框,在对话框中能够选择任意的异常类型(包括用户自定义的异常类型)并添加,那么当处于调试运行模式时每次在抛出该类型异常的位置就会产生一个断点。Add Class Load Breakpoint...菜单项打开Add Class Load Breakpoint对话框,在该对话框中选择一个Java类型能够为其添加类装载断点,即处在调试模式下当每次装载该类时产生一个断点。

9.Window菜单

Window菜单如图4.14所示。

Window菜单提供了一些打开/关闭界面窗口和对界面窗口进行设置的菜单。主要还提供了对透视图(Perspective)和视图(View)的管理和设置。透视图和视图是Java界面中的重要该面,视图表示一种界面窗口,用于显示特定的内容,例如浏览区的包浏览器(Package Explorer)是一种视图、提纲显示区的文件结构(Outline)是一种视图等。透视图是一种视图的组合方式,例如本节开始的Eclipse界面展现的是Java透视图,这是最经常使用的一种透视图。本章后面内容将会对透视图和视图进行详细介绍。

New Window菜单项从新打开一个Eclipse窗口。New Editor菜单项从新打开一个文件编辑窗口。

Open Perspective菜单项打开一个透视图,即在透视图选择区添加一个透视图。Show View菜单项打开一个视图。Customize Perspective...菜单项设置当前透视图。Save Perspective As...菜单项将当前透视图另存为一个透视图。Reset Perspective菜单项重置当前透视图为默认设置。Close Perspective菜单项关闭当前透视图。Close All Perspective菜单项关闭透视图选择区中全部已打开的透视图。

Navigation菜单项提供了一些界面窗口之间导航的功能,例如,经过输入名称快速打开一个视图或透视图、最大化/最小化视图或编辑器、前一个/后一个编辑器、前一个/后一个视图、前一个/后一个透视图,等等。

Working Sets菜单项能够新建/编辑/删除工做集。

Web Browser菜单项能够设置默认打开的浏览器。

Preferences...菜单项打开Preferences对话框,该对话框用于对Eclipse开发环境和Eclipse中安装的插件的设置。本章后面的内容会对其中经常使用的设置进行介绍。

10.Help菜单

Help菜单如图4.15所示。

图4.14  Window菜单 

         

图4.15  Help菜单

 

Help菜单提供了一些辅助的帮助信息,其菜单项以下。

Welcome菜单项打开Eclipse的欢迎界面。

Help Contents菜单项打开Eclipse系统的帮助文档窗口。Search菜单项在Eclipse界面内打开帮助搜索视图,在其中能够经过关键字搜索帮助文档。Dynamic Help菜单项打开动态帮助视图,该视图根据程序员当前进行的动做动态地更新其中的帮助连接。

Key Assist...菜单项弹出对话框展现当前系统中配置的快捷键和对应动做的对应表。Tips and Tricks...菜单项提供快捷途径打开关于平台或Java编辑器的使用技巧帮助文档。Cheat Sheets...菜单项提供快捷途径打开一些便条(Cheat Sheet),其中主要提供了一些构建应用的向导。

Software Updates菜单项在线检查Eclipse官方网站上的更新信息,而且支持在线更新。

About Eclipse SDK菜单项打开关于Eclipse平台的简单信息,包括版本号、Build ID以及版权说明等信息。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

快捷图标栏

 

快捷图表栏定义了一系列快捷图标,绝大多数都是打开相应菜单的快捷方式,这些快捷图标在不一样的透视图中都是存在的。它们大部分都与某个菜单项一一对应,单击该图标至关于单击相应的菜单项。图标与菜单项的对应关系如表4.1所示。

表4.1  快捷图标功能对照表

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse经常使用配置

 

Eclipse提供了强大的可配置功能,Eclipse开发平台和Eclipse插件提供的属性均可以经过方便的界面进行配置。用于配置Eclipse平台和插件的窗口能够经过Eclipse菜单的Window→Preferences...菜单项打开,配置窗口如图4.16所示。

图4.16  Eclipse配置主窗口

如图4.16所示,Eclipse中几乎全部的可配置项和全部插件的可配置项都在Preferences对话框中进行设置。不带WTP插件的Eclipse只提供了Eclipse平台的设置以及Eclipse平台自带插件(如:Java开发插件和Plug-in开发插件等)的设置。本书介绍的WTP-Eclipse在这些设置的基础上又增长了WTP插件的设置,其中包括不少对Web开发工具的设置和J2EE开发工具的设置。本节将对其中比较经常使用的一些设置进行简单介绍,包括:Eclipse平台的设置、Java开发工具的设置、Web开发工具的设置等。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

快捷键设置

 

Eclipse开发平台为许多操做定义了快捷键,在前面介绍菜单时已经发现有些菜单项上已经标记了所使用的快捷键,并且经过Help→Key Assist...菜单能够打开全部当前已绑定了快捷键的命令以及其所使用的快捷键。Eclipse中快捷键的设置在Preferences对话框的General→Keys配置页中,如图4.17所示。

图4.17  快捷键配置项

全部的命令与快捷键对应关系的定义都在图中的列表里,每一行定义了一条命令和快捷键的绑定记录。Command列表示命令,即快捷键所作的动做;Binding列就表示该命令所使用的快捷键;When列表示该快捷键在什么状况下激发该命令;Category列表示该命令动做的类型。以图4.17中选中的这条记录为例,命令为Add Block Comment(添加块注释),快捷键为Ctrl+Shift+/,激发条件是在Editing Java Source(编辑Java代码)时,命令动做的类型是Source(源代码)类型,由于该命令的行为与编辑源代码有关。该快捷键的效果如图4.18所示:

a)选中待注释代码块          

         

           b)按下快捷键后自动进行注释

图4.18  使用快捷键进行块注释

在编辑Java代码时,选中一段代码,如a)图所示,而后使用快捷键Ctrl+Shift+/,结果如b)图所示,即为选中的代码段加上了块注释。

在该快捷键配置窗口中,用户能够删除快捷键绑定、更改快捷键、复制新的快捷键、为没有绑定快捷键的命令绑定快捷键等操做,具体操做以下。

1.显示全部命令所对应的快捷键

默认状态下命令和快捷键的绑定列表中只显示已绑定了快捷键的命令,选中界面中的Include unbound commands能够打开没有绑定快捷键的命令。

2.查看命令描述

选中任何一条记录,未绑定快捷键记录或已绑定快捷键记录,在Description栏所显示的就是对该命令的描述。

3.删除快捷键的绑定

选中一条已绑定快捷键记录,单击Remove Binding按钮就能够删除该快捷键同该命令的绑定关系,将该记录变成一条未绑定快捷键记录。

4.修改绑定的快捷键

选中一条已绑定快捷键记录,删除Binding文本输入框中的内容,而后直接在键盘上按下想要设置的快捷键便可(注意不是输入快捷键的名称,而是直接按下快捷键,Eclipse会自动检测按下的键)。而且还能够在When输入框的下拉列表中选择新的快捷键应用场景。

5.为未绑定快捷键记录指定快捷键

选中一条未绑定快捷键记录,使用与修改绑定快捷键相同的方式在Binding输入框和When输出框中输入适当的内容。

6.复制命令

选中一条已绑定快捷键记录,单击Copy Binding按钮能够在选中记录下增长一条新的未绑定快捷键记录,而且该记录的命令和类型与选中记录相同。

7.恢复成默认设置

选中一个记录,单击Restore Command按钮能够将该命令的快捷键设置恢复为Eclipse的默认设置;单击界面右下角的Restore Defaults按钮能够将全部命令的快捷键设置恢复为Eclipse的默认设置。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

定义用户库

 

在编写Java代码时,不少应用可能须要引入第三方的库(除本身编写的代码和Java提供的库以外的库),尤为是在使用J2EE编写Java Web应用时可能须要引入不少jar包。当须要引入的jar文件多了之后,程序员本身可能都很难记清楚全部jar文件的用途;并且在编写有些类型的应用时须要同时引入几个jar文件(好比,编写Struts2应用时须要同时引入struts2-core.jar、xwork.jar、struts2-api.jar等库文件),当这种库集合愈来愈多后程序员很难记住作某种应用须要用哪几个jar文件。Eclipse中提供了一个管理用户库的工具,程序员能够在这里定义用户库文件,而后在工程中直接加入该用户库就能够自动将用户库中包含的jar文件添加到工程的类路径中。

用户库在Preference对话框中的Java→Build Path→User Libraries中定义。界面如图4.19所示。

图4.19  用户库定义配置项

初始状态下中间的用户库列表是空的,由于默认是没有任何用户库定义的。单击New...按钮会弹出New User Library对话框,在对话框的User library name输入框中输入用户库的名称,肯定后就能够在用户库列表中添加一个新的库,如图4.20所示。

在图4.20中,UserLib是新定义的用户库的名称,选中UserLib能够对其进行操做。Edit...按钮能够重命名用户库的名称;Add JARS...按钮打开文件选择对话框,从中选择jar文件或者zip文件添加到用户库中。Remove按钮能够删除该用户库。当有多个用户库在列表中时,Up按钮和Down按钮能够调整用户库中包含库文件的前后顺序。

Import按钮和Export按钮用于导入和导出用户库。单击Export按钮会弹出Export User Libraries对话框,在该对话框中选择用户库列表中的用户库而且指定一个本地的文件路径,该对话框会将选择的用户库的配置信息导出到指定的文件中,文件类型为*.userlibraries;Import...按钮则刚好与Export...相反,该按钮打开Import User Libraries对话框,该对话框中选择一个本地的*.userlibraries文件,对话框会将该文件中的用户库导入到用户库列表中。

图4.20  完成用户库定义

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

配置Clean up首选项

 

在前面介绍Source菜单时,提到了Source菜单中的Clean up...菜单项根据Clean up preference的配置对代码进行清理。代码清理工做主要是对代码中的多余代码进行清除,或者从新组织代码的结构等,使得代码更加简洁、更加清晰。

Clean up配置项主页如图4.21所示。

图4.21  Clean up配置项主页

其中Active profile的下拉菜单中列出了当前已有的全部Clean up配置记录,每条记录定义了一套清理代码的方式,在用户未进行任何操做前这里只有Eclipse提供的一条默认记录,即Eclipse[built-in]。用户能够经过New...按钮新建Clean up配置记录,也能够经过Import...按钮导入已定义好的Clean up配置记录。新建配置记录的各项设置都采用默认设置。

用户能够对任何一个配置记录进行配置,步骤是在Active profile的下拉列表中选择待编辑的配置记录,而后单击Edit...按钮,会弹出如图4.22所示的配置对话框。

图4.22  定义Clean up属性文件

如图4.22所示,Clean up的配置项中有5个方面的配置:代码风格(Code Style)、成员访问(Member Accesses)、没必要要的代码(Unnecessary Code)、缺失代码(Missing Code)和代码组织(Code Organizing)。每一个方面配置页的左边都是可配置的项,右边都是当前配置的效果展现;当更改左边的配置项时,右边的效果展现会同时改变以展现当前配置项更改后的代码效果。

1.Code Style

该配置项提供给用户配置一些代码组织的风格,在乎义相同的代码风格之间选择一个用户首选的风格。

Use blocks in if/while/for/do statements用于说明当if/while/for/do语句的代码体只有一条语句时,用户是否但愿将该语句体加上大括号。Always表示无论什么状况都加上;No block for single ‘return’ or ‘throw’ statements表示当这一条语句是return语句或throw语句时不加大括号;No block for single statement表示无论这一条语句是什么语句都不加括号。

Convert for loops to enhanced用于说明是否将采用循环变量的循环形式转化为采用迭代的循环形式,示例以下:

循环变量形式迭代形式

for (int i = 0; i < dbArray.length; i++) {

    double value= dbArray [i] / 2; 

    System.out.println(value);

}for (int element : dbArray) {

    double value= element / 2; 

    System.out.println(value);

}

Use parentheses around conditions用于说明在什么状况下为条件语句加上括号。Always表示为全部的条件语句都加上括号;Only if necessary表示只在必要时加上括号。

Use modifier ‘final’ where possible用于说明若是能够的话在哪些状况下须要加上final修饰符。Private Fields表示为私有域加上;Parameter表示为参数加上;Local variables表示为内部变量加上。

2.Member Accesses

Use ‘this’ qualifier for field access说明在访问域变量时什么状况下须要在域变量前加上this限定符。Always表示全部状况下都加;Only if necessary表示只在必要时加,即只有当有本地有与域变量同名的变量时才加,由于这种状况下若是不加就会产生引用错误。

Use ‘this’ qualifier for method access与上一项相似,它说明在访问类方法时什么状况下须要在方法名前加上this限定符。

Use declaring class as qualifier说明在访问类的静态(static)域或方法时使用什么样的表达方式。Qualify field accesses表示在访问域时是否须要使用类名做为限定符;Qualify method accesses表示在访问方法时是否须要使用类名做为限定符;Change all accesses through subtypes表示是否须要将全部使用定义所在类的子类名做为限定符的表达都改变为使用定义所在类的类名做为限定符;Change all accesses through instances表示是否须要将全部使用实例做为限定符的表达都改变为使用类名做为限定符。

3.Unnecessary Code

Remove unused imports说明是否须要删除全部没有使用的import语句。

Remove unused private members说明须要删除那些没有使用的私有成员。Types表示删除全部没有使用的私有类型(Class、Interface等);Constructors表示删除全部没有使用的私有构造函数;Fields表示删除全部没有使用的私有域;Methods表示删除全部没有使用的私有方法。

Remove unused local variables说明是否须要删除全部没有使用的局部变量。

Remove unnecessary casts说明是否须要删除全部没必要要的类型转换。

Remove unnecessary ‘$NON-NLS’ tags说明是否须要删除全部没必要要的‘$NON-NLS’标记。

【注】

‘$NON-NLS’标记是一类做为提示的特殊标记,它们是以注释形式出如今Java代码中的标记。前面提到过Eclipse提供了Externalize String向导,支持自动将代码中的全部字符串外部化到一个配置文件中以支持字符串的可配置性。该标记就用于提示Java编译器和外部化向导具备该标记的行中的字符串不须要进行外部化。

4.Missing Code

Add Missing Annotations说明是否要增长缺失的注记(Annotation),这里只支持@Override和@Deprecated注记。注记在代码中只起到辅助做用,它们不会影响Java代码的编译和执行。

Add serial version ID说明是否须要增长缺失的序列化版本号(serial version ID)。Generated表示生成一个版本号并增长;Default(1L)表示增长默认的版本号(1L)。序列化版本号是实现了Serializable接口的类的一个域,它用于在序列化对象时使对象的更改状况可控。

5.Code Orginazing

Format source code说明在执行Clean up时是否对代码同时执行Format操做。

Remove trailing whitespace说明是否去掉行末的空白字符。All Lines表示去掉全部行末的空白字符包括空行中的空白字符;Ignore empty lines表示只去掉非空行的行末空白字符。

Organize imports说明在执行Clean up时是否对代码同时执行Organize imports操做。

Sort members说明在执行Clean up时是否对代码同时执行Sort members操做。Sort all members表示对全部成员都进行排序;Ignore fields and enum constants表示只对除域和枚举常量外的成员进行排序。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

配置Java代码模板

 

在为Java代码中的某种程序元素(如:类、域、方法等)添加注释时或者在生成某种新的程序元素(如:类、get方法、catch语句等)时Eclipse会自动生成一个注释模板或程序元素内容模板。并且,Eclipse还支持对这些模板进行定制,能够在Java→Code Style→Code Templates中进行设置,如图4.23所示:

如图4.23所示,右面窗口上面的列表列出了全部能够设置的模板项,当点击一个模板项时,该模板的内容会在下面的文本框中显示。若是用户想更改某模板的内容,能够选择某模板而后单击Edit按钮,这样会打开模板编辑器,用户能够直接在编辑器中输入模板的内容便可。

模板中形如${variable}的内容是一种对变量的引用,其中variable是Eclipse定义的一种变量,它随使用场景而改变,例如,${user}的值是当前系统的用户名,${date}的值是当前系统时间等。Edit...按钮打开的模板编辑器对话框左下角有图标  ,单击该图标能够打开动态帮助,在动态帮助中有对每一个variable具体意义的解释。

图4.23  代码模板配置项

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

配置Java代码格式化工具

 

在前面介绍菜单时提到Source→Format菜单能够根据Code Formatter preference的设置对选中的全部代码进行格式化,这个功能有助于保持编程风格的统一。Code Formatter preference在Preferences中的Java→Code Style→Formatter中设置,设置的主页面如图4.24所示。

图4.24  代码格式化配置项

同Clean up的配置同样,该页面的Active profile文本框中显示的是当前系统中使用的Formatter的配置,下拉列表中列出了全部可用的配置。New...按钮能够新建配置、Import...按钮能够导入外部的配置文件(XML文件)、Edit...按钮对当前系统使用的配置进行编辑。

图4.25  定义格式化属性文件

图4.25为定义格式化属性文件,其中Formatter包含了八类配置项:Indentation、Braces、White Space、Blank Lines、New Lines、Control Statements、Line Wrapping、Comments。

1.Indentation

该类配置主要说明了一些对缩进的配置,包括:缩进的格式以及哪些地方须要缩进等。其中,Tab policy说明使用Tab键仍是空格键进行缩进。Use tabs only for leading indentations说明是否只对leading indentation使用tab。Indentation size和Tab size说明缩进的字符数。

Align fields in columns说明类声明中的域名称是否须要使用tab对齐。

在Indent组合框中的全部复选框都用于说明在所描述的状况下是否须要缩进,例如:Declarations within class body表示在类内的声明行是否须要相对类的声明行向后缩进一级;Statements within method/constructor body表示在方法或构造函数体内的语句是否须要相对于方法的声明行向后缩进一级。

2.Braces

Braces类配置规定了在各类出现大括号的场合中大括号的位置。可选择的位置有四类:

Same line:表示大括号与声明或者语句的前面部分在同一行。通常都是指左大括号,由于大括号内若是有新的语句,那么新的语句都会另起一行。

Next line:表示大括号另起一行。

Next line indented:表示大括号另起一行,并且在上一行的基础上缩进一级。

Next line on wrap:表示若是语句出现截断时大括号另起一行,不出现截断时大括号不另起一行,以下所示:

Class This_is_a_very_long_class_name

        extends SomeClass

{

...

}class ShortName extends SomeClass {

   ...

}

a)Next line on wrap示例b)Same line on no wrap示例

该类配置中的全部配置项都表示了一种出现大括号的场合,后面选择相应的值就表示在该种场合下大括号应该出现的状况。例如:Class or interface declaration表示在类或者接口声明中的大括号,Anonymous class declaration表示匿名类声明中的大括号,Array initializer表示数组初始化中使用的大括号。

3.White Space

该类配置规定了在什么状况下但愿出现空格。例如:Declaration→Classes中选中before opening brace of a class表示在类声明中的左大括号前加上空格;Control statements→‘if else’中选中before opening parenthesis表示在if/else语句中用于括起布尔表达式的左圆括号前加空格。

4.Blank Lines

该类配置规定了在什么状况下但愿出现空行。例如:After package declaration设为1表示在包声明行后增长一个空行;Before field declarations设为0表示类内的域定义前不添加空行;Number of empty lines to preserve设为1表示对代码中已存在的连续空行合并为一个空行。

5.New Lines

该类配置规定了在什么状况下但愿出现换行,即另起一行。例如:in empty class body表示是否在空的类中添加换行符;Insert new line after opening brace of array initializer表示在数组初始化表达式中是否在左大括号后添加换行符;Put empty statement on new line表示是否在空语句后加上换行符;Insert new line after annotations表示是否在注记后加上换行符。

6.Control Statements

该类配置规定了控制语句(if/else/while/do/try/catch/return/throw等)的一些格式。例如:Insert new line before ‘else’ in an ‘if’ statement表示在if/else语句中是否在else语句前添加换行符;Keep ‘return’ or ‘throw’ clause on one line表示当if或else语句中只有一个return或throw语句时,是否保持该条语句与if或else语句在同一行。

7.Line Wrapping

默认状况下一条语句都用一行表示,可是为了代码结构的清晰和程序员阅读方便,有时须要将一条语句分红多行显示。该类配置规定了在什么状况下将一条语句分红多行。Maximum line width规定了代码中一行最长容许的字符数,当一行代码的字符数多于该值时就对代码分行。Default indentation for wrapped lines规定了当将一行代码分红多行时下一行默认的缩进级数(注意不是字符数)。Default indentation for array initializers规定了在数组初始化中,若是须要分多行时默认的缩进级数。

界面下部的树状列表分别列出了各类状况下但愿的分行方式,每种方式均可以在预览栏中看到格式化的效果。

8.Comments

该类配置规定了关于注释的一些格式。Enable Javadoc comment formatting表示是否容许对Javadoc注释格式化,若是该配置项没有选中的话Javadoc comment settings中的各项配置将没法配置。Enable block comment formatting、Enable line comment formatting和Enable header comment formatting分别表示是否对块注释、行注释和头注释格式化。

Javadoc comment settings列出的配置项用于规定如何对Javadoc注释进行格式化,例如:Blank line before Javadoc tags表示在注释中的Javadoc标记前添加空行;Remove blank lines表示去掉注释中全部的空行。

Maximum line width for comments规定了注释中一行所容许的最多字符数,当某注释行超过该长度时就会自动变成多行。

Eclipse提供的代码格式化工具是一个很是强大的工具,它定义的格式项很是的详细,这对于某些对代码格式要求比较严格的团队来讲是很是有用的。可是对于初学者来讲,建议不要过多地修改格式化的配置项,由于Eclipse内建的格式化配置所提供的默认值都是借鉴了许多成功开发团队的代码格式规范,表明了Java开发领域中的主流风格,初学者应该去适应和习惯这种风格而不是改变这种风格。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

配置Web开发工具

 

在安装了WTP插件的Eclipse中,Preferences配置包含Web and XML配置项,这个配置项提供了对Web开发工具的配置,主要是对Web文件编辑器的一些配置。主要包含CSS文件、DTD文件、HTML文件、JavaScript文件、JSP文件、XML文件等。该配置的主页面如图4.26所示,选择每一个子配置项对该项进行配置。

图4.26  Web and XML配置项

从图4.26中能够发现,对各类文件的配置主要分三类:Source、Syntax Coloring和Templates。Source用于配置在编辑文件时的格式化和内容辅助等功能;Syntax Coloring用于配置各类文件中的语法着色,好比关键字的颜色、字符串的颜色等;Templates用于配置一些固定的模板,这些模板能够在编辑文件时被选择用以自动生成特定格式的代码。下面以HTML文件为例对这些配置进行介绍。

1.Source

HTML Files→Source的配置页如图4.27所示:

图4.27  Source配置页

Formatting组的配置项主要用于说明HTML文档的一些格式,例如:Line width表示单行最多字符数;Indent using tabs和Indent using spaces用于选择使用tab键进行缩进仍是使用空格键进行缩进。

Content assist组的配置项主要用于内容帮助的设置,例如:Automatically make suggestions表示是否自动提供内容建议,当该项被选中时Prompt when these characters are inserted就会变为可编辑状态,其中列出的字符表示当输入这些字符时激发内容帮助。

Preferred markup case for content assist, and code generation组说明,在内容协助和自动生成的代码中但愿标签名和属性名使用大写仍是小写。

2.Syntax Coloring

HTML Files→Syntax Coloring的配置页如图4.28所示:

图4.28  Syntax Coloring配置项

在Syntax Element列表框中的每一项就表示一种类型的文字,具体含义如表4.2所示。

表4.2  语法元素含义对照表

选中某一种文字类型,就能够在右边设置该类文字的颜色和属性。其中Foreground表示前景色;Background表示背景色;Bold表示是否加粗;Italic表示是否变成斜体;Strikethrough表示是否加中画线;Underline表示是否加下画线。

1.Templates

HTML→Templates的配置页如图4.29所示。

图4.29  Templates配置项

在图4.20的表格中列出了当前定义的全部HTML模板,用户能够经过New...按钮添加新的模板和经过Remove按钮删除已有模板,也能够经过Edit...按钮编辑已存在的模板。

每个模板都有一个模板名称,在编写HTML文件时,激活内容帮助(Edit→Content Assist或者经过Ctrl+Space快捷键)并输入模板名称,而后选择对应的模板就能够在光标处加入模板的内容。如图4.30所示。

a)激活内容帮助        

              

b)自动添加代码

图4.30  使用内容帮助自动添加代码

Eclipse和WTP插件都提供了许多的配置项,因为篇幅有限,本章只介绍几种比较经常使用的配置。实际上Eclipse的各项配置都提供了很是通用的默认值,因此初学者能够先不用过多的关注如何配置这些配置项;假如确实须要作相应配置请参阅Eclipse的帮助,在每一个配置页都有动态帮助按钮  ,读者能够单击该按钮打开动态帮助进行学习。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse插件

 

Eclipse的全插件模式提供了Eclipse高度的灵活性和可扩展性,丰富多样的Eclipse插件提供了Eclipse强大的功能和生命力,学会如何安装Eclipse插件是使用Eclipse的必需本领。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

浏览插件

 

在进行插件安装前,有时须要先查看当前已安装了哪些插件,由于有些插件的安装必需要有另外的插件做为前提。经过菜单Help→Software Updates→Manage Configuration能够打开Product Configuration对话框,如图4.31所示。

该对话框中列出了Eclipse当前已安装的全部插件。在左边树列表中单击Eclipse SDK,右边面板会显示针对整个Eclipse的产品配置状况,其中能够扫描Eclipse的在线更新状况、能够查看Eclipse的安装历史等;在树列表中单击任何一个插件(如图4.31中情景)能够查看插件的在线更新状况(Scan for Updates)、能够禁用/启用该插件(Disable,对于已禁用的插件这里会显示Enable)以及显示插件的版本、提供者和许可证等信息(Show Properties)。

图4.31  产品配置对话框

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

安装插件

 

Eclipse插件的安装很是简单,但在安装时要注意插件的依赖关系。Eclipse插件是具备依赖关系的,Eclipse在启动时会按照插件的依赖关系逐个将插件装载,若找不到所依赖的插件,则所安装的插件就有可能运行不正常。

一般在下载插件的根目录中会有plugin.xml文件,该文件中的<requires>标记列出了插件所依赖的其余插件,<requires>的<import>子元素中的内容即表示所依赖插件的ID。一个插件的例子以下所示。

<plugin ... >

        ...

        <requires>

                <import plugin="org.eclipse.core.runtime" /> 

                <import plugin="org.eclipse.core.resources" /> 

                <import plugin="org.eclipse.ui" /> 

                <import plugin="org.eclipse.debug.core" /> 

                <import plugin="org.eclipse.swt" /> 

                <import plugin="org.eclipse.jdt.core" /> 

                <import plugin="org.eclipse.jdt.launching" /> 

                <import plugin="org.eclipse.jdt.debug" /> 

                <import plugin="org.eclipse.jdt.ui" /> 

                <import plugin="org.eclipse.debug.ui" /> 

                <import plugin="org.eclipse.jdt.debug.ui" /> 

                <import plugin="org.eclipse.core.runtime.compatibility" optional="true" /> 

                <import plugin="org.eclipse.ui.ide" optional="true" /> 

                <import plugin="org.eclipse.ui.views" optional="true" /> 

        </requires>

        ...

</plugin>

【注意】

严格来讲一般所下载的插件应该是插件集,它里面包含了不少插件(包含其依赖的插件或自己就包含多个插件),因此在插件根目录外还会有外层目录,例如单独下载的WTP插件。包含plugin.xml文件的插件根目录是指某单个插件的根目录。

Eclipse有三种途径安装插件:在线更新、直接复制和连接文件。后两种都须要首先将插件文件下载到本地,第一种则是Eclipse自动从官方网站上下载而且安装。

1.在线更新

Eclipse提供了在线更新功能,Eclipse菜单Help→Software Update→Find and Install...会打开Install/Update对话框,如图4.32所示。

图4.32  Install/Update对话框

如图4.32所示,Search for updates of the currently installed features是搜索和更新当前已安装插件的最新版本;Search for new features to install是搜索和安装当前未安装的插件。若是选择第一项,单击Finish直接开始搜索(可能须要选择镜像站点);若是选择第二项,单击Next>按钮弹出Install对话框,在该对话框中选择想要更新的插件类型,选择后Eclipse更新管理器就只搜索被选择类型的插件。

在搜索结束后,若是有可更新的插件,这些插件就会被列出,用户只须要选择想要更新的插件而且按照指示操做,就能够将选定的插件安装到Eclipse中。

这种插件安装方式不须要提早寻找和下载插件,全界面操做完成安装,并且也不用考虑插件的依赖关系;可是这种方式必须首先得到插件更新的站点,并且只能经过这种更新下载方式,因此搜索和下载速度一般会比较慢并且可以安装的插件也很是有限。

2.直接复制

这种安装方式必须首先已经将插件文件下载到本地文件系统中,而后将已有的插件安装到Eclipse中。

Eclipse安装完成后,根目录内容如图4.33所示。

图4.33  Eclipse安装目录

该目录中的plugins目录用来放置全部的插件文件。Eclipse插件一般的根目录名为插件的包名加上插件的版本号,例如:org.eclipse.sdk_3.3.1.r331_v20070904。将下载插件的根目录直接复制到plugins目录下,重启Eclipse后便可完成该插件的安装。

这种插件安装方式简单易行,不容易出错;可是这种方式将全部插件都复制到plugins目录下,使得该目录过于庞杂,不利于插件的管理和动态加/卸载。

3.连接文件

这种方式是在直接复制的基础上将插件分类进行管理,而后经过连接文件的形式装载到Eclipse中。这种方式是比较好的插件安装方式,建议读者使用这种方式安装和管理Eclipse插件。

首先,在本地文件系统中(能够在Eclipse安装目录中也能够在Eclipse安装目录外)新建一个文件夹(例如在Eclipse根目录中新建MyPlugins目录)用于放置全部的Eclipse插件。

其次,为待安装插件(假设根目录为org.webtools.sdk_2.1_v20071210)取一个便于记忆和识别的名称(例如,WebTools),以该名称在MyPlugins目录中新建一个目录而且将插件根目录复制到该目录中。

而后,在Eclipse根目录中新建一个links目录,在该目录中新建一个*.link文件(例如,webtools.link),在该文件中加入一行path=MyPlugins/WebTools(假定MyPlugins目录在Eclipse根目录中),保存并关闭。

最后,重启Eclipse便可完成插件的安装。

这种安装插件的方式有利于分类管理插件,而且能够方便地卸/加载插件。当有新插件须要安装到Eclipse中时,能够在MyPlugins中创建该插件的目录,同时在links目录中新建和编辑新的link文件或在已存在的link文件中添加一个新行便可。当想卸载某个已安装的插件时,有不少方法:改变该插件对应link文件的后缀(改变为除link的其余值)、清除link文件中对应的行或将link文件移至其余目录等方式,只要使Eclipse没法在links目录中发现该插件便可。

 
 
 
 

第 4 章:Eclipse集成开发环境做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

本章对Eclipse的界面、经常使用配置以及Eclipse插件的安装进行了简单的介绍。

Eclipse中用于显示特定内容的窗口称为视图,视图的组织和布局方式称为透视图,Eclipse经常使用的透视图有Java、Debug等。

Eclipse的配置对话框经过Window→Preferences菜单打开,Eclipse及其所安装的全部插件均可以在该对话框中进行设置,最经常使用的配置项有:General→Keys用于配置快捷键;Java→Build Path→User Libraries用于配置用户自定义的库;Java→Code Style→Clean Up用于配置代码清理命令的操做原则;Java→Code Style→Code Templates用于配置自定义代码模板,能够在编辑代码时输入模板;Java→Code Style→Formatter用于定义和配置Java代码格式化的操做原则,在使用格式化命令时使用;Web and XML主要包括了对Web开发插件集的配置。

经过菜单Help→Software Updates→Manage Configuration能够浏览当前已安装的插件;安装Eclipse插件有三种方式:在线更新、直接复制和连接文件。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Eclipse Web工程

 

Eclipse为Java Web应用提供了专属集成开发环境,提供了Web工程管理、Web对象辅助开发等功能。本章将介绍在Eclipse中创建和配置Web工程的方法以及如何使用Eclipse开发各类Web对象。

本章在介绍使用Eclipse开发JSP和Servlet对象时会涉及一些JSP和Servlet的相关知识,对JSP和Servlet不了解的读者能够先大略了解,若有不懂可等学习了第8章和第9章后再结合学习这部份内容。

同其余大多数集成开发环境同样,Eclipse将应用程序的开发组织成工程进行管理,不一样的工程用于开发不一样的应用程序。在前面已经介绍过File→New→Project…菜单打开New Project菜单,该菜单中列出了各类不一样类型的工程,其中Web目录下的Dynamic Web Project和Static Web Project是WTP专门提供为开发Web应用的工程。Dynamic Web Project是动态Web工程,Static Web Project是静态Web工程。

在开发Web工程时,Eclipse会在程序员确认后打开Java EE透视图,该透视图的Eclipse开发界面如图5.1所示。

 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

静态Web工程

 

静态Web工程用于开发非交户式的Web应用,在应用中只有静态内容,如HTML页面、图片等,没有Servlet、JSP等动态Web内容。

新建静态Web工程的步骤以下。

图5.1  Java EE透视图

1.在New Project对话框中选择Static Web Project,弹出对话框如图5.2所示。


图5.2  新建静态Web工程

在该对话框的Project name栏中输入工程名称,例如:StaticWebProject。

2.单击Next>按钮,弹出菜单如图5.3所示。

图5.3  选择工程模块

该对话框中列出了要在该工程中包含的模块,静态Web工程当前的版本只包含惟一的模块,即Static Web Module,版本号是1.0。

3.单击Next>按钮,弹出对话框如图5.4所示。

图5.4  配置Web模块设置

该对话框中列出了以下两项配置:

Context root:表示Web应用上下文的根目录,默认值一般与该静态工程的名称同样,此处为StaticWebProject;在使用Eclipse开发Web工程时该配置项没有实质意义,一般保持默认值便可;

Web content folder name:用于放置Web内容的目录,在此处指定一个目录名后Eclipse所作的只是在工程根目录中新建一个具备该名称的子目录;读者能够指定一个本身习惯的名称(此处能够保留其默认值,或者为了之后方便使用也能够使用更简单的名称,例如Web等)。

4.单击Finish完成新建静态Web工程向导,新建工程成功后,在左边的Project Explorer视图中就能够发现该工程;展开该工程能够发现该工程中的内容,如图5.5所示。

在文件系统中打开工程目录,工程根目录的内容如图5.6所示。

图5.5  静态Web工程内容          

 

图5.6  静态Web工程根目录

 

其中WebContent是新建工程时在Web content folder name中输入的名称所新建的文件夹,初始是空的;.settings和.project都是Eclipse自动生成的用于管理工程的辅助文件或文件夹,它们对Web应用没有意义。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

动态Web工程

 

动态Web工程的内容比静态Web工程的内容要丰富得多,它容许在工程中加入交互式的动态内容。新建的步骤与静态Web工程相似。

1.在New Project对话框中选择Dynamic Web Project,弹出对话框如图5.7所示。

图5.7  新建动态Web工程

与静态Web工程相似,在该对话框的Project name栏中输入工程名称,例如:DynamicWebProject。

2.单击Next>按钮,弹出菜单如图5.8所示。

图5.8  选择动态Web工程模块

能够发现,动态Web工程所支持的模块比静态Web工程要多不少,不一样的模块在Web工程中支持不一样的Java技术。其中,Axis2 Web Services用于支持Web服务;Dynamic Web Module用于支持动态Web技术,后面的版本号表示支持的Servlet规范版本号;Java用于支持Java开发的内容,后面的版本号表示JDK的版本号;Java Persistence用于支持Java持久化,后面的版本号是使用的JPA版本号;JavaServer Faces用于支持JSF,后面的版本号是使用JSF的版本号;WebDoclet(Xdoclet)用于对WebDoclet的支持,后面的版本号是使用WebDoclet的版本号。对于只用于学习JSP和Servlet的工程来讲,只须要选择默认的两样就足够了。

3.单击Next>按钮,弹出菜单如图5.9所示。

图5.9  配置Web模块设置

在图5.9所示的对话框中前两个输入项的意义与静态Web工程相同;因为在动态Web工程中须要编写Java代码,因此这里增长了Java Source Directory,用于说明放置Java源代码的目录,Eclipse会在工程的根目录下再创建一个src目录。

4.单击Finish完成新建静态Web工程向导,新建工程成功后,在左边的Project Explorer视图中就能够发现该工程;展开该工程能够发现该工程中的内容,如图5.10所示。

图5.10  动态Web工程内容

在文件系统中打开工程目录,工程根目录的目录结构如5.11图所示。

图5.11  动态Web工程根目录

其中.settings、.classpath和.project都是Eclipse自动生成的用于管理工程的辅助文件;src目录用于放置Java源代码;build目录用于放置工程编译后的输出文件,classes目录用于放置对src目录下的java文件编译后的class文件;WebContent用于放置Web内容,一般将 Web应用中全部的HTML文件、JSP文件、图片等网页元素文件按照适当的目录放置在该目录中;WEB-INF是Web应用的信息目录,其中lib目录用于放置工程所须要的库文件,web.xml是Web应用的描述文件,它在Web应用中起到了很是重要的做用,其具体内容结构将会在后面章节中介绍;META-INF用于放置工程的元数据信息,其中的MANIFEST.MF是用于描述工程的信息,初始只自动添加了版本属性。

因为静态Web应用涵盖的内容只是动态Web应用的一个子集,并且Java Web开发技术也主要是针对动态Web应用开发技术,因此后面的内容主要针对动态Web工程进行讲解。若是不加特别说明,那么所提到的Web工程也都是动态Web工程。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Web工程属性配置

 

在Project Explorer中列出的Web工程上单击鼠标右键,在右键菜单中选择Properties会弹出该工程的属性对话框,如图5.12所示。

图5.12  动态Web工程配置

在该对话框中能够查看和配置与工程相关的不少属性。其中常见的属性以下。

1.Resource

该页列出了工程的相对路径、位置、更改时间、编码、行分隔符(在UNIX类系统和Windows系统中的分行符是不一样的),同时也能够对编码和行分隔符进行配置。

2.Java Build Path

该页用于配置在该工程中编写Java代码时所引用的Java构建路径,配置对话框如图5.13所示。

Source页用于配置源代码目录(Source folders on build path)以及编译输出目录(Default output folder),Eclipse只对在源代码目录中的内容进行编译而且将编译的输出放置在编译输出目录中。

Projects页用于配置须要添加到该工程构建路径中的工程,只有添加到这里的其余工程才可以被该工程引用,待添加工程只能是当前工做空间中已打开的工程。

图5.13  配置动态Web工程的Java Build Path属性

Libraries页用于向工程中添加库文件,只有添加到这里的库文件才可以被该工程中的Java代码引用。Add JARs按钮用于选择并添加工做空间内的库文件;Add External JARs按钮用于选择并添加本地文件系统中的库文件;Add Variable…按钮用于添加一个环境变量;Add Library按钮用于添加Eclipse携带的库文件(例如,JRE、JUnit等);Add Class Folder按钮用于将文件系统中的一个目录做为库添加到工程构建路径中。

Order and Export页用于定义寻找资源时的搜索次序,候选搜索源包括工程源代码目录、引入到工程构建路径中的工程和库文件。当同一个资源在候选搜索源中出现超过一次时,排在前面的资源将会被引用。

3.Java Compiler

该配置页用于配置编译Java代码时使用的JDK的版本,配置对话框如图5.14所示。

图5.14  配置动态Web工程的Java Compiler属性

其中最主要的配置项就是但愿使用哪一个版本的JDK对工程中的Java代码进行编译,如图5.14.中所示的Compiler compliance level对应的下拉选择框中列出了当前可用的JDK版本,一般默认都是最高版本。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

编辑Web内容

 

使用集成开发工具开发应用的优势就在于集成开发工具提供了能够在同一个界面环境下完成开发特定应用所须要进行的大部分工做。安装了WTP的Eclipse为程序员提供了开发Web应用和J2EE应用绝佳的集成开发环境,程序员能够在Eclipse界面中完成开发Web应用的大部分开发工做,并且还提供了不少开发Web对象的辅助编辑工具。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

开发静态Web对象

 

静态Web对象是指除Servlet、JSP等以外,在Web上的展示效果不会发生变化的对象文件,例如:各类图片、HTML页面、CSS文件、JavaScript代码文件等。对于这些类型的文件,能够使用浏览器在本地打开,在本地打开的展现效果与客户端浏览器经过Web服务器访问得到的展现效果彻底相同;并且任何客户端在任什么时候间访问得到的效果也不会有很大的差别。这也正是称其为静态Web对象的缘由。

WTP-Eclipse直接提供了对HTML、CSS、JavaScript等静态Web对象进行编辑的功能。在File→New→Others…菜单中,在Web文件夹中列出了这几种文件类型,选择相应的文件能够经过向导建立文件。

1.新建静态Web对象

静态Web对象都是以文件的形式存在,客户端浏览器经过Web服务器直接从Web应用的Web根目录中根据Web对象的路径获取这些文件。Eclipse动态Web工程的WebContent目录正是用于放置这些Web文件的根目录,在将Web工程部署到Web应用中后该目录中的全部Web文件将被放置在Web应用的根目录中,而且保持原有的目录结构。

因此在新建静态Web对象时,应该将全部的文件都新建在工程的WebContent目录中,而且按照最终在Web应用中的目录结构进行组织。假设,Web应用demo中有两个静态Web对象:index.html和bg.jpg,index.html经过http://localhost:8080/demo/index.html访问而且经过相对路径image/bg.jpg引用bg.jpg,那么在Web应用根目录中应该有index.html文件,和image目录,而且bg.jpg在image目录中;那么在开发demo应用时,应该将index.html文件和image目录都放在demo工程的WebContent目录中。

不管是开发HTML、CSS、JS文件或者引用图片,都应该将这些文件按照目标目录结构(即最终在Web应用中的目录结构)组织在WebContent目录中。

新建这些静态Web对象是很是简单的,Eclipse都提供了新建向导。以新建HTML文件为例,目标是在WebContent目录中新建一个display目录,而后在该目录中新建一个overview.html。步骤以下。

(1)在工程中的WebContent目录上单击鼠标右键,在右键菜单中选择New → Folder,新建一个目录,如图5.15所示。

图5.15  新建一个目录

在弹出的对话框中输入目录名:display,肯定后完成目录的新建。

(2)在工程浏览器视图中WebContent目录下会出现一个新的目录display,在display上单击鼠标右键,在右键菜单中选择New → HTML,如图5.16所示。

图5.16  新建HTML文件

(3)选择HTML后将弹出如图5.17所示的对话框。

图5.17  新建HTML文件向导

在图5.17的对话框中能够选择存放HTML文件的目录以及HTML文件的名称,存放目录默认是单击右键所指向的目录。在文件名输入框中输入HTML文件的名称overview.html(后缀也能够是htm)。单击Next > 按钮会弹出一个窗口用于选择文件模板,待选择的模板都是当前Eclipse中预约义的HTML模板(模板的定义在Eclipse配置时已介绍过);单击Finish按钮将使用默认的HTML模板完成新建向导。

按照这种方式能够依靠Eclipse提供的新建向导完成几乎全部须要编辑的静态Web对象的新建,而且经过在父目录上单击右键调出新建向导能够很容易地将Web对象按预期的目录结构进行组织。

可是从图5.16和图5.17两个图中能够发现,New菜单项只提供了不多的Web对象,并无提供JavaScript文件、CSS文件等。这是由于菜单中可以提供的选择是有限的,Eclipse不可能将提供的全部文件类型都列在菜单项中,可是Eclipse提供了Other...菜单项,经过该菜单项能够打开New对话框,经过New对话框能够选择全部可能的文件类型,如图5.18所示。

图5.18  New对话框

图5.18所示就是New对话框,它分类列出了全部Eclipse提供新建向导支持的对象类型,其中Web目录中主要提供了各类Web对象类型,包括下面将要介绍的动态Web对象JSP和Servlet。

2.编辑HTML

新建立的HTML文件内容并非空的,而是一个简单HTML文件框架,具体的框架内容根据选择模板的不一样而不一样。图5.19为选择默认HTML模板生成的框架内容:

图5.19  新建HTML文件内容

在Eclipse中,默认会使用HTML编辑器打开HTML文件(*.html和*.htm),HTML编辑器对HTML文件的编辑提供了支持。

大多数编辑过HTML文件的程序员都会有一个感觉:编辑HTML文件的最大难点就是HTML标准中定义了大量标签,而且大部分标签又定义了不少的属性,程序员很难准确记住每一个元素的名称和使用格式,以及每一个元素都有哪些属性。Eclipse的HTML编辑器提供的内容提示功能刚好解决了这个问题,HTML编辑器的内容提示功能相似于Java编辑器中的内容帮助功能。程序员在任何一个元素的开始标签和结束标签之间按下Alt+/(菜单Edit→Content Assist的快捷键),编辑器会弹出一个候选插入元素列表,列表中按字母顺序列出了全部可能出如今当前位置的元素以及在HTML设置中预约义的模板;一样,程序员在元素的开始标签中输入空格后再按下Alt+/能够调出候选插入属性列表。并且,这些列表还能够根据程序员的输入动态过滤列表中的候选元素。如图5.20、图5.21所示。

a)激活内容帮助

  
 b)输入字符过滤

 

 c)自动增长标签

 

图5.20  使用内容帮助插入元素

a)激活内容帮助        

 b)输入字符过滤

 

 c)自动增长属性

 

图5.21  使用内容帮助插入属性

3.编辑JavaScript和CSS文件

同HTML相似,在新建向导中选择JavaScript项即启动新建JavaScript向导,在向导中选择存放目录和文件名后生成一个空的JS文件。JavaScript文件编辑器一样也提供了内容帮助功能,能够提供对象名补齐和显示候选方法列表等功能,如图5.22所示。

 

 

a)激活内容帮助输入对象

 
 b)激活内容帮助输入方法

 

图5.22  使用内容帮助编辑JavaScript代码

使用一样的过程读者能够完成对CSS文件的建立,也能够使用CSS编辑器的Content Assist功能,如图5.23所示。

a)激活内容帮助        

 

 b)输入字符过滤

 

 

 c)自动添加代码

图5.23  使用内容帮助编辑CSS代码

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

开发动态Web对象

 

Eclipse除了能够开发静态Web对象外,还提供了对Servlet和JSP开发的支持。Servet自己就是一种特殊的Java类,Eclipse内建提供了对开发Java代码的支持;而对于JSP文件来讲,Eclipse也提供了新建向导和JSP编辑器以提供对JSP的开发支持。

1.开发JSP文件

同静态Web对象同样,JSP也是以文件的形式经过相对路径进行访问的,因此在开发JSP文件时,一般也将其按照预订的目录存放在Web工程的WebContent目录中。

JSP的新建向导相似于HTML,在其父目录上单击鼠标右键,在右键菜单中选择New → JSP打开新建向导,在弹出的对话框中输入存放目录和文件名而且选择模板完成JSP文件的新建。

JSP是在HTML文件中加入动态的Java代码,因此JSP编辑器至关于将HTML编辑器与Java编辑器相结合。在编辑HTML代码时能够激活编辑HTML代码的内容帮助功能,在编辑Java代码时能够激活编辑Java代码的内容帮助功能。除此以外,对于一些JSP特有的内容,JSP编辑器也提供了支持,例如激活HTML内容帮助时支持对JSP内置标签的选择,激活Java内容帮助时支持对JSP内置对象的访问。

2.开发Servlet

Servlet是全部Web对象中最特殊的一种。Servlet与其余Web对象不一样,不是以文件的形式存在,而是一种特殊的Java类。Servlet以Java类的形式被编辑,编译成的class文件被放在Web应用的类路径中,而后经过配置进Web应用的描述文件中被部署到Web应用中。在有请求访问Servlet时,Web服务器调用Servlet响应请求。因此这就决定了Servlet不能像其余Web对象同样直接放在WebContent目录下,而应该放在Web工程的src目录下,由Eclipse编译成class文件,在将Web工程部署到Web服务器中后放置在WEB-INF的classes目录下。

Java类应该有必定的包结构,因此在新建Servlet以前应该先在src目录下新建包,例如:cn.csai.web.servlet。而后再在包中新建Servlet,步骤以下。

(1)在工程的src中新建包。在工程中的Java Resources:src项上单击鼠标右键,在弹出菜单中选择New→Package,如图5.24所示。

图5.24  在工程的src新建包

在弹出窗口中输入待建包的名称cn.csai.web.servlet后肯定,就可完成对包的新建。

(2)在新建的包中新建Servlet。在新生成的包上单击鼠标右键,选择New→Other...,如图5.25所示。

图5.25  打开Servlet新建向导

在弹出的New对话框中选择Web目录中的Servlet,激活Servlet新建向导。

(3)Servlet新建向导如图5.26所示。

图5.26  新建Servlet向导

在图5.26中的Class name输入框中输入新建Servlet的类名,例如TestServlet,单击Next>按钮进入图5.27所示对话框。

图5.27  指定web.xml中的Servlet配置

在图5.27所示对话框中能够设置该Servlet的初始化参数和URL映射模式,能够经过按钮添加/编辑/删除初始化参数和URL映射模式。确认输入后单击Next>按钮打开如图5.28所示对话框。

图5.28  指定自动生成的初始Servlet代码

在图5-28所示的对话框中能够选择一些设置,Eclipse会根据这些设置为建立的Servlet自动添加代码。Modifiers表示生成的Servlet类是否声明为Public/Abstract/Final的;Interfaces表示生成的Servlet类所实现的接口;Constructors from superclass被选中时,生成的Servlet类的构造方法会默认调用父类的构造方法;Inherited abstract methods被选中时,生成的Servlet类会自动添加对接口或父类中的抽象方法的空白实现;init、toString、getServletInfo、doPost、doPut、doDelete、destroy、doGet被选中时,生成的Servlet类会自动生成被选中方法的空白方法体,开发人员能够添加方法的具体实现以覆盖父类中对应的方法。

单击Finish完成Servlet的新建。在Servlet被新建成功后,Eclipse会用Java编辑器打开新建的Servlet,Servlet的初始内容会根据图5.28中选择的选项生成以下:

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

 * Servlet implementation class for Servlet: TestServlet

 *

 */

 public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

   static final long serialVersionUID = 1L;

    /* (non-Java-doc)

     * @see javax.servlet.http.HttpServlet#HttpServlet()

     */

    public TestServlet() {

        super();

    }  

    /* (non-Java-doc)

     * @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws Servlet Exception, IOException {

        // TODO Auto-generated method stub

    }  

    /* (non-Java-doc)

     * @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

     */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws Servlet Exception, IOException {

        // TODO Auto-generated method stub

    }         

}

可是,在新建成功后Java编辑器会提示没法识别其中的一些类,这是由于Servlet库是J2EE中的内容,在J2SE中并不能被识别,因此在进行开发以前须要将Servlet库添加到动态Web工程的构建路径中:参考5.1.3节中关于Java Build Path的设置方法,使用Libraries标签中的Add External JARs...将库${TOMCAT_HOME}/lib/servlet-api.jar添加到工程的构建路径中。

同时,在Servlet新建好后Eclipse还自动将该Servlet配置到了该工程的web.xml文件中,配置的内容由图5.27中的设置状况决定。初始的web.xml文件内容以下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" 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 http://java.sun.com /xml/ns/j2ee/web-app_2_4.xsd">

<display-name>

DynamicWebProject</display-name>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

</web-app>

TestServlet新建完成后,web.xml文件的内容以下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4"

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 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>DynamicWebProject</display-name>

<servlet>

<description></description>

<display-name>TestServlet</display-name>

<servlet-name>TestServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.TestServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>TestServlet</servlet-name>

<url-pattern>/TestServlet</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

</web-app>

其中添加了对Servlet的配置,url-pattern对应于图5.27中的URL Mappings;因为没有配置初始化参数,因此这里也没有添加任何初始化参数。

 
 
 
 

第 5 章:使用Eclipse开发Java Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

Eclipse提供了开发Java Web应用的集成开发环境,包括Web工程的建立和管理以及Web对象的建立和编辑。

Eclipse中有两类Web工程:静态Web工程和动态Web工程,静态Web工程用于开发不包含JSP和Servlet等动态Web对象的Web应用;动态Web工程则可用于开发包含动态功能的Web应用。

除此以外,Eclipse还提供了辅助开发各类静态和动态Web对象的功能,包括:新建向导、编辑器等。提供专门支持的Web对象有HTML、JavaScript、CSS、JSP、Servlet等。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Tomcat简介

 

Tomcat是目前最流行的Servlet和JSP容器,也是Sun公司官方推荐的Servlet和JSP容器,而且Tomcat的版本也会随着Servlet和JSP版本的更新不断地升级。Tomcat是学习Java Web开发和开发Java Web应用最理想的Web服务器。

本章将从以下几个方面对Tomcat进行介绍:

Tomcat和Servlet容器的概念;

Tomcat的下载和安装;

Tomcat服务器的结构和配置;

在Eclipse中安装Tomcat插件。

随着交互式Web应用的出现,Sun公司提出了基于Java技术的动态Web技术:JSP(JavaServer Page)技术和Servlet技术。做为Java领域中动态Web的两个基础技术,JSP和Servlet在Web开发中起着愈来愈重要的做用,几乎全部基于Java的高级Web技术都是创建在JSP技术和Servlet技术基础之上的。

Tomcat是Apache Jakarta项目中的一个重要的子项目,它是Sun公司官方推荐的Servlet和JSP容器,Servlet和JSP的最新规范均可以在Tomcat的新版本中获得实现;其次,Tomcat是彻底免费的软件,任何人均可以从Tomcat的官方网站上自由地下载。所以,Tomcat愈来愈多的受到软件公司和开发人员的青睐;尤为对于学习Java Web开发的程序员来讲很是合适,由于它能够免费得到并且功能完备、有完善的文档和发展成熟的社区、并且随着Servlet和JSP规范的不断发展会不断更新版本。Tomcat版本及其所实现的Servlet/JSP规范的版本之间的关系如表6.1所示。

表6.1  Tomcat版本与Servlet/JSP版本对应关系

表6.1中列出的Tomcat版本号都是二级版本号,一般下载的具体Tomcat版本会有更加细致的小版本号,可是一般二级版本号相同的Tomcat版本所实现的Servlet规范和JSP规范版本也是相同的。

在介绍Tomcat的资料和文档中都会提到,Tomcat是Servlet/JSP容器,或者说Tomcat是实现了JSP规范的Servlet容器。因而可知,Tomcat最主要的角色是做为一种Servlet容器出现的,本节首先介绍Servlet容器的概念。

Servlet容器也叫作Servlet引擎,顾名思义它是放置Servlet的容器,它在Servlet的生命周期内包容、装载、运行、和中止Servlet;它是Web服务器或应用程序服务器的一部分,它还必须具备在外部请求和Servlet之间传递消息的功能。外部请求在到达Servlet容器时,Servlet容器经过解析请求消息将请求消息分发给目的Servlet,运行Servlet得到响应,并将响应以特定格式返回给请求端。

Servlet容器的工做模式能够分为如下三类:

1.独立的Servlet容器

将Servlet容器与基于Java技术的Web服务器集成,Servlet容器与Web服务器在同一个JVM中运行,做为独立的Web服务器运行。这种运行模式称为独立的Servlet容器模式。

2.进程内的Servlet容器

假如将Servlet容器与基于非Java技术的Web服务器一块儿使用,则经过Web服务器插件便将Servlet容器集成到Web服务器中。Web服务器插件在某个Web服务器内部地址空间中打开一个JVM,使得Servlet容器能够在此JVM中加载并运行Servlet。若有客户端调用Servlet的请求到来,那么插件将此请求经过JNI接口传递给Servlet容器,而后由Servlet容器处理该请求。

3.进程外的Servlet容器

这种模式也是经过服务器插件的形式将Servlet容器与Web服务器联系起来。在这种模式下,Web服务器将Servlet容器运行于服务器外部的JVM。Web服务器插件与Servlet容器使用IPC机制进行通讯。当访问Servlet的请求到达Web服务器时,Web服务器插件经过使用IPC消息传递给Servlet容器。因此这种方式与进程内的Servlet容器的区别就是Servlet容器与Web服务器的耦合程度不一样和Web服务器插件与Servlet容器的通讯方式不一样。

Tomcat的运行模式默认是以独立的Servlet容器模式运行,同时Tomcat也能够附加到现有服务器(例如,Apache,IIS和Netscape服务器)。但对于学习、开发和调试Web应用来讲,单纯使用Tomcat做为Web服务器就已经足够了。

 
 

Tomcat的得到很是简单,因为它是Apache基金会的开源项目,因此能够免费从Apache的官方网站上下载,而后再根据下载文件的格式进行安装。下面将对下载和安装方法进行详细的介绍。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

下载

 

Tomcat的官方主页是:http://tomcat.apache.org/,如图6.1所示。

图6.1  Tomcat官方主页

该页左边“Download”菜单下列出了几种经典的Tomcat版本,截至本书写做时Tomcat的最新版本是6.0.14。单击“Download”中的子菜单“Tomcat 6.x”,进入Tomcat的下载页面,如图6.2所示。

如图6.1所示,在打开页面的下部有6.0.14版的下载连接。下载的内容有两种发布形式:二进制数发布(Binary Distribution)和源代码发布(Source Code Distribution)。二进制数发布下载应用程序,源代码发布下载Tomcat的源代码。

图6.2  Tomcat 6.x下载页面

在二进制数发布中又提供了两种类型的内容:Core和Deployer。Core是Tomcat应用的核心内容,Deployer是供Web开发人员开发与Tomcat6自己相关的一些Web应用时发布Web应用的参考。Core中列出了三种下载的格式:zip格式、tar.gz格式和Windows Service Installer格式。Zip格式下载后获得的是一个zip文件,tar.gz格式下载后获得的是一个tar.gz文件,这两种格式下载的文件都无须安装,解压缩后便可使用,只是他们使用了不一样的压缩方式,zip文件使用ZIP压缩方式,tar.gz文件一般是在GNU操做系统(一种相似于UNIX的操做系统)中用tar命令打包而成的,所以必须在与GNU相兼容的操做系统中解包;“Windows Service Installer”格式下载后获得的是一个exe文件,在Windows中运行该文件能够将Tomcat安装到Windows系统中,而且能够选择将Tomcat安装为系统服务,这样就能够经过Windows服务来控制启动和中止。在学习本书时建议读者使用zip格式的下载方式。

【提示】

Tomcat 6要求系统至少安装JDK5或更高版本。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

安装

 

将zip文件下载到本地后选择适当的ZIP解压软件将zip文件进行解压并保存到本地文件系统,例如保存为D:\Tomcat目录,那么该目录的目录内容如图6.3所示。

bin目录下是Windows系统下和UNIX类系统下的可执行文件(批处理文件和Shell脚本文件),如启动、中止Tomcat的执行文件。在Winodws操做系统中启动Tomcat的命令是startup.bat,中止Tomcat的命令是shutdown.bat。在UNIX下启动Tomcat的命令是startup.sh,中止Tomcat的命令是shutdown.sh。

conf目录下是一些有关Tomcat服务器的配置文件和属性文件,如server.xml、web.xml、logging.properties等。

图6.3  Tomcat安装目录

lib目录下是一些库文件(jar文件或class文件)和资源文件。在Tomcat 5.5版本中Tomcat还将库文件分红三个不一样的目录:common目录用于存放供Tomcat服务器和Web应用共同使用的库文件; server目录用于存放供Tomcat服务器使用的库文件;shared目录用于存放供全部Web应用使用的库文件。在Tomcat 6.0中将这些目录去掉,只使用一个lib目录,此目录下的全部库文件均可以供Tomcat服务器和Web应用共用。

logs目录是Tomcat服务器的日志目录,Tomcat将各类与服务器相关的日志都放置在该目录下。

temp是供JVM使用的存放临时文件的目录。

webapps目录用于存放一些Tomcat中的Web应用,每一个子文件夹表示一个Web应用,该目录中的Web应用会被Tomcat自动装载。默认该目录中已自带了一些Web应用,其中ROOT应用是默认的根Web应用。

work目录是供Web应用使用的临时工做目录。

解压完还须要确保系统中已正确配置了JAVA_HOME环境变量,如此便完成了Tomcat的安装。为了之后在使用Tomcat以及Tomcat在与其余工具联合工做时不至于产生问题,在安装完Tomcat后最好将TOMCAT_HOME环境变量添加到系统中,该环境变量的值应该是Tomcat的安装根目录,在上例中应该是D:\Tomcat。

安装好Tomcat后,双击bin目录下的startup.bat即可启动Tomcat,启动后的命令行界面如图6.4所示。

图6.4  启动Tomcat

Tomcat启动成功后,在浏览器中输入地址“http://localhost:8080”,将出现Tomcat默认的欢迎页面,如图6.5所示。

图6.5  Tomcat欢迎页面

若是该欢迎页面能正确出现,说明Tomcat已正确安装。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Tomcat服务器结构

 

虽然Tomcat是开源的免费软件,可是Tomcat做为一种Servlet容器,其功能是十分强大的,并且Tomcat在结构设计上也是以一个大型商业软件为标准的。所以,理解Tomcat服务器的结构有助于读者很好地理解Tomcat的实现原理,有助于更好地理解Tomcat服务器的配置,并且若是读者有兴趣还能够在深刻理解Tomcat结构和配置的基础上扩展Tomcat的功能。本节将从比较宏观的层面介绍Tomcat的结构,对于Tomcat的具体使用和配置方法将在后面介绍。

之因此说Tomcat的结构设计是以大型商业软件为标准的,是由于Tomcat在结构上充分考虑了多域名和多主机使用,以及服务器的可配置性和可扩展性。Tomcat服务器的结构层次如图6.6所示。

图6.6中,Tomcat Server表明整个Tomcat服务器,Tomcat服务器中能够配置多个Service,一个Service表明Tomcat提供的一种服务。一个Service中能够配置若干个Connector和一个Engine,Connector是负责与外界交流的模块,它负责在指定的服务器端口上监听来自客户端的请求,当请求到达时接受请求,将请求交给处理引擎,并将处理结果返回给客户端。每一个Connector实例其实是实现一种网络传输协议,它对经过这种协议传入的客户端请求进行分析,构造相应的Request和Response实例,将Request和Response实例传递给Engine,待Engine处理结束后将处理结果经过实现的传输协议返回给客户端。Engine是整个Service中的处理机,一个Service中只有一个Engine,它处理来自各个Connector的客户端请求。Engine中又能够配置一个或多个Host,Host就是常说的“虚拟主机”。一般,一个完整的Web服务器包含一个或多个“虚拟主机”。所谓虚拟主机,就是在一个物理的服务器上配置多个域名,这样在客户端看起来好像是有多个主机。每一个虚拟主机中又能够部署一个或多个Web应用,如图6.6中的Context。Web应用中又能够配置多个Servlet。Tomcat经过分级的结构将其提供的多服务、多协议、多主机进行层层分解,最终都归结到一个一个的Servlet来执行具体的任务,这也是Tomcat被称为Servlet容器的缘由。因此,开发人员在使用Tomcat服务器时,应该根据Tomcat的这种层次结构将本身的应用进行分析和分解,将应用中的每一块配置到合适的位置上。

图6.6  Tomcat服务器结构层次图

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Tomcat基础配置

 

将6.3节介绍的Tomcat服务器的结构与前面介绍的Eclipse的全插件结构进行比较,能够发现这二者有一些相似之处:Tomcat服务器的基础结构实质上是提供了一个能够容纳Service、Context、Servlet等组件的容器,开发人员经过设置配置文件将这些组件添加到Tomcat容器中,这种思想与Eclipse的全插件结构有殊途同归之妙。可是,没有安装任何插件的Eclipse是没法进行工做的;一样,没有添加任何组件的Tomcat也是没法进行工做的。因此,Eclipse在发布时也同时安装了Workspace、JDT、PDT等基础插件;一样,Tomcat在发布时也默认配置了一些组件以完成基本的功能,这在server.xml配置文件中能够体现出来。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

server.xml配置文件

 

server.xml配置文件的初始内容以下(已删除注释):

<Server port="8005" shutdown="SHUTDOWN">

    <Listener className="org.apache.catalina.core.AprLifecycleListener" />

    <Listener className="org.apache.catalina.core.JasperListener" />

    <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />

    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

    <GlobalNamingResources>

        <Resource name="UserDatabase" auth="Container"

type="org.apache.catalina.UserDatabase"

description="User database that can be updated and saved"

factory="org.apache.catalina.users.MemoryUserDatabaseFactory"

pathname="conf/tomcat-users.xml" />

    </GlobalNamingResources>

    <Service name="Catalina">

        <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"

redirectPort="8443"/>

        <Connector port="8009" 

                           protocol="AJP/1.3" redirectPort="8443" />

        <Engine name="Catalina" defaultHost="localhost">

            <Realm className="org.apache.catalina.realm.UserDatabaseRealm"

                           resourceName="UserDatabase"/>

            <Host name="localhost" appBase="webapps"

                           unpackWARs="true" autoDeploy="true"

                           xmlValidation="false" xmlNamespaceAware="false">

            </Host>

        </Engine>

    </Service>

</Server>

从XML文件的内容能够发现,元素之间的层次结构与前面介绍的Tomcat服务器的结构大体同样。

1.Server

Server表示整个Tomcat服务器,它是整个文件的顶层元素,不能做为任何其余元素的子元素。Server元素可包含Listener元素、GlobalNamingResources元素和Services元素。Server元素的属性如表6.2所示。

表6.2  Server元素属性表

Listener元素用于提供对JMX MBeans的支持,其属性className是实现该元素的类。

GlobalNamingResources元素为Server定义全局的JNDI(Java Naming and Directory Interface,Java 命名和目录接口)资源。Resource元素配置Web应用能够使用的资源名字、数据类型以及资源须要的参数。

整个Tomcat服务器由一个Server元素表示,因此Server元素做为配置文件的根元素,而且在配置文件中不能再出现别的以Server为标签的元素。

2.Service

Service元素表示Server提供的一个服务,它由若干个Connector和一个Engine组成。Service元素的属性如表6.3所示。

表6.3  Service元素属性表

在Tomcat的初始配置中提供了一个名为Catalina的服务,该服务是随Tomcat一块儿发布的由Tomcat提供的默认服务。若是开发人员还但愿Tomcat服务器提供别的服务,能够在Server元素下添加新的Service元素,并指定其实现类。

3.Connector

Connector元素表明与客户端实际交互的链接器,它负责接收客户的请求以及向客户返回响应结果。Connector根据protocol属性可分为两种类型:HTTP Connector和AJP Connector。HTTP Connector表示支持HTTP/1.1的链接器,它使得Tomcat能够经过HTTP协议通讯,这也是使得Tomcat可以成为独立的Web服务器的关键部件。AJP Connector表示使用AJP协议通讯的链接器,它用于Tomcat与Apache服务器通讯,这样便于将Tomcat与Apache服务器集成,让Apache处理Web应用中的静态内容请求。HTTP Connector和AJP Connector都是Connector元素,根据Connector元素的protocol属性值进行区分,默认是HTTP Connector。Connector元素常见属性的含义如表6.4所示。

表6.4  Connector元素属性表

在Catalina服务中初始提供了两个Connector:一个HTTP Connector和一个AJP Connector。HTTP Connector监听8080端口,实现了标准的HTTP/1.1协议;AJP Connector监听8009端口,实现了AJP/1.3协议。在有些使用场合,这两个Connector是不够的,例若有些应用须要实现HTTPS的安全协议,那开发人员能够再增长一个Connector元素以添加支持HTTPS协议的Connector,server.xml文件的注释中提供的一个Connector以下:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" />

4.Engine

Engine元素表示在一个Service中处理全部客户请求的引擎。Engine元素的属性如表6.5所示。

表6.5  Engine元素属性表

Engine在每一个Service中只能配置一个,Catalina服务中默认配置了一个Catalina Engine。

Realm元素表示一个包含用户名、密码和角色定义的存储源,经过实现不一样的Realm能够使Tomcat适应不一样环境下不一样的验证信息获取方式,以实现Tomcat的容器访问安全。其className属性表示实现该元素的Java类,该类必须实现org.apache.catalina.Realm接口,Tomcat提供了Realm类的几个标准实现,分别表示不一样机制的Realm:org.apache.catalina.realm.JDBCRealm、org.apache.catalina.realm. DataSourceRealm、org.apache.catalina.realm.JNDIRealm和org.apache.catalina.realm.MemoryRealm。不一样的Realm实现具备不一样的属性。

5.Host

Host元素表示一个虚拟主机,由服务器的一个网络名称表示。Host元素的常见属性如表6.6所示。

 

表6.6  Host元素属性表

Catalina Engine中默认配置了一个Host:localhost,这个Host表示本地主机,在Web应用的开发和调试阶段使用这个Host部署Web应用已经足够了。

将server.xml文件的初始内容结合Tomcat的结构,能够获得Tomcat初始状态下所配置的组件,如图6.7所示。

图6.7  Tomcat初始配置的组件结构

初始状态下,Tomcat只提供一个服务,其中包括两个Connector和一个Host。对于学习Web开发的读者来讲,能够将全部待开发的Web应用都部署到localhost中,而后用URL前缀http://localhost:8080(8080是HTTP Connector监听的端口号)访问Web应用进行调试。关于如何在Tomcat中部署、设置和访问Web应用将在第7章中介绍。

server.xml文件是用于配置Tomcat服务器的最主要的配置文件,Tomcat提供了不少可供配置的配置项以提供极大的灵活性,可是对于使用Tomcat进行学习和简单开发来讲只须要涉及几个简单的配置项,其中最经常使用的多是HTTP Connector的port属性。它表示Catalina服务经过HTTP协议提供服务的端口号,默认是8080,这就表示经过HTTP协议访问Catalina服务时必须使用服务器的8080端口,例如访问localhost中配置的Web应用时URL前缀是http://localhost:8080/;固然该端口号也能够更改成其余合法端口号,若是更改成HTTP协议的默认端口号80,那么在访问Catalina服务时URL前缀中就能够不用加上端口号,即http://localhost。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Tomcat其余配置文件

 

除了server.xml文件,Tomcat还有其余一些配置文件。Tomcat的配置文件都在${TOMCAT_HOME}/conf目录下,1.6版的Tomcat初始安装后,该目录中除了server.xml还有以下几个。

web.xml:在Tomcat中每一个Web应用都拥有一个对Web应用进行配置的web.xml文件,因为全部Web应用的配置中有不少是相同的,因此Tomcat在这里提供了一个配置文件,用于配置全部Web应用共用的设置,使每一个Web应用只须要关注本身应用特有的配置;在实际运行期间Tomcat会将共用配置文件和特有配置文件合并做用于Web应用。web.xml文件就是做为全部Web应用共用的配置文件,修改该文件中的配置会对全部Web应用起做用。web.xml文件的结构和内容将在后续章节中详细介绍。

tomcat-users.xml:该文件对登陆Tomcat后台管理的用户作定义,包括角色和用户名/密码,用于登陆Tomcat的管理界面。

Catalina文件夹:该文件夹包含用于配置Catalina服务的文件。

catalina.policy:Catalina服务的策略配置文件,其中主要说明一些安全访问的策略。

catalina.properties:Catalina服务的属性配置文件,其中主要说明该Service的一些属性。例如,common.loader、server.loader和shared.loader分别定义了三类库(服务器和Web应用共用、仅供服务器使用和仅供Web应用共用)的装载策略,即装载哪些文件添加到库中,默认server.loader和shared.loader没有配置任何值。经过修改这几个属性的值能够自定义分别将哪些文件夹中的哪些文件装载到这三类库中。对比6.2.2节中对安装目录下lib目录的介绍,正是由于这里将common.loader的值指向lib目录才使得lib目录中的库能够同时被Tomcat和Web应用使用。

context.xml:用于配置Web应用,该内容将被添加到每一个Web应用的Context配置中。

logging.properties:Tomcat服务器日志功能属性文件,定义了每一种日志的级别、存放目录、日志文件名前缀、使用的日志处理器等属性。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

在Eclipse中安装Tomcat插件

 

这里所谓的Tomcat插件是一种容许在Eclipse中启动、关闭Tomcat服务器以及在Eclipse中调试Servlet的Eclipse插件。该插件不能替代Tomcat服务器。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

下载和安装

 

该插件能够从Eclipse的官方网站上免费下载,官方网站的地址是:http://www.eclipsetotale.com/,主页打卡如图6.8所示。

图6.8  eclipsetotale主页

点击Download Tomcat Plugin按钮进入下载页面,在下载页面的下部有最新版本的下载连接,如图6.9所示。

图6.9  Tomcat插件下载页面

每一个版本的Comment栏说明该版本的插件适用于哪些版本的Eclipse,这里选择适合Eclipse3.3的最新版3.2.1,点击File栏的连接下载文件。下载获得文件tomcatPluginV321.zip,对该文件解压缩,解压后得到文件夹com.sysdeo.eclipse.tomcat_3.2.1,该文件夹即为Tomcat插件根目录。

考虑到在开发Web应用时须要常常在Eclipse中操做Tomcat,因此Tomcat插件不该该被频繁地加载和卸载,而应该将Tomcat插件做为Eclipse一个较稳定的功能。所以,这里直接使用前面介绍的第二种插件安装方式,将插件根目录直接复制到Eclipse根目录下的plugins目录中以完成插件的安装。安装完成后从新打开Eclipse,便可在Eclipse工具栏上出现Tomcat的三个图标,如图6.10所示。

图6.10  安装了Tomcat插件后的Eclipse

图6.10中方框包围的三个图标,从左向右依次为:启动Tomcat、关闭Tomcat和重启Tomcat的按钮。经过这三个按钮就能够在Eclipse中直接操做Tomcat。

可是,将插件安装到Eclipse中,在对Tomcat插件进行正确配置以前这几个按钮是不能正常工做的。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

配置Tomcat插件

 

该插件安装完成后,在Eclipse的Preferences配置对话框中就会出现对Tomcat插件的配置,如图6.11所示。

图6.11  配置Tomcat插件

在使用Tomcat前至少须要在该配置页指定Tomcat的版本系列和Tomcat home,即Tomcat的安装根目录。在前面介绍Tomcat的安装时安装的是Tomcat 6.0而且将Tomcat安装到D:\Tomcat目录中,因此此处在选择Tomcat version时应该选择Version 6.x,Tomcat home应该选择D:\Tomcat目录。完成这两项配置后,Tomcat插件就能够正常使用了。

 
 
 
 

第 6 章:Tomcat基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

本章主要介绍了Tomcat的下载、安装、结构、配置以及在Eclipse中安装Tomcat插件的方法。

Tomcat是Sun公司官方推荐的Servlet/JSP容器。其最新发布版本能够从官方主页直接免费下载,读者可下载zip文件并将其解压到系统本地目录中,再配置相关系统环境变量后就能够正常使用了。

Tomcat服务器是一种层次结构,最顶层是Server,逐级向下依次为Service→Engine→Host→Context→Servlet。Service表明Server提供的一种服务,一个Server中能够包含若干个Service;Engine是Service中的处理引擎,一个Service中只能包含一个Engine;Host表示一个虚拟主机,一个Engine能够包含若干个Host;Context表示Web应用;Servlet表示Web应用中部署的Servlet。Tomcat服务器的配置文件是${TOMCAT_HOME}/conf/server.xml文件,与Tomcat服务器的层次结构对应,该文件中元素的结构也按照从Server到Servlet的包含关系。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Web应用简介

 

Web应用是指可以经过Web提供一系列功能的应用系统。若是脱离了Eclipse和Tomcat等开发工具和Web服务器,一个Web应用就是具备特定目录结构的文件和目录。不一样Web服务器中的Web应用具备不一样的目录结构。Tomcat中的Web应用也具备特定的文件结构,而且每一个Web应用都包含一个配置文件。本章将介绍Tomcat中Web应用的结构、如何将Web应用部署到Tomcat中以及如何配置Web应用。

基于Java技术开发的典型Web应用中一般会存在以下几类Web对象:

静态文件对象:包括HTML页面、图片、普通文件等;

Servlet:依据Servlet规范实现的Java类,能够以编译后的class文件出现,也能够以包含class文件的jar文件出现;

JSP文件:符合JSP规范的动态页面。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Web应用的结构

 

实质上一个Web应用一般就是文件系统中的一个目录,称为Web应用根目录。Web应用根目录中的文件是该Web应用中的资源,包括:须要经过Web提供给客户端访问的资源以及Web应用自己的配置和描述文件等。不一样的Web服务器对Web应用根目录中文件的结构和意义有不一样的规定,只有结构符合规定的Web应用部署到Web服务器中后才能得到预期的效果。典型的Tomcat Web应用具备以下图7.1所示的目录结构。

图7.1  Tomcat Web应用目录结构示例

该Web应用的根目录是WebTest,一般称该Web应用为WebTest应用。Web应用的全部资源和配置文件都应该放置在Web应用的根目录中,也只有Web应用根目录中的资源才可以经过该Web应用访问。

全部的静态Web对象和JSP文件能够按任意的目录层次放置在Web应用根目录下,在将Web应用部署到Tomcat中后这些文件均可以根据其目录结构经过URL直接访问;WEB-INF目录是一个特殊的子目录,它存在的目的不是为了能让客户端直接访问其中的文件,而是经过间接的方式支持Web应用的运行,好比提供Web应用须要访问的资源文件、放置Web应用的属性文件或者配置文件等。WEB-INF目录必须位于Web应用根目录下,一般该文件夹中包含lib子目录,classes子目录和web.xml文件。其中,lib目录用于放置该Web应用使用的库文件,classes目录用于放置该Web应用使用的class文件(按包结构组织),web.xml是Web应用描述符,用于设置Web应用特有的配置。WEB-INF目录中的文件是不能经过URL直接访问的。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Web应用的上下文路径

 

Web应用在文件系统中存储时表现为一个目录,在文件系统中能够使用不一样的路径用于区分目录。当将Web应用部署到Tomcat中时,Web应用就是一个抽象的概念,并且Tomcat中能够部署不少的Web应用,那Tomcat如何区分每一个Web应用呢?答案是使用Web应用的上下文路径(Context Path)区分。

Web应用的上下文路径是一个字符串,在Tomcat中与Host名一块儿用于惟一肯定Tomcat中的一个Web应用。在将Web应用部署到Tomcat中时必须为Web应用指定一个上下文路径,而且在同一个Host中每一个Web应用的上下文路径必须惟一。例如,localhost中部署了2个Web应用,它们的上下文路径分别是:app1和app2。访问上下文路径为app1的Web应用时使用的URL前缀为:http://localhost:8080/app1;访问上下文路径为app2的Web应用时使用的URL前缀为:http://localhost: 8080/app2。

反过来,Tomcat也能够利用上下文路径根据客户端请求URL的前缀将客户端请求分发到适当的Web应用。例如,请求URL的前缀为http://localhost:8080/app1的客户端请求被分发到第一个Web应用;请求URL的前缀为http://localhost:8080/app2的客户端请求被分发到第二个Web应用。

【注意】

上下文路径与Web应用的根目录名称是两个概念,对于同一个Web应用而言,这两个值未必是同样的。在将Web应用部署到Tomcat中时能够为Web应用设置不一样于Web应用根目录的上下文路径。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

将Web应用部署到Tomcat中

 

所谓将Web应用部署到Tomcat中,就是让Tomcat知道Web应用的存在,在Tomcat启动时加载和初始化Web应用,在Tomcat启动后,客户端能够使用适当的URL经过Tomcat访问到Web应用,而且Web应用可以按照预期正确地工做。

在本地文件系统中创建一个Web应用,若是不将其部署到Tomcat中,那么该Web应用是没法被访问到的。在Web应用建立好后必须将其部署到Tomcat中才能经过Tomcat访问到Web应用。将Web应用部署到Tomcat中的方法有两种:复制Web应用到Webapps目录下和使用Context元素。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

将Web应用复制到webapps目录下

 

在前面介绍server.xml配置时讲过,文件中的Host元素有一个属性是appBase,localhost的该属性值是webapps。这个属性的值是一个本地目录路径,能够是绝对路径也能够是相对路径,相对路径的基准路径是${TOMCAT_HOME},这个路径所指向的目录即为该Host的应用程序根目录。以localhost为例,server.xml中localhost appBase的值是webapps,因此localhost的应用程序根目录是${TOMCAT_ HOME}\webapps。

Tomcat在启动时会自动将应用程序根路径中的每个子目录做为一个Web应用装载到所对应的Host中。因此对于localhost来讲,Tomcat会自动将${TOMCAT_HOME}\webapps中全部的子目录自动加载做为localhost的Web应用。

因此,将Web应用部署到Tomcat中最简单的方法就是将Web应用的根目录复制到应用程序根目录中。按照这种方式部署的Web应用,其上下文路径与Web应用根目录的目录名一致。

这种部署方式的优势是简单易行并且不容易出错,缺点是当部署到Tomcat中的Web应用愈来愈多时,会使Tomcat的安装目录变得很庞大,并且这种方式要求全部的Web应用都放置在同一个目录中,不利于自由灵活地组织Web应用。
       ① ${...}表示取出某环境变量的值。在安装Tomcat完成时已经将TOMCAT_HOME添加到环境变量中,因此这里${TOMCAT_HOME}就表示取TOMCAT_HOME环境变量的值,也就是安装Tomcat的根目录。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

使用Context元素

 

在第6章介绍Tomcat服务器结构时提到:Server表示Tomcat服务器、Service表示Tomcat提供的一个服务、Engine表示Service中的处理引擎、Host表示一个虚拟主机,Context表示Host中的一个Web应用。因此,读者很容易就能够想到能够利用Context元素将Web应用部署到Tomcat中。

Context元素表示一个运行于特定虚拟主机中的Web应用,每一个Web应用能够是一个war(Web Application Archive)文件或者文件系统中的一个文件夹。理论上在一个Web服务器中能够定义任意多个Web应用,每一个Web应用必须有一个惟一的上下文路径(Context Path)。若是某个Web应用的上下文路径是空串的话,该Web应用就成为其所在虚拟主机的默认Web应用,全部请求URL没有与虚拟主机中其余任何一个Web应用的上下文路径匹配的客户请求都会被分发到该Web应用。

Context元素常见的属性如表7.1所示。

表7.1  Context元素属性表

从Context元素的属性设置能够很清楚地发现,Web应用的根目录经过docBase指定,Web应用的上下文路径经过path属性指定,这两个属性是彻底不一样的。固然若是读者愿意,也能够将上下文路径设置成与Web应用的根目录一致,只要保证同一个Host中的Web应用上下文路径惟一就能够了。

以下是一个典型的Context元素的例子:

<Context docBase=”D:\\WebAppBase\application” path=”/app1” 

          workDir=”D:\\WebAppBase\application\work”/> 

其中,D:\WebAppBase\application是Web应用的根目录,D:\WebAppBase\app1\work是Web应用的工做目录,/app1是该Web应用的上下文路径。

将Context元素配置到Tomcat服务器中的方法有以下几种:

1.在server.xml中添加Context元素。

由于server.xml是对整个Tomcat服务器的配置,Web应用隶属于虚拟主机,因此按照这种包含关系直接将Context元素添加到Host元素下。以下所示:

<Host name="localhost" appBase="webapps"

    unpackWARs="true" autoDeploy="true"

    xmlValidation="false" xmlNamespaceAware="false">

   <Context docBase="D:\\WebAppBase\application" path="/app1" workDir="D:\\WebAppBase\application\work"/>

</Host>

这种方式最简单直观,可是不利于维护,由于一个Tomcat服务器中可能会部署不少Web应用,每一个Web应用必须配置一个Context元素,当server.xml文件中的Context元素不少之后会使得server.xml很大、很难阅读和维护。

2.添加到${TOMCAT_HOME}\conf\context.xml文件中

Tomcat提供了一个独立的文件conf/context.xml用于配置全部Web应用的Context元素。程序员能够将Context元素添加到该文件中,以下所示:

<Context>

<!-- Default set of monitored resources-->

<WatchedResource>WEB-INF/web.xml</WatchedResource>

<!--Uncomment this to disable session persistence across Tomcat restarts -->

<!--

<Nanager pathname="" />

-->

<!--  Uncomment this to enable Comet connection tacking (provides events

on sesslon expiracion as well as webapp lirecycle) -->

<!--

<Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />

-->

</Context>

<Context docBase="D:\\WebAppBase\application" path="/app1" workDir="D:\\WebAppBase\application\work"/>

这种方式有利于将全部Web应用的Context配置从server.xml配置文件中独立出来,可是这种方式是将全部Web应用的配置放在一个配置文件中,当Web应用多了之后就不利于修改和维护。

3.为每一个虚拟主机的全部Web应用使用一个独立的配置文件

${CATALINA_HOME}/conf/[enginename]/[hostname]/context.xml.default文件能够配置特定主机中全部Web应用的Context元素。其中,[enginename]是指主机所在的引擎的名称,即Engine元素name属性的值;[hostname]表示该主机的主机名,即Host元素name属性的值。例如,在localhost主机中添加三个Web应用后,${CATALINA_HOME}/conf/Catalina/localhost/context.xml.default文件内容以下:

<Context docBase="D:\\WebAppBase\application1" path="/app1" workDir="D:\\WebAppBase\application1\work"/>

<Context docBase="D:\\WebAppBase\application2" path="/app2" workDir="D:\\WebAppBase\application2\work"/>

<Context docBase="D:\\WebAppBase\application3" path="/app3" workDir="D:\\WebAppBase\application3\work"/>

4.为每一个Web应用使用独立的配置文件

在${CATALINA_HOME}/conf/[enginename]/[hostname]目录中也能够经过使用xml文件来为每一个Web应用定义Context,文件名为Web应用的上下文路径。例如在${CATALINA_HOME} /conf/Catalina/localhost/目录中新建文件app1.xml,而且添加以下内容也能够将application应用部署到localhost中:

<Context docBase="D:\\WebAppBase\application" path="/app1" workDir="D:\\WebAppBase\application\work"\>

 
 

将Web应用部署到Tomcat中后,从理论上说Web应用就已经能够经过Tomcat正常访问了。可是为了可以灵活地配置Web应用以及向Web应用中添加更加丰富的内容(例如Servlet),读者还应该了解Web应用部署描述符web.xml。

在第6章介绍Tomcat的配置时已经提到了,在Tomcat中每一个Web应用都拥有一个对Web应用进行配置的web.xml文件,它位于Web应用根目录的WEB-INF目录下。因为Web应用的许多配置在各个Web应用之间是通用的,因此Tomcat使用${TOMCAT_HOME}\conf\web.xml文件(称通用部署描述符)来配置通用部分,各个Web应用将本身Web应用特有的配置内容放置在Web应用根目录下的WEB-INF\web.xml文件(称特有部署描述符)中。每一个Web应用经过将通用部署描述符和特有部署描述符中的配置项合并起来进行配置,假如通用部署描述符和特有部署描述符中的某些配置项有冲突,则特有部署描述符中的配置项优先。

无论是通用部署描述符仍是特有部署描述符,它们都是一个XML文件,都遵循Web应用部署描述符的结构。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Web应用部署描述符

 

web-app元素是Web应用部署描述符的根元素,它包含了若干个子元素,每一个子元素对应于Web应用一个方面的配置,这些子元素的顺序是任意的。根据Web应用部署描述符2.5版本的XML Schema定义,web-app元素全部的属性和子元素如图7.2所示。

web-app元素包含三个主要属性:version、id和metadata-complete,其中最经常使用的是version,它表示Web描述文件兼容的最高版本。每一个版本的Tomcat都有其兼容的Servlet标准版本号,例如Tomcat 6.x最高兼容Servlet 2.5,Tomcat 5.5最高兼容Servlet 2.4。每一个Web应用也有其使用的版本号,该版本号与Servlet版本号一致,高版本的Web应用不能在只兼容低版本的Tomcat中使用,例如web-app元素version属性为2.5的Web应用不能应用于Tomcat 5.5中。

图7.2  Web部署描述符2.5版本Schema定义

web-app元素中能够定义许多元素,每一种元素对应一个Web应用的一项配置。其中最经常使用的有如下五种。

1.welcome-file-list:该元素定义了一个welcome文件列表,例如:

<welcome-file-list>

    <welcome-file>index.html</welcome-file>

    <welcome-file>index.htm</welcome-file>

    <welcome-file>index.jsp</welcome-file>

</welcome-file-list>

welcome文件是目录的默认访问文件,即当请求URL指向的是一个目录而不是一个文件时,目录中的默认访问文件就会成为目标访问文件。在welcome文件列表中的文件是有序的,排在前面的文件将被优先返回,当前面的文件在目录中不存在时才会依次寻找排在后面的文件。例如,在welcome-file-list为上面所示配置时,假如在上下文路径为test的Web应用的根目录中有index.html和index.jsp两个文件,那么请求URL为http://localhost:8080/test的请求至关于请求URL为http://localhost:8080/test/index. html的请求。

2.servlet和servlet-mapping:这两个元素用于向Web应用中添加Servlet。servlet元素用于定义Servlet的名称、实现类等属性,servlet-mapping用于定义Servlet的路径映射方式。具体的实现和配置方式将在后面介绍Servlet技术时详细介绍。

3.filter和filter-mapping:这两个元素用于向Web应用中添加过滤器。filter用于定义过滤器的名称、实现类等属性,filter-mapping用于定义filter的路径映射方式。具体的实现和配置方式将在介绍Servlet过滤器技术时详细介绍。

4.mime-mapping:该元素用于定义在Web应用中,如何根据文件名后缀映射文件的mime类型。例如:

<mime-mapping>

        <extension>htm</extension>

        <mime-type>text/html</mime-type>

</mime-mapping>

表示将全部后缀名为htm的文件映射为text/html类型。

【注意】

MIME是Multipurpose Internet Mail Extensions(多用途Internet邮件扩展)的简称,最初做为电子邮件非文本格式附件传输方式被提出和使用,如今这种方式被普遍应用于使用HTTP协议传输的各类应用中。MIME类型由内容类型(大类型)和子类型(小类型)组成,被用于判断二进制数文件的内容和打开方式。

5.session-config:用于配置session的一些参数,例如session的超时时间:

<session-config>

       <session-timeout>30</session-timeout>

</session-config>

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

默认的通用Web应用部署描述符

 

Tomcat默认的通用Web应用部署描述符的初始内容以下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_ 5.xsd"

    version="2.5">

<servlet>

        <servlet-name>default</servlet-name>

        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>

        <init-param>

            <param-name>debug</param-name>

            <param-value>0</param-value>

        </init-param>

        <init-param>

            <param-name>listings</param-name>

            <param-value>false</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

</servlet>

<servlet>

        <servlet-name>jsp</servlet-name>

        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>

        <init-param>

            <param-name>fork</param-name>

            <param-value>false</param-value>

        </init-param>

        <init-param>

            <param-name>xpoweredBy</param-name>

            <param-value>false</param-value>

        </init-param>

        <load-on-startup>3</load-on-startup>

    </servlet>

<servlet-mapping>

        <servlet-name>default</servlet-name>

        <url-pattern>/</url-pattern>

    </servlet-mapping>

    <servlet-mapping>

        <servlet-name>jsp</servlet-name>

        <url-pattern>*.jsp</url-pattern>

    </servlet-mapping>

    <servlet-mapping>

        <servlet-name>jsp</servlet-name>

        <url-pattern>*.jspx</url-pattern>

    </servlet-mapping>

    <session-config>

        <session-timeout>30</session-timeout>

    </session-config>

<mime-mapping>

        <extension>abs</extension>

        <mime-type>audio/x-mpeg</mime-type>

    </mime-mapping>

    <mime-mapping>

        <extension>ai</extension>

        <mime-type>application/postscript</mime-type>

    </mime-mapping>

    <mime-mapping>

        <extension>aif</extension>

        <mime-type>audio/x-aiff</mime-type>

    </mime-mapping>

    ...

    <mime-mapping>

        <extension>doc</extension>

        <mime-type>application/vnd.ms-word</mime-type>

    </mime-mapping>

    <mime-mapping>

        <extension>ppt</extension>

        <mime-type>application/vnd.ms-powerpoint</mime-type>

    </mime-mapping>

    <welcome-file-list>

        <welcome-file>index.html</welcome-file>

        <welcome-file>index.htm</welcome-file>

        <welcome-file>index.jsp</welcome-file>

    </welcome-file-list>

</web-app>

能够发现,该默认的部署描述符中定义了两个Servlet(名称分别为default和jsp)、一个session-config、若干个mime-mapping和一个welcome-file-list。

1.default Servlet

这里配置的default Servlet是使用于全部Web应用的默认Servlet,即假如在Host中没有定义Servlet或者到达的请求没法根据匹配规则分发到任何一个Servlet时,请求就会被分发到default Servlet。部署描述符中的以下部分对default Servlet进行了设置:

<servlet>

<servlet-name>default</servlet-name>

<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>

<init-param>

            <param-name>debug</param-name>

           <param-value>0</param-value>

        </init-param>

        <init-param>

            <param-name>listings</param-name>

            <param-value>false</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

</servlet>

...

<servlet-mapping>

        <servlet-name>default</servlet-name>

        <url-pattern>/</url-pattern>

</servlet-mapping>

servlet-name定义了这个Servlet的名称,为default,用于标识这个Servlet;servlet-class是该Servlet使用的Java类,这里配置的是Tomcat提供的默认default Servlet的实现,正是这个Servlet实现了对静态Web对象的访问,使Tomcat可以支持静态Web对象访问;init-param定义了一些参数,这些参数在org.apache.catalina.servlets.DefaultServlet中使用;load-on-startup定义了一个整数值,当该值是0或正整数时,Tomcat在启动时必须加载和初始化该Servlet,并且保证该值越小的Servlet越早被加载并初始化;当该值是负数或该配置项不存在时Tomcat能够选择不在启动时加载和初始化Servlet。

url-pattern定义了一个请求URI模板,请求URI与该模板相匹配的请求将被分发到对应的Servlet。default Servlet的url-pattern是/,它能够与任意URI匹配,因此当没有其余Servlet的url-pattern更合适时,请求就会被分发到default Servlet。

2.jsp Servlet

jsp Servlet是Tomcat提供的用于处理全部JSP文件的Servlet,这也是Tomcat能正确处理JSP文件请求的缘由。部署描述符中对于jsp Servlet的配置以下:

<servlet>

<servlet-name>jsp</servlet-name>

<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>

<init-param>

<param-name>fork</param-name>

<param-value>false</param-value>

</init-param>

<init-param>

<param-name>xpoweredBy</param-name>

<param-value>false</param-value>

</init-param>

<load-on-startup>3</load-on-startup>

</servlet>

...

<servlet-mapping>

<servlet-name>jsp</servlet-name>

<url-pattern>*.jsp</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>jsp</servlet-name>

<url-pattern>*.jspx</url-pattern>

</servlet-mapping>

在Servlet配置的结构上,jsp Servlet与default Servlet一致,其含义也相同。这里须要强调的是url-pattern,有两个servlet-mapping中定义的两个url-pattern都对应jsp Servlet,这在web.xml中是容许的。这两个servlet-mapping产生的效果就是全部访问*.jsp和*.jspx的文件都被分发到jsp Servlet进行处理,也就是说,Tomcat中的JSP文件支持*.jsp和*.jspx两种格式。

3.session-config

session-config用于配置在Web应用中与session有关的设置,部署描述符中对于session-config的配置以下:

<session-config>

<session-timeout>30</session-timeout>

</session-config>

session-timeout用于设置session的超时时间,这里设置为30分钟。

4.mime-mapping

在部署描述符中预设了不少mime-mapping,用于将特定的文件后缀与一种MIME类型对应起来。开发人员能够经过在这里设置映射关系将Web应用中的某些文件映射为特定的MIME类型,以便客户端在接收到此类文件时能够使用适当的应用程序打开。部署描述符中对于mime-mapping的设置以下例所示:

<mime-mapping>

<extension>htm</extension>

<mime-type>text/html</mime-type>

</mime-mapping>

extension表示这种文件的后缀,mime-type表示将这种文件映射为MIME类型,该例将htm文件映射为text/html类型。

5.welcome-file-list

welcome-file-list定义了欢迎文件列表,即当请求URI指向一个目录时,那么就在目录中依次寻找欢迎文件列表中的文件,将找到的第一个返回。部署描述符中默认定义了三个欢迎文件:index.html、index.htm和index.jsp。

开发人员能够使用部署描述符对Web应用的许多方面进行配置,若是开发人员但愿修改的配置对全部Web应用都有效,例如Session的超时时间,那么就能够直接在通用Web应用部署描述符中进行设置;若是开发人员只但愿对某一个Web应用有效,例如某一个Servlet,那么就在该Web应用的特有部署描述符中进行设置。这样也简化了Web应用的配置。

 
 
 
 

第 7 章:Tomcat中的Web应用做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

本章小结

 

Tomcat中能够同时部署多个Web应用,Tomcat经过Web应用的上下文路径区分各个Web应用。将Web应用部署到Tomcat中的方法有两类:将Web应用根目录复制到webapps目录中或经过在Tomcat配置文件中添加Context元素。在Tomcat中每一个Web应用均可以使用一个web.xml文件对Web应用进行配置。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet简介

 

Servlet是Java Web开发最重要的基础技术,绝大多数Java Web开发技术都是基于Servlet基础的,因此了解Servlet技术是深刻理解其余Java Web开发技术的前提。同时Servlet也是Tomcat支持的最主要技术之一。

本章将简单介绍Servlet的基本概念、原理以及使用方法。重点介绍其中主要的一些接口和对象,包括使用普遍的ServletRequest和ServletResponse,以及Servlet中的高级概念——Servlet过滤器。最后经过大量实例应用为读者分别介绍Servlet开发、ServletConfig的使用、ServletContext的使用、HttpSession的使用、Cookie的使用以及如何在应用中使用Servlet Filter。

Servlet是Java Web开发技术中最主要和最基础的技术,但愿初学者可以认真学习本章的内容,而且根据所学的内容多作一些实验。

Servlet是一种能够配置进Servlet容器(如Tomcat)中用于处理客户端请求的特殊Java对象。Servlet Specification(Servlet规范)规定了Servlet对象和Servlet容器的协做方式,以及Servlet体系中相关的API;其中最关键的是Servlet接口,它规定了一个Servlet应该具备的行为;开发人员开发出符合Servlet接口的Java对象,并将其部署到Servlet容器中就能够使Servlet容器具备该Servlet实现的功能。Servlet经过配置Servlet容器被部署到Servlet容器中,多种多样的Servlet为Servlet容器添加了丰富的Web处理功能;同时也丰富了与Servlet容器相结合的Web服务器的功能。Tomcat是具备普通Web服务器功能的最典型的Servlet容器,经过配置Tomcat的配置文件能够将Servlet部署到Tomcat中。本章就将Tomcat做为默认的Servlet容器进行讲解和实验。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet的概念

 

一个Servlet就是一个Java对象,通常来说它与其余Java对象没有本质的区别,惟一特殊的是,它的实现类必须实现Servlet体系中的javax.servlet.Servlet接口,该接口规定了程序员实现的Servlet必须知足的一种标准格式,只有知足该格式的Servlet才能被部署到 Servlet容器中。举一个形象的例子,国家规定两孔圆头插座的规格标准是227IEC42(RVB)2×0.5mm2 6A250V标准,全部厂商生产的两孔圆头插座必须符合该标准,全部生产两孔圆头插头的厂商也必须符合该标准,不然生产出来的插头和插座就没法匹配。

一样道理,Servlet容器与Servlet之间的关系也至关于插座和插头的关系,Servlet规范规定了全部的Servlet必须符合javax.servlet.Servlet接口规范,全部的Servlet容器必须使用该规范规定的格式调用Servlet,因此程序员编写的Servlet也必须符合该规范。这样,编写的Servlet被部署到Servlet容器中后Servlet容器才可以与Servlet协调工做。

插头标准可能规定了插头的大小、电流、电压等参数,Servlet接口标准则规定了Servlet类必须实现的方法。Servlet接口规定的一个最主要的方法就是Servlet的执行方法,service()方法,该方法是一个Servlet用于处理请求和响应的所有代码。任何一个实现了Servlet接口的Java类都必须实现该方法,因此Servlet容器不须要知道部署到其中的每一个Servlet的具体实现,当有请求到达时,Servlet容器只须要调用该Servlet类的service()方法便可。

或者也能够反过来讲,一个实现了javax.servlet.Servlet接口的Java类的对象就是一个Servlet。因此实现javax.servlet.Servlet接口与一个Java类是一个Servlet的充分必要条件。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet的生命周期

 

程序员在开发Servlet时,首先开发一个实现了Servlet接口的Java类,暂称其为Servlet类;而后将该Servlet类部署到Tomcat中,此时Servlet仍是以Java类的形式存在,并不具备任何实例;启动Tomcat,Tomcat实例化一个该Servlet的对象而且对其进行初始化;当有客户请求指向该Servlet时,Tomcat调用该Servlet对象的执行方法对请求和响应进行处理;处理完后销毁该Servlet对象。因此一个Servlet对象的生命周期包括三个阶段:初始化阶段、执行阶段和销毁阶段。如图8.1所示。

图8.1  Servlet工做过程示意图

如图8.1所示,Servlet在被初始化后才会进入Tomcat的地址空间中响应客户端的请求,在服务结束后在适当的时机Tomcat会销毁Servlet,将Servlet从地址空间中清除。

Tomcat初始化Servlet包括两个动做:将Servlet实例化并加载到Tomcat地址空间中;调用Servlet的初始化方法对Servlet进行初始化。

【注意】

实例化和初始化是两个不一样的概念。实例化是指根据Java类生成一个该类的对象,在实例化时会调用Java对象的构造方法,任意一个非抽象的Java类均可以被实例化;初始化是一个特殊的概念,并非全部Java类都有初始化的概念,Servlet的初始化是指在从一个Servlet类实例化一个Servlet对象后再调用该Servlet对象的初始化方法对其进行初始化。可见初始化的前提必须是先实例化。因此这里提到的Tomcat对Servlet的初始化默认就包括了从Servlet类实例化一个Servlet对象。

Tomcat初始化Servlet的时机可能有三个:

启动Tomcat时:在前面介绍web.xml文件配置时,介绍了在配置Servlet时有一个load-on-startup子标签,该标签的值是一个整数值。若是某个Servlet的该标签值是0或正整数,那么该Servlet就会在Tomcat启动时被初始化,并且值越小初始化得越早;若是某个Servlet没有配置该标签或者该标签的值为负数,那么该Servlet在Tomcat启动时就不会被初始化。因此,对于全部load-on-startup子标签值为正整数的Servlet都会在Tomcat启动时被初始化。

有请求访问Servlet时:如上所述,全部load-on-startup子标签值没有配置为正整数的Servlet都不会在Tomcat启动时被初始化;它们采用的是Lazy初始化机制,就是只有当Servlet须要被使用时才初始化,也就是有请求访问Servlet时才初始化。

在Servlet所在Web应用的/WEB-INF/classes或/WEB-INF/lib目录发生变化时:在前面介绍的使用Context元素将Web应用部署到Tomcat中的方法时,介绍了Context元素有一个属性reloadable,当将该属性设置为true时,Tomcat就会监控该Web应用的/WEB-INF/classes 和 /WEB-INF/lib目录的变化,若是这两个目录发生了变化,Tomcat就会从新载入该Web应用,这时该Web应用中的全部Servlet就会被从新初始化。

Servlet被销毁的时机由Tomcat指定,Tomcat并不会在Servlet响应完请求后就当即销毁Servlet,而是选择一个适当的时机销毁Servlet,销毁Servlet时Tomcat会调用Servlet的销毁方法。一般Tomcat销毁一个Servlet的时机可能有以下几种状况:

Servlet已处理完全部请求,而且长期处在闲置状态;

Servlet已处理完全部请求,而且当前Tomcat的空间资源相对紧张,须要销毁一些Servlet释放空间;

Servlet已处理完全部请求,而且当前Servlet须要被重启。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet的工做过程

 

根据前面对Servlet生命周期的介绍,Servlet真正用于处理客户端请求的阶段是Servlet的执行阶段。Servlet采用Request/Response模式进行工做。如图8.1所示,在Servlet被初始化前直处处理完请求被销毁,其执行过程以下(假设Servlet在Tomcat启动时不被初始化)。

(1)在有指向Servlet的请求到达Tomcat时,Tomcat对Servlet进行实例化,而且调用Servlet的init()方法对Servlet对象进行初始化。

(2)Tomcat将客户端的请求构形成一个Request对象,同时构造一个输出指向客户端的Response对象,将Request对象和Response对象同时做为参数传递给Servlet的service()方法,并执行该方法。

(3)Servlet的service()方法解析Request对象,执行相应的操做,而且根据执行结果设置Response对象。

(4)Tomcat根据Servlet设置的Response对象,构造相应的响应消息返回给客户端。

(5)Servlet完成对请求的处理后,在适当的时候Tomcat会调用Servlet的destroy()方法将Servlet销毁。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

请求的分发

 

Tomcat中能够配置若干个主机,一个主机能够配置若干个Web应用,一个Web应用中又能够配置若干个Servlet。那当有请求到达Tomcat时,Tomcat如何判断请求所要访问的Servlet呢?每一个请求都有其所访问资源的URL,URL是全局资源定位符,用于说明请求所访问资源的位置,并且一个URL在同一时刻所指向的网络资源位置也是惟一的。Tomcat根据请求的URL肯定请求所访问的Servlet。

根据Tomcat的层次结构:Server → Service → Host → Context → Servlet,请求在到达Tomcat后首先根据请求的主机名(域名)被分发到某个Host;再根据请求URI以及Host中各Context(Web应用)的上下文路径被分发到某个Web应用;最后将请求URI与Web应用中全部Servlet的URL Pattern进行匹配,匹配成功的Servlet将处理该请求。主机的主机名在Tomcat的server.xml文件中设置(Host标签表示一个主机)。Web应用上下文路径在Context标签中配置(webapps路径下的Web应用的上下文路径就是Web应用的根目录名),Context标签的path属性表示该应用的上下文路径,Context标签的配置信息可能存在于多个位置(参见7.2.2节)。Servlet在Web应用部署描述符web.xml文件中经过servlet标签和servlet-mapping标签配置,这里的Web应用部署描述符包括Tomcat的通用部署描述符和各个Web应用中的特有部署描述符。

Tomcat在启动时,Tomcat分别读取和解析server.xml文件、各个Context描述文件和各个web.xml文件,获取其中关于Host、Context和Servlet的配置信息,构建一个从Host到Servlet的树形结构,并将其保存在Tomcat的内存空间中。当有新请求到达Tomcat时,Tomcat解析请求的URL根据URL的格式将请求从该树顶端以级分发到Servlet。

一种配置状况的示例如图8.2所示。

 

图8.2  Tomcat配置示例

 

如图8.2的示例所示,Tomcat中配置了两个Host:localhost和csai.cn;localhost中配置了两个Web应用:user-manage和book;user-manage和book中又分别定义了三个Servlet,每一个Servlet的URL Pattern如图中所示。针对该例的配置状况,考虑具备以下几个URL请求的分发状况。

(1)http://localhost/user-manage/login/setup

首先根据域名localhost将请求分发到localhost主机,而后将请求URI“/user-manage/login/setup”与主机中全部Web应用的上下文路径进行匹配,该URI与上下文路径为user-manage的Web应用匹配成功,而后将相对路径“/login/setup”与user-manage中全部Servlet的URL Pattern进行匹配,与“/login/*”匹配成功,因此该请求将由URL Pattern为“/login/*”的Servlet处理。

(2)http://localhost/book/store/query.do?id=12432

主机和Web应用上下文路径的匹配状况相似于上例,但特殊的是该例中包含了查询参数。不要紧,在匹配时能够不用管查询参数,直接忽略就能够了。因此很明确,该请求被分发到book中的“*.do”Servlet。

(3)http://localhost/book/buy

根据域名和Web应用能够将该请求分发到localhost的book应用,可是该应用的全部模板都没法与相对路径“/buy”相匹配。在这种状况下,请求会被分发给default Servlet处理。default Servlet是每一个Web应用都有的一个比较特殊的Servlet,它的URL Pattern是“/”,这是由Tomcat默认提供的web.xml中配置的。

(4)http://localhost/webpage/setup.html

根据域名能够将该请求分发到localhost主机,但localhost中并不存在名为webpage的Web应用。对于这种状况,Tomcat将该请求分发到localhost主机的默认应用。在${TOMCAT_HOME}/webapps/目录中有一个特殊的目录ROOT,该目录表示了一个localhost的默认应用,当请求URI没法与任何Web应用的上下文路径相匹配时就会被分发到该应用。因为该应用默认并无配置任何Servlet,因此该请求最终仍是被分发到default Servlet。

default Servlet是Tomcat自带的一个用于处理静态文件请求的Servlet。因为default Servlet的URL Pattern是“/”,它能够匹配任何相对路径,因此若是某个请求的URL没法与任何其余Servlet的URL Pattern相匹配时,请求就会被分发到default Servlet。default Servlet将分发到它的请求都做为静态文件请求处理,例如上面第三个URL被分发到default Servlet中后,default Servlet就会认为该请求是想请求book应用根目录下面的buy文件,假如book应用根目录下不存在buy文件,Tomcat就会返回一个错误,指示请求的资源不存在;如上面第四个URL,default Servlet会认为该请求是想请求ROOT目录下webpage目录中的setup.html文件,若是ROOT目录下存在webpage目录,而且webpage目录中存在setup.html文件,那么该文件将被返回,不然Tomcat会返回一个错误,指示请求的资源不存在。

在上面的例子中,读者已经发如今URL Pattern中能够用*表明任意字符,不少读者确定会很快将这个模式与正则表达式联系起来。但惋惜的是URL Pattern并不支持使用正则表达式描述。实质上,在Servlet规范中已经对URL Pattern的写法、意义以及当有多个URL Pattern时匹配次序的选择都是有明确规定的。在Servlet规范中定义了URL Pattern支持的四种格式:

(1)以“/”开头和以“/*”结尾:这种模式用于匹配一个路径前缀,好比“/login/*”能够匹配“/login/aaa”、“/login/bb/a.html”和“/login/cc/dd/q?x=5”等。

(2)之前缀“*.”开始:这种模式用于匹配以一种后缀结束的路径,好比“*.do”能够匹配“/aaa.do”、“/bb/cc/d.do”和“/bb/cc/q.do?x=5”等。

(3)字符“/”:这种模式只用来表示default Servlet。

(4)一个以“/”开头的字符串,而且不符合以上的任何一种格式:除了上面三种格式之外,其余格式都被用于精确匹配。好比“/register”只被用于匹配路径“/register”,而没法匹配“/register/default”;即便形如“/register/*.do”也只能匹配路径“/register/*.do”,而没法匹配“/register/start.do”。

除了以上规定的四类URL Pattern,其余格式都会被认为是非法格式,若是Tomcat在启动时探测到非法格式,Tomcat会在启动窗口中打印错误。

若是在一个Web应用中,这几类URL Pattern都存在,那么Tomcat会按照必定的优先顺序逐个匹配,即便有一个相对路径同时与多个URL Pattern相匹配,Tomcat也会选择优先顺序在先的Servlet处理请求。顺序以下:

首先Tomcat会从精确匹配模式(以上第4)类模式)中寻找是否有相匹配的模式。好比分别有模式“/aa”、“/aa/bb”和“/aa/*”,对于相对路径为“/aa/bb”的请求就会与模式“/aa/bb”相匹配;

若是没有匹配成功则从属于以上第(1)类模式的各类URL Pattern中寻找相匹配的模式。好比分别有模式“/aa”、“/aa/bb”和“/aa/*”,对于相对路径为“/aa/bb/cc”的请求就会与模式“/aa/*”相匹配。并且,假如这种匹配是最长匹配原则,好比分别有模式“/aa/*”和“/aa/bb/*”,对于相对路径为“/aa/bb/cc”的请求会与模式“/aa/bb/*”相匹配;

若是没有匹配成功则从属于以上第(2)类模式的各类URL Pattern中寻找相匹配的模式。好比分别有模式“/aa”、“/aa/bb”、“/aa/*”和“*.do”,对于相对路径为“/bb/cc.do”的请求就会与模式“*.do”相匹配;

若是以上各种模式都没有匹配成功,那么就会与“/”匹配成功,并最终由default Servlet处理。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Hello World Servlet

 

做者一直认为,学习编程最主要的是实践。在学习的过程当中,不断构造适当代码并进行不断实验是最好的学习过程。学习Servlet也同样。在开始学习以前,读者先搭建好实验环境,在学习过程当中能够将实例直接放进测试环境中测试。

本书中将Eclipse做为Servlet的开发环境,将Tomcat做为Servlet的运行环境。前面已经介绍了Eclipse和Tomcat的配置和使用,本节将介绍如何构建Servlet工程,而且将实现一个Servlet的Hello World工程、将该工程部署到Tomcat中、以及运行测试该Servlet。

1.新建工程

新建一个可以开发和测试Servlet的工程——ServletTest。在介绍Eclipse的使用时已经提到,用于开发Servlet应用的工程应该是动态Web工程。根据前面介绍的新建动态Web工程的步骤在Eclipse中新建一个动态Web工程ServletTest,建成的ServletTest工程在工程浏览视图中的内容如图8.3所示:

图8.3  新建ServletTest工程

新建的Web工程搭建了一个动态Web工程的基本架构,可是没有定义任何具备实质功能的内容。

2.编辑Servlet

工程建好后就能够直接新建待开发的Servlet。根据第5章中介绍的步骤新建一个TestServlet,所属的包为cn.csai.web.servlet。Eclipse为TestServlet自动生成的初始内容以下:

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

static final long serialVersionUID = 1L;

public TestServlet() {

        super();

}  

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}  

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}         

}

其中自动生成的方法中,除了默认构造方法外还有两个重要的方法doGet()和doPost(),这是大部分Servlet都会覆盖的方法。doGet()用于处理客户端的GET请求,doPost()用于处理客户端的POST。也就是说,当客户端使用GET方法的HTTP消息访问Servlet时,Servlet就将请求和响应传递给doGet()方法,由doGet()方法处理请求和响应;当客户端使用POST方法的HTTP消息访问Servlet时,Servlet就将请求和响应传递给doPost()方法,由doPost()方法处理请求和响应。

这两个方法的方法体中初始没有任何内容,程序员就将本身的代码添加到方法体中,所谓Servlet的不一样主要也就是这两个方法的方法体有所不一样。对于Hello World应用,读者能够在doGet()方法中添加输出“Hello World”的语句。将以下所示的HelloWorld代码体添加到doGet()方法中:

PrintWriter pw = response.getWriter();

pw.write("Hello World");

pw.flush();

pw.close();

这段代码从response对象得到一个Writer对象,而后利用Writer对象向客户端输出一个“Hello World”字符串,最后刷新和关闭Writer对象。因此这段代码的做用就是向客户端浏览器输出一个“Hello World”字符串。

Servlet须要被添加到Web应用中才能进行工做。使用Eclipse向导新建Servlet时,Eclipse已经自动将Servlet的配置信息写入到应用的web.xml文件中(参见第5章)。web.xml文件的内容以下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" 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 http://java.sun.com /xml/ns/j2ee/web-app_2_4.xsd">

<display-name>

ServletTest</display-name>

<servlet>

<description>

</description>

<display-name>

TestServlet</display-name>

<servlet-name>TestServlet</servlet-name>

<servlet-class>

cn.csai.web.servlet.TestServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>TestServlet</servlet-name>

<url-pattern>/TestServlet</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

</web-app>

3.部署Web应用

新建的工程必须被部署到Tomcat中才可以经过Tomcat测试,将Web工程部署到Tomcat中包括如下步骤:

(1)新建适用于Tomcat的Web应用:在${TOMCAT_HOME}/webapps目录下新建ServletTest目录;

(2)将Web工程的全部class文件和WEB-INF目录复制到Tomcat Web工程的适当目录中:打开ServletTest工程在文件系统中的目录(记为${ECLIPSE_PROJECTS}),将WebContent子目录中的WEB-INF目录整个复制到${TOMCAT_HOME}/webapps/ServletTest目录中;再将${ECLIPSE_ PROJECTS}/ServletTest/build中的classes目录复制到${TOMCAT_HOME}/webapps/ ServletTest/WEB- INF目录中;

(3)从新启动Tomcat。

4.运行Servlet

运行Servlet是相对比较容易的。在将Web工程部署到Tomcat中后,确保Tomcat已正常启动。而后打开浏览器,键入以下URL便会看到TestServlet的执行结果,如图8.4所示。

http://localhost:8080/ServletTest/TestServlet

图8.4  运行TestServlet

以上展现了新建、编辑、部署和运行Servlet工程和Servlet的步骤,读者在从此的学习中能够将该ServletTest工程做为测试环境,不断地修改TestServlet的doGet()方法和doPost(),以用于测试学到的新内容,而不须要从新创建新的工程和新的Servlet。步骤以下:

(1)在Eclipse中修改TestServlet的内容,保存并编译;

(2)删除${TOMCAT_HOME}/webapps/ServletTest/WEB-INF目录中的classes目录,将${TOMCAT_ HOME}/webapps/ServletTest/WEB-INF目录中的classes目录复制到${TOMCAT_HOME} /webapps/ ServletTest/WEB-INF目录中;

(3)重启Tomcat;

(4)输入URL测试。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet中的关键概念

 

在面向对象的系统中,理解系统的第一步首先须要理解系统中的一些关键概念,这些概念以及概念之间的关系构筑了系统的基础框架。对概念的理解有助于把握系统的结构。

在面向对象的系统中,一般一个类就表示了一个具体的概念,可是在设计比较完善的系统中一般会将重要和稳定的概念用接口表示,这种设计方法在JDK中随处可见。在Java Servlet体系中也不例外,其中一些关键的概念都被定义成相应的接口,而后再用一些具体类提供对这些接口的实现。

Java Servlet中定义了以下几个关键接口。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

Servlet接口

 

Servlet接口表明一个Servlet。这在介绍Servlet的概念时已经作了阐述。service()方法是Servlet接口最核心的方法,但Servlet接口并不只仅定义了这一个方法,Servlet接口的定义以下:

package javax.servlet;

import java.io.IOException;

public interface Servlet

{

    public abstract void init(ServletConfig servletconfig)

        throws ServletException;

    public abstract ServletConfig getServletConfig();

    public abstract void service(ServletRequest servletrequest, ServletResponse servletresponse) throws ServletException, IOException;

    public abstract String getServletInfo();

    public abstract void destroy();

}

其中:

init():该方法对应Servlet生命周期的初始化阶段,它在Tomcat初始Servlet时被调用。实质上,所谓Tomcat对Servlet初始化就是Tomcat调用Servlet的init()方法。该方法提供了一个ServletConfig做为参数,这是为了便于Servlet开发者可以在init()方法中得到关于Servlet的配置信息,该参数在Tomcat调用init()方法时由Tomcat提供,Tomcat能够经过调用getServletConfig()方法得到。同时,在初始化时还容许抛出ServletException,即开发人员在编写init()方法时能够将未处理的异常状况抛出为ServletException;

service():该方法对应于Servlet生命周期的执行阶段,在该Servlet的请求到达时被调用,任何到达该Servlet的请求都会执行这同一段代码,只是不一样请求的输入参数不一样;输入参数ServletRequest表明到达Servlet的请求,ServletResponse表明Servlet对请求的响应,Tomcat构造一个到客户端的ServletResponse对象并将其传给service()方法,方法在执行期间对该ServletResponse进行设置和操做,service()方法执行完后Servlet也就完成了对客户端的响应。该方法不只能够抛出ServletException异常还能够抛出IOException,那是由于service()方法中常常会经过ServletResponse对象向客户端传输响应数据,在这个过程当中可能会发生输入输出错误;

destroy():该方法对应于Servlet生命周期的销毁阶段,在Servlet执行结束后Tomcat能够适时地将其销毁,在Servlet被销毁时Tomcat会调用该方法。一般该方法中能够进行诸如释放资源等一些收尾工做;

getServletConfig():返回与该Servlet相关的ServletConfig对象;

getServletInfo():返回该Servlet的描述,一般该描述信息是Servlet实现者提供的用于描述Servlet的信息。

【注意】

因为Servlet基本上都被用于处理HTTP请求和响应,因此须要程序员就将Servlet等同于处理HTTP请求和响应的HttpServlet,但实际上Servlet接口所表示的是一种广泛概念的Servlet,它不只表示处理HTTP请求和响应的HttpServlet,也能够用于表示处理其余通用请求和响应的Servlet。一样ServletRequest和ServletResponse不能将其狭义理解为HTTP请求和HTTP响应。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

ServletConfig接口

 

ServletConfig接口表明对一个Servlet的配置信息,对应于web.xml中对该Servlet的配置项,以下是web.xml中default Servlet的配置信息:

<servlet>

        <servlet-name>default</servlet-name>

        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>

        <init-param>

            <param-name>debug</param-name>

            <param-value>0</param-value>

        </init-param>

        <init-param>

            <param-name>listings</param-name>

            <param-value>false</param-value>

        </init-param>

        <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

        <servlet-name>default</servlet-name>

        <url-pattern>/</url-pattern>

</servlet-mapping>

ServletConfig接口表示了一个Servlet的配置信息,在一个Servlet中能够经过ServletConfig接口的对象得到Servlet的这些配置信息。

ServletConfig接口的定义以下:

package javax.servlet;

import java.util.Enumeration;

public interface ServletConfig

{

    public abstract String getServletName();

    public abstract ServletContext getServletContext();

    public abstract String getInitParameter(String s);

    public abstract Enumeration getInitParameterNames();

}

其中:

getServletName():该方法得到该Servlet的名称,对应于web.xml中该Servlet的servlet-name标签的内容,例如default;

getServletContext():该方法返回该ServletConfig对象对应的ServletContext对象;

getInitParameter():该方法返回具备指定名称的初始参数的值。对应于Servlet的配置中,经过init-param标签订义的Servlet初始化参数,param-name是参数名,param-value是参数值。getInitParameter()方法能够经过参数名得到参数值,例如getInitParameter(“debug”)→ 0;

getInitParameterNames ():该方法返回一个Enumeration类型的对象,该对象中包含了该Servlet定义的全部初始化参数的参数名。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

ServletContext接口

 

ServletContext接口表明了Servlet所运行的上下文信息,定义了一个Servlet环境对象。Tomcat在加载Web应用时,为每一个Web应用建立惟一的ServletContext对象,Web应用中的Servlet经过该ServletContext对象与Servlet容器进行通讯。定义以下:

package javax.servlet;

import java.io.InputStream;

import java.net.MalformedURLException;

import java.net.URL;

import java.util.Enumeration;

import java.util.Set;

public interface ServletContext

{

    public abstract ServletContext getContext(String s);

    public abstract int getMajorVersion();

    public abstract int getMinorVersion();

    public abstract String getMimeType(String s);

    public abstract Set getResourcePaths(String s);

    public abstract URL getResource(String s) throws MalformedURLException;

    public abstract InputStream getResourceAsStream(String s);

    public abstract RequestDispatcher getRequestDispatcher(String s);

    public abstract RequestDispatcher getNamedDispatcher(String s);

    public abstract Servlet getServlet(String s) throws ServletException;

    public abstract Enumeration getServlets();

    public abstract Enumeration getServletNames();

    public abstract void log(String s);

    public abstract void log(Exception exception, String s);

    public abstract void log(String s, Throwable throwable);

    public abstract String getRealPath(String s);

    public abstract String getServerInfo();

    public abstract String getInitParameter(String s);

    public abstract Enumeration getInitParameterNames();

    public abstract Object getAttribute(String s);

    public abstract Enumeration getAttributeNames();

    public abstract void setAttribute(String s, Object obj);

    public abstract void removeAttribute(String s);

    public abstract String getServletContextName();

}

其中:

getContext(String s):该方法经过提供URI得到Server上其余ServletContext对象。参数是一个以“/”开始的字符串,表示相对于Server根路径的相对路径;

getMajorVersion():返回该Servlet容器支持的Servlet规范的主版本号;

getMinorVersion():返回该Servlet容器支持的Servlet规范的次版本号;

getMimeType(String s):根据提供的文件类型返回该文件类型对应的MIME类型;

getResourcePaths(String s):根据提供的子路径返回Web应用中的该子路径下的全部一级资源;例如Web应用中有以下文件:

/index.html

/images/bg.jpg

/WEB-INF/web.xml

/WEB-INF/lib/my.lib

那么:

getResourcePaths(“/”)将返回“/index.html”、“/images/”和“/WEB-INF/”;

getResourcePaths(“/WEB-INF”)将返回“/WEB-INF/web.xml”和“/WEB-INF/lib/”。

getResource(String s):根据给定的路径返回表示路径所指向资源的URL对象,提供的路径必须以“/”开头,表示以Web应用根路径计算的相对路径;

getResourceAsStream(String s):将给定路径所指向资源做为InputStream的对象返回。路径的格式和意义同getResource()方法;

getRequestDispatcher(String s):返回一个到给定路径所指向资源的RequestDispatcher对象。RequestDispatcher对象能够用于将请求转到资源或者将资源包含到响应中;

getNamedDispatcher(String s):返回一个到给定Servlet的RequestDispatcher对象,参数为Servlet名称;

getServlet(String s):已过期。返回名称为给定参数的Servlet对象;

getServlets():已过期。返回一个Enumeration,包含该ServletContext中全部的Servlet对象;

getServletNames():已过期。返回一个Enumeration,包含该ServletContext中全部Servlet的名称;

log(String s):记录日志,将参数字符串记录到日志文件中;

log(Exception exception,String s):记录日志,将给定Exception对象的stack trace和给定字符串所表示的解释字符串记录到日志文件中;

log(String s, Throwable throwable):将给定的解释字符串和给定Throwable对象的stack trace记录到日志文件中;

getRealPath(String s):返回给定路径所指向资源在文件系统中的绝对路径。路径的表现方式取决于Servlet容器所运行的操做系统;

getServerInfo():返回所运行的Servlet容器的名称版本号,输出格式为server-name/server-version;

getInitParameter(String s):返回给定具备给定名称的初始化参数的值,假如不存在给定名称的初始化参数则返回null;

getInitParameterNames():返回一个Enumeration对象,包含全部初始化参数的名称;

getAttribute(String s):Servlet容器可能为Servlet还提供了除该接口定义的属性之外的其余属性,该方法提供了一个扩展方法容许Servlet容器为Servlet提供其余可访问的属性。该方法根据属性名返回属性值,属性值能够是任何Object对象或其子对象;

getAttributeNames():返回一个Enumeration对象,包含Servlet容器提供的全部属性的名称;

setAttribute(String s, Object obj):在该ServletContext中设置属性名/值对,若是设置的属性名已经存在则用新值替换旧值,若是设置的属性值为null,则删除该属性;

removeAttribute(String s):在该ServletContext中删除具备给定名称的属性;

getServletContextName():在配置Web应用的web.xml时,能够经过display-name指定Web应用的名称,该方法返回配置的该名称。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

RequestDispatcher接口

 

RequestDispatcher接口表示一个请求转发器,它接收客户端的请求并把请求转发到任何Web资源,能够是:Servlet、JSP、HTML等。

定义以下:

package javax.servlet;

import java.io.IOException;

public interface RequestDispatcher {

  void forward(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException;

  void include(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException;

}

其中:

forward():将请求从一个Servlet转发到Server上的任何其余Web资源。该方法的出现容许一个Servlet对请求进行预处理,而后在响应消息发出前将请求转发给另外一个Web资源,由另外一个Web资源完成对请求的响应;

include():将资源的内容包含到响应消息中。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

接口之间的关系

 

在Servlet接口、ServletConfig接口和ServletContext接口的方法中,虽然使用Servlet的getServletConfig()能够得到一个ServletConfig接口的对象,再经过ServletConfig接口的getServletContext()方法能够得到一个ServletContext接口的对象,可是这几个接口所表示的概念并不具备相似的层叠关系。

这几个接口分别属于不一样的层面,ServletContext处于最高层,Servlet和ServletConfig属于同一层,在下层。每一个Web应用有一个ServletContext对象,每一个Servlet有一个ServletConfig对象,因此当一个Web应用中有多个Servlet时,从多个Servlet中得到的ServletContext对象是同一个,就是它们所在的Web应用的ServletContext对象。其关系如图8.5所示。

图8.5  Servlet、ServletConfig和ServletContext关系图

从各个概念所定义的方法也能够看出这种区别,ServletConfig中的方法能够用来获取单个Servlet相关的配置信息,而ServletContext中的方法能够用来获取Web应用或者Servlet容器相关的配置信息,这也是该接口被命名为ServletContext的缘由,由于它表明了Servlet所运行环境的信息。RequestDispatcher接口与这几个概念之间没有明确的层次关系,它仅表明一个请求转发器。

除了这几个关键概念外,还有两个接口所表达的概念也很是重要,它们是ServletRequest和ServletResponse,这两个接口分别表示Servlet的请求和Servlet返回给客户端的响应。它们被普遍应用于Servlet的各个处理场合和概念中。这两个概念将在本章的后续小节中介绍。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

GenericServlet和HttpServlet

 

接口只表示一个概念,接口及其之间的关系构筑了系统的概念体系,这是抽象层面。在具体层面,全部的概念都由具体的类实现,继承自同一接口的不一样类体现了这种概念的侧面或者不一样子类。

在Servlet体系中实现了Servlet接口的最主要的类是GenericServlet类和HttpServlet类,它们的类继承层次关系如图8.6所示。

图8.6  GenericServlet和HttpServlet类的继承层次图

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

GenericServlet抽象类

 

GenericServlet实现了Servlet接口和ServletConfig接口,它表示一种通用的Servlet可表明处理各类类型请求的Servlet;它只是抽象类,不能直接用于处理Servlet请求,因此只能经过实现继承自GenericServlet的子类来完成处理特定请求的功能。值得注意的是,GenericServlet类不只实现了Servlet接口,并且还实现了ServletConfig接口,因此GenericServlet类还包含了对Servlet配置信息的操做。

GenericServlet对Servlet接口和ServletConfig接口的实现只是一种最简单的默认实现,实质上并不具有实际意义,GenericServlet存在的意义就是用来被其余类继承的。GenericServlet的定义以下:

package javax.servlet;

import java.io.IOException;

import java.io.Serializable;

import java.util.Enumeration;

public abstract class GenericServlet

    implements Servlet, ServletConfig, Serializable

{

    private transient ServletConfig config;

    public GenericServlet()

    {

    }

    public void init(ServletConfig config) throws ServletException

    {

        this.config = config;

        init();

}

    public void init() throws ServletException

    {

}

    public abstract void service(ServletRequest servletrequest, ServletResponse servletresponse) throws ServletException, IOException;

    public void destroy()

    {

    }

    public ServletConfig getServletConfig()

    {

        return config;

    }

    public String getServletInfo()

    {

        return "";

    }

    public String getServletName()

    {

        return config.getServletName();

    }

    public String getInitParameter(String name)

    {

        return getServletConfig().getInitParameter(name);

    }

    public Enumeration getInitParameterNames()

    {

        return getServletConfig().getInitParameterNames();

    }

    public ServletContext getServletContext()

    {

        return getServletConfig().getServletContext();

    }

    public void log(String msg)

    {

        getServletContext().log((new StringBuilder()).append(getServletName()).append(":").append(msg). toString());

    }

    public void log(String message, Throwable t)

    {

        getServletContext().log((new StringBuilder()).append(getServletName()).append(":").append(message).toString(), t);

    }

}

从代码中能够发现,GenericServlet定义了一个ServletConfig对象的私有成员,并将从init(ServletConfig config)方法中传入的ServletConfig对象作了封装,而后增长了一个不带任何参数的init()方法供子类继承,这样能够简化子类的实现;对于init()、destroy()、getServletConfig()和getServletInfo()方法提供了实现,可是没有在方法体中提供任何实质性内容,这实际上只是搭建了一个Servlet的框架供子类使用,子类只须要覆盖其关心的方法便可;对于service()方法,GenericServlet将其定义为抽象方法,没有提供默认实现,由于service()方法是一个Servlet的核心处理代码,也是一个Servlet区别于其余Servlet的关键部分,因此GenericServlet将其定义为抽象方法,强制其具体子类提供该方法的具体实现。

对于ServletConfig接口的几个方法,GenericServlet也提供了最简单的实现。从代码体中能够发现,这种实现实际上只是对其封装的config的接口的一种调用,因此这种实现并无实质做用,具体所执行的工做只能由交给传递进来的实现了ServletConfig接口的类来实现。

除此以外,GenericServlet类提供了两个log()方法,它们利用ServletContext对象的log()方法提供日志功能,它们虽然也没有实现任何实质内容,可是它们提供了一个进行日志工做的方便途径。

总而言之,GenericServlet类是一个抽象类,虽然只有一个抽象方法,可是它实现的其余方法都只仅仅提供了最基本的、无实质内容的实现,因此GenericServlet只能做为一种Servlet的父类进行继承,并且在子类中还应该将关心的相关方法进行覆盖,并提供相应实现。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HttpServlet抽象类

 

HttpServlet类是GenericServlet类的子类,它比GenericServlet类所涵盖的范围要小得多,它仅表示处理HTTP请求/响应的Servlet。可是同GenericServlet同样,HttpServlet也是一个抽象类,它覆盖了GenericServlet的一些方法,并且对GenericServlet的service()方法提供了实现。HttpServlet的实现以下:

package javax.servlet.http;

import java.io.IOException;

import java.io.Serializable;

import java.lang.reflect.Method;

import java.text.MessageFormat;

import java.util.Enumeration;

import java.util.ResourceBundle;

import javax.servlet.*;

public abstract class HttpServlet extends GenericServlet

    implements Serializable

{

    public HttpServlet()

    {

    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String protocol = req.getProtocol();

        String msg = lStrings.getString("http.method_get_not_supported");

        if(protocol.endsWith("1.1"))

            resp.sendError(405, msg);

        else

            resp.sendError(400, msg);

    }

    protected long getLastModified(HttpServletRequest req)

    {

        return -1L;

    }

    protected void doHead(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        NoBodyResponse response = new NoBodyResponse(resp);

        doGet(req, response);

        response.setContentLength();

    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String protocol = req.getProtocol();

        String msg = lStrings.getString("http.method_post_not_supported");

        if(protocol.endsWith("1.1"))

            resp.sendError(405, msg);

        else

            resp.sendError(400, msg);

    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String protocol = req.getProtocol();

        String msg = lStrings.getString("http.method_put_not_supported");

        if(protocol.endsWith("1.1"))

            resp.sendError(405, msg);

        else

            resp.sendError(400, msg);

    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String protocol = req.getProtocol();

        String msg = lStrings.getString("http.method_delete_not_supported");

        if(protocol.endsWith("1.1"))

            resp.sendError(405, msg);

        else

            resp.sendError(400, msg);

    }

    private static Method[] getAllDeclaredMethods(Class c)

    {

        if(c.equals(javax/servlet/http/HttpServlet))

            return null;

        Method parentMethods[] = getAllDeclaredMethods(c.getSuperclass());

        Method thisMethods[] = c.getDeclaredMethods();

        if(parentMethods != null && parentMethods.length > 0)

        {

            Method allMethods[] = new Method[parentMethods.length + thisMethods.length];

            System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);

            System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);

            thisMethods = allMethods;

        }

        return thisMethods;

    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        Method methods[] = getAllDeclaredMethods(getClass());

        boolean ALLOW_GET = false;

        boolean ALLOW_HEAD = false;

        boolean ALLOW_POST = false;

        boolean ALLOW_PUT = false;

        boolean ALLOW_DELETE = false;

        boolean ALLOW_TRACE = true;

        boolean ALLOW_OPTIONS = true;

        for(int i = 0; i < methods.length; i++)

        {

            Method m = methods[i];

            if(m.getName().equals("doGet"))

            {

                ALLOW_GET = true;

                ALLOW_HEAD = true;

            }

            if(m.getName().equals("doPost"))

                ALLOW_POST = true;

            if(m.getName().equals("doPut"))

                ALLOW_PUT = true;

            if(m.getName().equals("doDelete"))

                ALLOW_DELETE = true;

        }

        String allow = null;

        if(ALLOW_GET && allow == null)

            allow = "GET";

        if(ALLOW_HEAD)

            if(allow == null)

                allow = "HEAD";

            else

                allow = (new StringBuilder()).append(allow).append(", HEAD").toString();

        if(ALLOW_POST)

            if(allow == null)

                allow = "POST";

            else

                allow = (new StringBuilder()).append(allow).append(", POST").toString();

        if(ALLOW_PUT)

            if(allow == null)

                allow = "PUT";

            else

                allow = (new StringBuilder()).append(allow).append(", PUT").toString();

        if(ALLOW_DELETE)

            if(allow == null)

                allow = "DELETE";

            else

                allow = (new StringBuilder()).append(allow).append(", DELETE").toString();

        if(ALLOW_TRACE)

            if(allow == null)

                allow = "TRACE";

            else

                allow = (new StringBuilder()).append(allow).append(", TRACE").toString();

        if(ALLOW_OPTIONS)

            if(allow == null)

                allow = "OPTIONS";

            else

                allow = (new StringBuilder()).append(allow).append(", OPTIONS").toString();

        resp.setHeader("Allow", allow);

    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String CRLF = "\r\n";

        String responseString = (new StringBuilder()).append("TRACE ").append(req.getRequestURI()). append(" ").append(req.getProtocol()).toString();

        for(Enumeration reqHeaderEnum = req.getHeaderNames(); reqHeaderEnum.hasMoreElements();)

        {

            String headerName = (String)reqHeaderEnum.nextElement();

            responseString = (new StringBuilder()).append(responseString).append(CRLF).append(header Name).append(": ").append(req.getHeader(headerName)).toString();

        }

        responseString = (new StringBuilder()).append(responseString).append(CRLF).toString();

        int responseLength = responseString.length();

        resp.setContentType("message/http");

        resp.setContentLength(responseLength);

        ServletOutputStream out = resp.getOutputStream();

        out.print(responseString);

        out.close();

    }

    protected void service(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException

    {

        String method = req.getMethod();

        if(method.equals("GET"))

        {

            long lastModified = getLastModified(req);

            if(lastModified == -1L)

            {

                doGet(req, resp);

            } else

            {

                long ifModifiedSince = req.getDateHeader("If-Modified-Since");

                if(ifModifiedSince < (lastModified / 1000L) * 1000L)

                {

                    maybeSetLastModified(resp, lastModified);

                    doGet(req, resp);

                } else

                {

                    resp.setStatus(304);

                }

            }

        } else if(method.equals("HEAD"))

        {

            long lastModified = getLastModified(req);

            maybeSetLastModified(resp, lastModified);

            doHead(req, resp);

        } else if(method.equals("POST"))

            doPost(req, resp);

        else if(method.equals("PUT"))

            doPut(req, resp);

        else if(method.equals("DELETE"))

            doDelete(req, resp);

        else if(method.equals("OPTIONS"))

            doOptions(req, resp);

        else if(method.equals("TRACE"))

        {

            doTrace(req, resp);

        } else

        {

            String errMsg = lStrings.getString("http.method_not_implemented");

            Object errArgs[] = new Object[1];

            errArgs[0] = method;

            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(501, errMsg);

        }

    }

    private void maybeSetLastModified(HttpServletResponse resp, long lastModified)

    {

        if(resp.containsHeader("Last-Modified"))

            return;

        if(lastModified >= 0L)

            resp.setDateHeader("Last-Modified", lastModified);

    }

    public void service(ServletRequest req, ServletResponse res)

        throws ServletException, IOException

    {

        HttpServletRequest request;

        HttpServletResponse response;

        try

        {

            request = (HttpServletRequest)req;

            response = (HttpServletResponse)res;

        }

        catch(ClassCastException e)

        {

            throw new ServletException("non-HTTP request or response");

        }

        service(request, response);

    }

    private static final String METHOD_DELETE = "DELETE";

    private static final String METHOD_HEAD = "HEAD";

    private static final String METHOD_GET = "GET";

    private static final String METHOD_OPTIONS = "OPTIONS";

    private static final String METHOD_POST = "POST";

    private static final String METHOD_PUT = "PUT";

    private static final String METHOD_TRACE = "TRACE";

    private static final String HEADER_IFMODSINCE = "If-Modified-Since";

    private static final String HEADER_LASTMOD = "Last-Modified";

    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";

    private static ResourceBundle lStrings =

ResourceBundle.getBundle("javax.servlet.http.LocalStrings");

}

其中所定义的方法及其含义以下。

public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException:该方法是对GenericServlet的抽象方法service(ServletRequest req, ServletResponse res)的实现。因为全部到达HttpServlet的请求都是HTTP请求,并且对HTTP请求的响应也都是HTTP响应,因此在该方法中分别将参数req和res造型成HttpServletRequest和HttpServletResponse;而且用造型后的参数调用service(HttpServletRequest req, HttpServletResponse res)方法。

protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException:与上一个service()方法不一样的是上一个方法只是为了提供对抽象方法的实现,而这个方法实质上是提供对HTTP请求的处理,全部对上一个方法的调用都会被传递到该方法;该方法会对req参数进行分析,根据HTTP请求消息的方法是GET、HEAD、POST、PUT、DELETE、OPTIONS或TRACE分别调用方法doGet()、doHead()、doPost()、doPut()、doDelete()、doOptions()、doTrace()对请求进行处理。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException:提供对GET方法HTTP请求的处理。

protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException:提供对HEAD方法HTTP请求的处理。

protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException:提供对POST方法HTTP请求的处理。

protected void doPut(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException:提供对PUT方法HTTP请求的处理。

protected void doDelete(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException:提供对DELETE方法HTTP请求的处理。

protected void doOptions(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException:提供对OPTIONS方法HTTP请求的处理。

protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException:提供对TRACE方法HTTP请求的处理。

protected long getLastModified(HttpServletRequest req):返回对HTTP请求的最后修改时间。

虽然该类中没有定义任何抽象方法,可是因为该类也是抽象类,因此也只能被继承,而没法直接实例化。一般在实现HttpServlet的子类时,不须要覆盖HttpServlet对service(ServletRequest req, ServletResponse res)方法、service(HttpServletRequest req, HttpServletResponse res)方法、doOptions()方法和doTrace()方法,由于HttpServlet对这几个方法的实现对几乎全部处理HTTP消息的Servlet都是适用的,除非有些Servlet有特别的要求。而HttpServlet对doGet()方法、doHead()方法、doPost()方法、doPut()方法、doDelete()方法和getLastModified()方法的实现都是默认的最小实现,好比在doGet()、doHead()、doPost()、doPut()和doDelete()方法中只是发送错误信息说明该方法不被支持,而在getLastModified()方法中只是返回一个无效的时间,因此这几个方法都须要HttpServlet的子类来实现,若是实现的Servlet只但愿提供给客户端经过GET方法获取资源,那么就只须要覆盖doGet()方法,若是还但愿支持经过POST方法提交资源,那么就再覆盖doPost()方法,依此类推。对于没有实现的方法,若是接收到此类消息将返回一个错误消息,提示该方法没有被实现。

在Java Web开发中,绝大多数Servlet都继承自HttpServlet类,子类再根据各自业务须要分别实现do***()方法以覆盖父类的实现。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

ServletRequest

 

Servlet采用请求/响应模式进行工做,接受来自客户端的请求,对请求进行处理,而后将对请求的回复经过响应消息发送给客户端。可见请求和响应消息在Servlet的工做过程当中起着很重要的做用。在Servlet体系中,用ServletRequest对象表示通用请求、HttpServletRequest对象表示Http请求;用ServletResponse对象表示通用响应、HttpServletResponse对象表示Http响应。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

ServletRequest

 

ServletRequest是Servlet体系中又一个重要的接口,它表示Servlet请求,不只仅对请求消息做了封装,并且还表明一次请求过程,ServletRequest对象的生命周期就是从Servlet容器接收到请求到请求被处理结束。其定义以下:

package javax.servlet;

import java.io.*;

import java.util.*;

public interface ServletRequest

{

    public abstract Object getAttribute(String s);

    public abstract Enumeration getAttributeNames();

    public abstract String getCharacterEncoding();

    public abstract void setCharacterEncoding(String s)

        throws UnsupportedEncodingException;

    public abstract int getContentLength();

    public abstract String getContentType();

    public abstract ServletInputStream getInputStream()

        throws IOException;

    public abstract String getParameter(String s);

    public abstract Enumeration getParameterNames();

    public abstract String[] getParameterValues(String s);

    public abstract Map getParameterMap();

    public abstract String getProtocol();

    public abstract String getScheme();

    public abstract String getServerName();

    public abstract int getServerPort();

    public abstract BufferedReader getReader()

        throws IOException;

    public abstract String getRemoteAddr();

    public abstract String getRemoteHost();

    public abstract void setAttribute(String s, Object obj);

    public abstract void removeAttribute(String s);

    public abstract Locale getLocale();

    public abstract Enumeration getLocales();

    public abstract boolean isSecure();

    public abstract RequestDispatcher getRequestDispatcher(String s);

    public abstract String getRealPath(String s);

    public abstract int getRemotePort();

    public abstract String getLocalName();

    public abstract String getLocalAddr();

    public abstract int getLocalPort();

}

其中:

getAttribute(String s):获取具备给定名称属性的值。属性可能经过两种方式进行设置,一种是Servlet容器设置一些属性,使得Servlet容器能够与Servlet进行通讯,另外一种是在Servlet中本身调用setAttribute()方法设置;

getAttributeNames():返回一个Enumeration对象,包含全部属性的名称;

getCharacterEncoding():返回请求所使用的字符编码;

setCharacterEncoding(String s):设置字符编码,在将字符编码设置后,Servlet经过Reader读取Request的内容时就会以新的字符编码进行读取;

getContentLength():返回请求体的长度,以字节为单位。若是长度未知则返回 –1;

getContentType():返回请求内容的MIME类型;

getInputStream():返回一个ServletInputStream对象,经过该对象能够以二进制的形式读取Request的内容;

getParameter(String s):获取请求携带的参数中名称为s的参数值,若是没有该参数则返回null。在HTTP请求中,请求携带的查询字符串被做为参数,例如http://localhost:8080/query?num= 1&type=apple中的查询字符串有两个参数,参数名为num的值为1,参数名为type的值为apple。还有,从表单中提交的数据也做为参数,参数名为表单元素的名称,参数值为表单元素的值;

getParameterNames():返回一个Enumeration对象,包含全部参数的名称;

getParameterValues(String s):该方法与getParameter(String s)的做用同样,也是用于获取给定参数名的值。由于同一参数名可能具备多个参数值,因此getPatameter(String s)通常用来得到确信只有一个值的参数,getParameterValues(String s)通常用来得到可能有多个值的参数。多个值以字符串数组的形式返回;

getParameterMap():以Map的形式返回请求携带的全部参数的参数名和参数值。Map的键是String类型,表示参数名;Map的值是String数组类型,表示参数值;

getProtocol():返回请求所使用的协议及其版本,格式为:Protocol/MajorVersion.MinorVersion,例如,HTTP消息所使用的协议一般都是HTTP/1.0或HTTP/1.1;

getScheme():不一样的模式(HTTP、HTTPS、FTP等)对于请求的格式有不一样的造成规则。该方法返回请求所使用的模式名称;

getServerName():返回该请求的目的主机服务器的名称,一般为请求URL中的域名、主机名或IP地址;

getServerPort():返回请求的目的端口,一般在请求URL中域名后,用“:”与域名隔开。默认是80;

getReader():返回一个BufferedReader对象,经过该对象能够以字符形式读取请求的内容,若是在该方法被调用前已经使用setCharacterEncoding(String s)设置了编码,那么就使用设置的编码解析请求中的二进制形式内容,不然使用默认的编码解析;

getRemoteAddr():得到发出该请求的主机的IP地址,多是客户机也多是转发的代理服务器;

getRemoteHost():得到发出该请求的主机名称;

setAttribute(String s, Object obj):将名为s值为obj的属性设置到请求中;

removeAttribute(String s):删除请求中名为s的属性;

getLocale():返回客户端指望使用的本地化设置。根据HTTP请求消息头的Accept-Language头域的值进行判断;

getLocales():返回一个Enumeration对象,包含客户端能够接受的全部本地化设置,从指望的本地化设置开始按优先顺序排列;

isSecure():返回该请求是否使用了安全通道进行传输,好比HTTPS;

getRequestDispatcher(String s):s表示一个路径,该方法返回一个到s所指定资源的RequestDispatcher对象。与ServletContext的getRequestDispatcher(String s)不一样的是,该方法的路径能够是相对路径;

getRemotePort():得到发出该请求的主机所使用的端口;

getLocalName():得到接收该请求的主机名;

getLocalAddr():得到接收该请求的主机IP地址;

getLocalPort():得到接收该请求的主机使用的端口。

从ServletRequest接口提供的方法能够看出,ServletRequest对象提供了一种对请求的封装,能够经过该接口中的方法获取请求相关的信息,例如请求的参数、发出和接收请求的主机的相关信息等;同时也提供了一些请求相关的操做,例如设置和获取做用域为本请求的属性,获取相对于请求的RequestDispatcher对象等。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HttpServletRequest

 

虽然从设计上来讲Servlet不只仅局限于处理HTTP消息,但从Servlet诞生到如今不得不认可Servlet主要仍是被用于处理HTTP消息。因此学习Servlet重点仍是学习HTTP Servlet,一样学习ServletRequest重点仍是学习HttpServletRequest。

HttpServletRequest表示HTTP请求,它是ServletRequest的子接口,因此ServletRequest中定义的方法也都是HttpServletRequest的方法。除此以外,HttpServletRequest还定义了一些HTTP请求特有的方法。定义以下:

package javax.servlet.http;

import java.security.Principal;

import java.util.Enumeration;

import javax.servlet.ServletRequest;

public interface HttpServletRequest

    extends ServletRequest

{

    public static final String BASIC_AUTH = "BASIC";

    public static final String FORM_AUTH = "FORM";

    public static final String CLIENT_CERT_AUTH = "CLIENT_CERT";

    public static final String DIGEST_AUTH = "DIGEST";

    public abstract String getAuthType();

    public abstract Cookie[] getCookies();

    public abstract long getDateHeader(String s);

    public abstract String getHeader(String s);

    public abstract Enumeration getHeaders(String s);

    public abstract Enumeration getHeaderNames();

    public abstract int getIntHeader(String s);

    public abstract String getMethod();

    public abstract String getPathInfo();

    public abstract String getPathTranslated();

    public abstract String getContextPath();

    public abstract String getQueryString();

    public abstract String getRemoteUser();

    public abstract boolean isUserInRole(String s);

    public abstract Principal getUserPrincipal();

    public abstract String getRequestedSessionId();

    public abstract String getRequestURI();

    public abstract StringBuffer getRequestURL();

    public abstract String getServletPath();

    public abstract HttpSession getSession(boolean flag);

    public abstract HttpSession getSession();

    public abstract boolean isRequestedSessionIdValid();

    public abstract boolean isRequestedSessionIdFromCookie();

    public abstract boolean isRequestedSessionIdFromURL();

}

其中:

getAuthType():返回Servlet容器用于保护Servlet所使用的验证模式,返回的字符串能够是常量BASIC_AUTH、FORM_AUTH、CLIENT_CERT_AUTH和DIGEST_AUTH中之一;

getCookies():返回一个Cookie数组,包含请求所携带的全部Cookie对象;

getDateHeader(String s):得到HTTP请求消息头中日期类型的头域的值,参数为头域的名称,返回long表示的日期;

getHeader(String s):得到HTTP请求消息头中名为s的头域的值,返回String类型;

getHeaders(String s):由于在HTTP消息头中,容许多个头域具备相同的名称。该方法返回一个Enumeration对象,包含名为s的全部头域的值;

getHeaderNames():返回一个Enumeration对象,包含全部头域的名称;

getIntHeader(String s):得到Int类型头域的值;

getMethod():得到该HTTP请求所使用的HTTP方法,例如GET、POST等;

getPathInfo():返回URL中Servlet名和查询字符串之间的路径,以“/”开头;

getPathTranslated():得到URL中Servlet名和查询字符串之间的路径而且将其翻译成真实路径返回;

getContextPath():返回该请求URL所指向应用的上下文路径;

getQueryString():返回请求URL中的查询字符串;

getRemoteUser():若是发出该请求的终端用户进行了登陆,则返回用户的登陆信息,不然返回null;

isUserInRole(String s):若是终端用户进行了登陆,则验证用户是否在指定的角色里,若是是则返回true,不然返回false;

getUserPrincipal():返回一个java.security.Principal 对象,该对象包含当前验证用户的名称;若是该用户没有通过验证则返回null;

getRequestedSessionId():返回指定给客户端的Session ID;

getRequestURI():返回请求消息中请求URI,就是在HTTP请求消息头中第一行出现的所请求资源的路径;

getRequestURL():请求的URL,包含协议、域名/IP、端口、服务器上的相对路径,但不包含查询字符串;

getServletPath():返回URL中用于进行Servlet映射的路径;

getSession(boolean flag):返回与当前Request相关联的HttpSession,若是没有与当前Request相关联的HttpSession且flag为true时就新建一个Session;

getSession():至关于getSession(true);

isRequestedSessionIdValid():检查请求的Session ID是否还有效;

isRequestedSessionIdFromCookie():检查请求的Session ID是否来自于Cookie;

isRequestedSessionIdFromURL():检查请求的Session ID是否来自于URL。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

HttpServletRequestPrinter实验

 

实验是学习编程的最好方法,本节将介绍一个实验用来学习ServletRequest和HttpServletRequest。本实验实现一个Servlet,该Servlet用于打印输出HTTP请求所对应的HttpServletRequest对象的相关方法的值。

直接在前面建成的ServletTest工程中进行实验。修改TestServlet,将TestServlet的内容修改以下:

示例8.1  HttpServletRequestPrinter

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Enumeration;

import java.util.Map;

import javax.servlet.ServletException;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

/**

 * Servlet implementation class for Servlet: TestServlet

 * 

 */

public class TestServlet extends javax.servlet.http.HttpServlet implements

javax.servlet.Servlet

{

static final long serialVersionUID = 1L;

private PrintWriter pw;

/*

* (non-Java-doc)

* @see javax.servlet.http.HttpServlet#HttpServlet()

*/

public TestServlet() {

super();

}

/*

* (non-Java-doc)

* @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest request,

*      HttpServletResponse response)

*/

protected void doGet(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException

{

pw = response.getWriter();

writeln("--- Attributes --------------------");

Enumeration<String> attrNames = request.getAttributeNames();

for (String attr = ""; attrNames.hasMoreElements(); attr = attrNames

.nextElement())

{

writeln(attr + " = " + request.getAttribute(attr));

}

writeln("--- HTTP Message --------------------");

writeln("getScheme(): " + request.getScheme());

writeln("getProtocol(): " + request.getProtocol());

writeln("getMethod(): " + request.getMethod());

writeln("--- URL --------------------");

writeln("getRequestURI(): " + request.getRequestURI());

writeln("getRequestURL(): " + request.getRequestURL());

writeln("getPathInfo(): " + request.getPathInfo());

writeln("getPathTranslated(): " + request.getPathTranslated());

writeln("getQueryString(): " + request.getQueryString());

writeln("--- Parameters --------------------");

Map params = request.getParameterMap();

for(Object key : params.keySet()) {

String[] values = (String[]) params.get(key);

for(String v : values){

writeln(key + " = " + v);

}

}

writeln("--- Headers --------------------");

Enumeration<String> headerNames = request.getHeaderNames();

for (String header = headerNames.nextElement(); 

null != header && headerNames.hasMoreElements(); 

header = headerNames.nextElement())

{

writeln(header + " = " + request.getHeader(header));

}

writeln("--- Server Information --------------------");

writeln("getLocale(): " + request.getLocale());

writeln("getLocalAddr(): " + request.getLocalAddr());

writeln("getLocalName(): " + request.getLocalName());

writeln("getLocalPort(): " + request.getLocalPort());

writeln("getServerName(): " + request.getServerName());

writeln("getServerPort(): " + request.getServerPort());

writeln("getContextPath(): " + request.getContextPath());

writeln("getServletPath(): " + request.getServletPath());

writeln("--- Client Information --------------------");

writeln("getRemoteAddr(): " + request.getRemoteAddr());

writeln("getRemoteHost(): " + request.getRemoteHost());

writeln("getRemoteUser(): " + request.getRemoteUser());

writeln("--- Cookies --------------------");

Cookie[] cookies = request.getCookies();

if (null != cookies) {

for (Cookie cookie : cookies) {

writeln(cookie.getName() + "=" + cookie.getValue()+ 

";expires=" + cookie.getMaxAge() + 

";domain="+ cookie.getDomain() + 

";path=" + cookie.getPath());

}

}

writeln("--- Request Information --------------------");

writeln("getContentLength(): " + request.getContentLength());

writeln("getContentType(): " + request.getContentType());

writeln("getAuthType(): " + request.getAuthType());

writeln("getCharacterEncoding(): " + request.getCharacterEncoding());

writeln("getRequestedSessionId(): " + request.getRequestedSessionId());

pw.flush();

pw.close();

}

private void writeln(String str) {

pw.write(str + "<br>");

}

}

该Servlet将请求生成的HttpServletRequest对象的各个方法的值输出到客户端浏览器。按照以上内容编辑好TestServlet,并修改ServletTest工程的web.xml文件,将TestServlet的url-mapping修改成“/TestServlet/*”,即让全部相对路径以TestServlet开始的请求都分发到TestServlet。

从新部署Web应用并从新启动Tomcat,在浏览器中输入以下URL:

http://localhost:8080/ServletTest/TestServlet/a?b=1&c=tt

返回的页面内容以下:

--- Attributes --------------------

--- HTTP Message --------------------

getScheme(): http

getProtocol(): HTTP/1.1

getMethod(): GET

--- URL --------------------

getRequestURI(): /ServletTest/TestServlet/a

getRequestURL(): http://localhost:8080/ServletTest/TestServlet/a

getPathInfo(): /a

getPathTranslated(): D:\tomcat\MyWebapps\ServletTest\a

getQueryString(): b=1&c=tt

--- Parameters --------------------

c = tt

b = 1

--- Headers --------------------

accept = */*

accept-language = zh-cn,en-us;q=0.5

accept-encoding = gzip, deflate

user-agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)

host = localhost:8080

--- Server Information --------------------

getLocale(): zh_CN

getLocalAddr(): 127.0.0.1

getLocalName(): localhost

getLocalPort(): 8080

getServerName(): localhost

getServerPort(): 8080

getContextPath(): /ServletTest

getServletPath(): /TestServlet

--- Client Information --------------------

getRemoteAddr(): 127.0.0.1

getRemoteHost(): 127.0.0.1

getRemoteUser(): null

--- Cookies --------------------

--- Request Information --------------------

getContentLength(): -1

getContentType(): null

getAuthType(): null

getCharacterEncoding(): null

getRequestedSessionId(): null

从返回的页面内容中能够发现各个方法所得到的值,对照输入的URL能够很清晰地明确每一个方法所获取的内容。其中因为测试环境没有设置任何属性,因此Attributes项没有任何内容;一样缘由Cookies项也没有内容。许多输出为null的就表示请求中没有设置该属性。

在学习其余部份内容时,读者也能够采用一样的实验方法,经过构造适当的Servlet内容,将Servlet实际部署到Tomcat中,经过运行Servlet观察输出结果;比较不一样Servlet的不一样输出结果能够很清楚的了解不少内容。

 

相对于Request,Response表示响应。ServletResponse表示通用Servlet响应,HttpServletResponse表示HTTP Servlet响应。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月14日

 

ServletResponse

 

ServletResponse的定义以下:

package javax.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Locale;

public interface ServletResponse

{

    public abstract String getCharacterEncoding();

    public abstract String getContentType();

    public abstract ServletOutputStream getOutputStream()

        throws IOException;

    public abstract PrintWriter getWriter()

        throws IOException;

    public abstract void setCharacterEncoding(String s);

    public abstract void setContentLength(int i);

    public abstract void setContentType(String s);

    public abstract void setBufferSize(int i);

    public abstract int getBufferSize();

    public abstract void flushBuffer()

        throws IOException;

    public abstract void resetBuffer();

    public abstract boolean isCommitted();

    public abstract void reset();

    public abstract void setLocale(Locale locale);

    public abstract Locale getLocale();

}

其中:

getCharacterEncoding():返回在Response消息中使用的字符编码方式;

getContentType():返回Response消息内容所使用的MIME类型;

getOutputStream():得到ServletOutputStream类型的对象,经过该对象Servlet能够向客户端发送二进制形式的响应数据;

getWriter():得到PrintWriter类型的对象,经过该对象Servlet能够向客户端发送字符形式的响应数据;

setCharacterEncoding(String s):将响应消息所使用的字符编码方式设置为s;

setContentLength(int I):设置响应消息的内容长度,该数据将被设置为HTTP响应消息Content-Length头域的值;

setContentType(String s):设置响应消息内容的MIME类型为s,该设置也将体现到Content-Type头域中;

setBufferSize(int I):用于设置该响应消息所使用的缓冲区大小。在设置了缓冲区大小后,Servlet容器会为响应消息分配不小于所设置大小的缓冲区供响应消息缓冲数据;

getBufferSize():得到Servlet容器实际为响应消息分配的缓冲区大小;

flushBuffer():刷新缓冲区,强制将缓冲区中全部的响应数据发送给客户端;

resetBuffer():在响应消息发送给客户端以前,重置缓冲区,除HTTP响应消息头和响应代码外,清空缓冲区中HTTP响应消息的内容;

isCommitted():返回响应消息是否已经发送到客户端;

reset():清空缓冲区中的全部数据,包括HTTP响应消息头和响应代码;

setLocale(Locale locale):设置响应消息的本地化为locale对象;

getLocale():得到响应消息的本地化对象。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

HttpServletResponse

 

HttpServletResponse的定义以下:

package javax.servlet.http;

import java.io.IOException;

import javax.servlet.ServletResponse;

public interface HttpServletResponse

    extends ServletResponse

{

    public abstract void addCookie(Cookie cookie);

    public abstract boolean containsHeader(String s);

    public abstract String encodeURL(String s);

    public abstract String encodeRedirectURL(String s);

    public abstract void sendError(int i, String s)

        throws IOException;

    public abstract void sendError(int i)

        throws IOException;

    public abstract void sendRedirect(String s)

        throws IOException;

    public abstract void setDateHeader(String s, long l);

    public abstract void addDateHeader(String s, long l);

    public abstract void setHeader(String s, String s1);

    public abstract void addHeader(String s, String s1);

    public abstract void setIntHeader(String s, int i);

    public abstract void addIntHeader(String s, int i);

    public abstract void setStatus(int i);

    public static final int SC_CONTINUE = 100;

    public static final int SC_SWITCHING_PROTOCOLS = 101;

    public static final int SC_OK = 200;

    public static final int SC_CREATED = 201;

    public static final int SC_ACCEPTED = 202;

    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;

    public static final int SC_NO_CONTENT = 204;

    public static final int SC_RESET_CONTENT = 205;

    public static final int SC_PARTIAL_CONTENT = 206;

    public static final int SC_MULTIPLE_CHOICES = 300;

    public static final int SC_MOVED_PERMANENTLY = 301;

    public static final int SC_MOVED_TEMPORARILY = 302;

    public static final int SC_FOUND = 302;

    public static final int SC_SEE_OTHER = 303;

    public static final int SC_NOT_MODIFIED = 304;

    public static final int SC_USE_PROXY = 305;

    public static final int SC_TEMPORARY_REDIRECT = 307;

    public static final int SC_BAD_REQUEST = 400;

    public static final int SC_UNAUTHORIZED = 401;

    public static final int SC_PAYMENT_REQUIRED = 402;

    public static final int SC_FORBIDDEN = 403;

    public static final int SC_NOT_FOUND = 404;

    public static final int SC_METHOD_NOT_ALLOWED = 405;

    public static final int SC_NOT_ACCEPTABLE = 406;

    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;

    public static final int SC_REQUEST_TIMEOUT = 408;

    public static final int SC_CONFLICT = 409;

    public static final int SC_GONE = 410;

    public static final int SC_LENGTH_REQUIRED = 411;

    public static final int SC_PRECONDITION_FAILED = 412;

    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;

    public static final int SC_REQUEST_URI_TOO_LONG = 414;

    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;

    public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;

    public static final int SC_EXPECTATION_FAILED = 417;

    public static final int SC_INTERNAL_SERVER_ERROR = 500;

    public static final int SC_NOT_IMPLEMENTED = 501;

    public static final int SC_BAD_GATEWAY = 502;

    public static final int SC_SERVICE_UNAVAILABLE = 503;

    public static final int SC_GATEWAY_TIMEOUT = 504;

    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;

}

其中:

addCookie(Cookie cookie):将指定的Cookie添加到HTTP响应消息中;

containsHeader(String s):返回在HTTP响应消息中是否已经包含了指定的头域;

encodeURL(String s):参数为一个URL,该方法表示对指定的URL进行编码,将Session ID编码进URL中。若是不须要编码则返回原始URL;

encodeRedirectURL(String s):参数为一个用于重定向的URL,该方法对用于重定向的URL进行编码,将Session ID编码进URL中;

sendError(int i, String s):使用错误代码i和错误消息s向客户端发送错误消息;

sendError(int i):使用错误代码i向客户端发送错误消息;

sendRedirect(String s):该方法返回一个重定向响应到客户端,s是重定向的URL;

setDateHeader(String s, long l):设置HTTP响应消息日期类型的头域,s为头域名称,l为long表示的日期。若是s头域已经存在,则使用l替换原来的值;

addDateHeader(String s, long l):添加一个日期类型的头域到HTTP响应消息中,若是s头域已存在则新添加一个具备名称s的头域;

setHeader(String s, String s1):设置字符类型的头域,s为头域的名称,s1是头域的值。若是s头域已经存在,则使用s1替换原来的值;

addHeader(String s, String s1):添加一个字符类型的头域,s为头域名称,s1为头域的值,若是s头域已存在则新添加一个具备名称s的头域;

setIntHeader(String s, int I):设置int类型的头域;

addIntHeader(String s, int I):添加int类型的头域;

setStatus(int i):设置响应消息的响应码为i。

其中除了这些方法外,还定义了一系列静态常量,这些常量是一些经常使用的HTTP响应码,常量的名字简单表述了该响应码的含义。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

Servlet过滤器

 

Servlet过滤器,顾名思义是一种搭建在Servlet之上的过滤器,这种过滤器存在于Servlet容器和Servlet之间,对Servlet容器分发给Servlet的请求和Servlet返回给Servlet容器的响应进行过滤。可是这里所说的过滤的含义并非“让一部分经过而不让另外一部分经过”,而是“对通过的全部请求和响应消息进行修改”。Servlet过滤器对请求URL符合某种规则的请求和响应进行过滤,获取这些请求的ServletRequest对象和ServletResponse对象,对其进行修改和操做,以达到影响请求和响应的效果。

Servlet过滤器对全部Servlet是透明的,Servlet并不清楚Servlet过滤器的存在,Servlet仍是一如既往地接收请求(已被Servlet过滤器处理过),对请求进行处理,而后将响应返回,返回后的响应还会经过Servlet过滤器,并被过滤器修改(若是须要的话)。

Servlet过滤器能够定义若干个,对于同一个请求可能有多个过滤器对其进行过滤,过滤器遵守必定的顺序排列在Servlet容器与Servlet之间,请求按照从Servlet容器到Servlet的顺序依次被过滤器修改,响应则按照反方向依次被过滤器修改。过滤器之间也一样不清楚对方的存在,它们也是对传递到的请求和响应进行处理,不管该请求来自于Servlet容器仍是来自于其余过滤器。

Servlet过滤器工做如图8.5所示。

图8.5  Servlet过滤器工做示意图

如图8.5所示,处理流程以下:

(1)Tomcat接收到来自客户端的请求,将请求构形成为一个ServletRequest对象,同时构造一个空的ServletResponse对象;

(2)Tomcat将两个对象同时传递给第一个过滤器,第一个过滤器对ServletRequest对象和ServletResponse对象进行修改或者使用一个新的对象将其包装,而后将修改或包装好的ServletRequest对象和ServletResponse对象传递给第二个Filter,第二个作相似的处理,再传递给下一个,依次日后直到最终到达Servlet;

(3)Servlet调用传递进来的ServletRequest对象的相关方法对其进行解析,而后再调用传递进来的ServletResponse对象的相关方法向客户端发送响应消息;

因为每一个Filter均可以对ServletRequest对象和ServletResponse对象进行修改或封装,因此Servlet调用的ServletResponse对象的方法实质上调用的是封装后的方法,或者调用ServletRequest对象的方法得到的信息实质上是修改后的信息。

【注意】

理论上并非只有请求Servlet的请求和响应才会被过滤。全部请求URL符合过滤器匹配模式的请求和响应都会被过滤,即便请求所指向的是一个静态资源。但在Tomcat中,全部的请求最终都是由Servlet处理的(没有分发到用户Servlet的请求都会由default Servlet或jsp Servlet处理),因此过滤器过滤的请求最终都会达到某个Servlet。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

关键概念

 

Servlet中有三个与Servlet过滤器相关的接口:Filter、FilterChain和FilterConfig。

1.Filter接口

Filter接口表示一个Servlet过滤器。其定义以下:

package javax.servlet;

import java.io.IOException;

public interface Filter

{

    public abstract void init(FilterConfig filterconfig) throws ServletException;

    public abstract void doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException;

    public abstract void destroy();

}

其中:

init(FilterConfig filterconfig):表示Filter初始化时执行的内容,在Tomcat初始化Filter时会调用Filter的方法;

doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain):表示对请求和响应进行过滤的代码,在有请求经过该Filter时会执行该方法。其中servletrequest表示请求对象,servletresponse表示响应对象,filterchain表示过滤器链。该方法是一个Servlet过滤器的核心处理代码,Tomcat将构造的Request对象和Response对象传递给过滤器的该方法,过滤器能够对Request对象和Response对象作任何修改,而后将修改后的Request对象和Response对象经过filterchain对象传递给Filter链中的下一个过滤器;

destroy():表示Filter在销毁时执行的内容,在Tomcat销毁Filter时会调用Filter的该方法。

2.FilterChain接口

FilterChain接口表示一个过滤器链,它至关于一个链表,全部的过滤器按照顺序依次连接。链表的最后一个节点是目的Servlet。该接口只定义了一个方法,被过滤器用来将Request对象和Response对象向下传递,其定义以下:

package javax.servlet;

import java.io.IOException;

public interface FilterChain

{

public abstract void doFilter(ServletRequest servletrequest, ServletResponse servletresponse) throws IOException, ServletException;

}

其中:

doFilter(ServletRequest servletrequest, ServletResponse servletresponse):该方法用来将servletrequest对象和servletresponse对象传递给过滤器链中的下一个过滤器。

3.FilterConfig接口

FilterConfig接口表示对过滤器的配置,从中能够获取过滤器的相关配置信息。定义以下:

package javax.servlet;

import java.util.Enumeration;

public interface FilterConfig

{

    public abstract String getFilterName();

    public abstract ServletContext getServletContext();

    public abstract String getInitParameter(String s);

    public abstract Enumeration getInitParameterNames();

}

其中:

getFilterName():得到该过滤器的名称,对应于配置该过滤器时设置的filter-name属性;

getServletContext():得到调用该过滤器的应用的ServletContext对象;

getInitParameter(String s):得到名为s的初始化参数,这里的初始化参数是指在配置该过滤器时设置的初始化参数,是过滤器独有的,与Servlet定义的初始化参数无关;

getInitParameterNames():得到全部初始化参数的名称。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

Servlet包装器

 

在前面介绍Filter的工做过程当中提到,Filter影响Servlet对客户端的请求/响应有两种方式:一是经过修改传递进来的Request对象和Response对象;二是经过对Request和Response对象进行包装。对一种对象进行包装使得调用者在不作任何修改的状况下改变程序的行为,这种方式在设计模式中称为包装(Wrapper)模式。

ServletRequestWrapper和ServletResponseWrapper分别是ServletRequest和ServletResponse的包装器,HttpServletRequestWrapper和HttpServletResponseWrapper分别是HttpServletRequest和HttpServletResponse的包装器。它们的定义以下:

package javax.servlet;

import java.io.*;

import java.util.*;

public class ServletRequestWrapper

    implements ServletRequest

{

    public ServletRequestWrapper(ServletRequest request)

    {

        if(request == null)

        {

            throw new IllegalArgumentException("Request cannot be null");

        } else

        {

            this.request = request;

            return;

        }

    }

    public ServletRequest getRequest()

    {

        return request;

    }

    public void setRequest(ServletRequest request)

    {

        if(request == null)

        {

            throw new IllegalArgumentException("Request cannot be null");

        } else

        {

            this.request = request;

            return;

        }

    }

    public Object getAttribute(String name)

    {

        return request.getAttribute(name);

    }

    public Enumeration getAttributeNames()

    {

        return request.getAttributeNames();

    }

    public String getCharacterEncoding()

    {

        return request.getCharacterEncoding();

    }

    public void setCharacterEncoding(String enc)

        throws UnsupportedEncodingException

    {

        request.setCharacterEncoding(enc);

    }

    public int getContentLength()

    {

        return request.getContentLength();

    }

    public String getContentType()

    {

        return request.getContentType();

    }

    public ServletInputStream getInputStream()

        throws IOException

    {

        return request.getInputStream();

    }

    public String getParameter(String name)

    {

        return request.getParameter(name);

    }

    public Map getParameterMap()

    {

        return request.getParameterMap();

    }

    public Enumeration getParameterNames()

    {

        return request.getParameterNames();

    }

    public String[] getParameterValues(String name)

    {

        return request.getParameterValues(name);

    }

    public String getProtocol()

    {

        return request.getProtocol();

    }

    public String getScheme()

    {

        return request.getScheme();

    }

    public String getServerName()

    {

        return request.getServerName();

    }

    public int getServerPort()

    {

        return request.getServerPort();

    }

    public BufferedReader getReader()

        throws IOException

    {

        return request.getReader();

    }

    public String getRemoteAddr()

    {

        return request.getRemoteAddr();

    }

    public String getRemoteHost()

    {

        return request.getRemoteHost();

    }

    public void setAttribute(String name, Object o)

    {

        request.setAttribute(name, o);

    }

    public void removeAttribute(String name)

    {

        request.removeAttribute(name);

    }

    public Locale getLocale()

    {

        return request.getLocale();

    }

    public Enumeration getLocales()

    {

        return request.getLocales();

    }

    public boolean isSecure()

    {

        return request.isSecure();

    }

    public RequestDispatcher getRequestDispatcher(String path)

    {

        return request.getRequestDispatcher(path);

    }

    public String getRealPath(String path)

    {

        return request.getRealPath(path);

    }

    public int getRemotePort()

    {

        return request.getRemotePort();

    }

    public String getLocalName()

    {

        return request.getLocalName();

    }

    public String getLocalAddr()

    {

        return request.getLocalAddr();

    }

    public int getLocalPort()

    {

        return request.getLocalPort();

    }

    private ServletRequest request;

}

package javax.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Locale;

public class ServletResponseWrapper

    implements ServletResponse

{

private ServletResponse response;

    public ServletResponseWrapper(ServletResponse response)

    {

        if(response == null)

        {

            throw new IllegalArgumentException("Response cannot be null");

        } else

        {

            this.response = response;

            return;

        }

    }

    public ServletResponse getResponse()

    {

        return response;

    }

    public void setResponse(ServletResponse response)

    {

        if(response == null)

        {

            throw new IllegalArgumentException("Response cannot be null");

        } else

        {

            this.response = response;

            return;

        }

    }

    public void setCharacterEncoding(String charset)

    {

        response.setCharacterEncoding(charset);

    }

    public String getCharacterEncoding()

    {

        return response.getCharacterEncoding();

    }

    public ServletOutputStream getOutputStream()

        throws IOException

    {

        return response.getOutputStream();

    }

    public PrintWriter getWriter()

        throws IOException

    {

        return response.getWriter();

    }

    public void setContentLength(int len)

    {

        response.setContentLength(len);

    }

    public void setContentType(String type)

    {

        response.setContentType(type);

    }

    public String getContentType()

    {

        return response.getContentType();

    }

    public void setBufferSize(int size)

    {

        response.setBufferSize(size);

    }

    public int getBufferSize()

    {

        return response.getBufferSize();

    }

    public void flushBuffer()

        throws IOException

    {

        response.flushBuffer();

    }

    public boolean isCommitted()

    {

        return response.isCommitted();

    }

    public void reset()

    {

        response.reset();

    }

    public void resetBuffer()

    {

        response.resetBuffer();

    }

    public void setLocale(Locale loc)

    {

        response.setLocale(loc);

    }

    public Locale getLocale()

    {

        return response.getLocale();

    }

}

package javax.servlet.http;

import java.security.Principal;

import java.util.Enumeration;

import javax.servlet.ServletRequestWrapper;

public class HttpServletRequestWrapper extends ServletRequestWrapper

    implements HttpServletRequest

{

    public HttpServletRequestWrapper(HttpServletRequest request)

    {

        super(request);

    }

    private HttpServletRequest _getHttpServletRequest()

    {

        return (HttpServletRequest)super.getRequest();

    }

    public String getAuthType()

    {

        return _getHttpServletRequest().getAuthType();

    }

    public Cookie[] getCookies()

    {

        return _getHttpServletRequest().getCookies();

    }

    public long getDateHeader(String name)

    {

        return _getHttpServletRequest().getDateHeader(name);

    }

    public String getHeader(String name)

    {

        return _getHttpServletRequest().getHeader(name);

    }

    public Enumeration getHeaders(String name)

    {

        return _getHttpServletRequest().getHeaders(name);

    }

    public Enumeration getHeaderNames()

    {

        return _getHttpServletRequest().getHeaderNames();

    }

    public int getIntHeader(String name)

    {

        return _getHttpServletRequest().getIntHeader(name);

    }

    public String getMethod()

    {

        return _getHttpServletRequest().getMethod();

    }

    public String getPathInfo()

    {

        return _getHttpServletRequest().getPathInfo();

    }

    public String getPathTranslated()

    {

        return _getHttpServletRequest().getPathTranslated();

    }

    public String getContextPath()

    {

        return _getHttpServletRequest().getContextPath();

    }

    public String getQueryString()

    {

        return _getHttpServletRequest().getQueryString();

    }

    public String getRemoteUser()

    {

        return _getHttpServletRequest().getRemoteUser();

    }

    public boolean isUserInRole(String role)

    {

        return _getHttpServletRequest().isUserInRole(role);

    }

    public Principal getUserPrincipal()

    {

        return _getHttpServletRequest().getUserPrincipal();

    }

    public String getRequestedSessionId()

    {

        return _getHttpServletRequest().getRequestedSessionId();

    }

    public String getRequestURI()

    {

        return _getHttpServletRequest().getRequestURI();

    }

    public StringBuffer getRequestURL()

    {

        return _getHttpServletRequest().getRequestURL();

    }

    public String getServletPath()

    {

        return _getHttpServletRequest().getServletPath();

    }

    public HttpSession getSession(boolean create)

    {

        return _getHttpServletRequest().getSession(create);

    }

    public HttpSession getSession()

    {

        return _getHttpServletRequest().getSession();

    }

    public boolean isRequestedSessionIdValid()

    {

        return _getHttpServletRequest().isRequestedSessionIdValid();

    }

    public boolean isRequestedSessionIdFromCookie()

    {

        return _getHttpServletRequest().isRequestedSessionIdFromCookie();

    }

    public boolean isRequestedSessionIdFromURL()

    {

        return _getHttpServletRequest().isRequestedSessionIdFromURL();

    }

    public boolean isRequestedSessionIdFromUrl()

    {

        return _getHttpServletRequest().isRequestedSessionIdFromUrl();

    }

}

package javax.servlet.http;

import java.io.IOException;

import javax.servlet.ServletResponseWrapper;

public class HttpServletResponseWrapper extends ServletResponseWrapper

    implements HttpServletResponse

{

    public HttpServletResponseWrapper(HttpServletResponse response)

    {

        super(response);

    }

    private HttpServletResponse _getHttpServletResponse()

    {

        return (HttpServletResponse)super.getResponse();

    }

    public void addCookie(Cookie cookie)

    {

        _getHttpServletResponse().addCookie(cookie);

    }

    public boolean containsHeader(String name)

    {

        return _getHttpServletResponse().containsHeader(name);

    }

    public String encodeURL(String url)

    {

        return _getHttpServletResponse().encodeURL(url);

    }

    public String encodeRedirectURL(String url)

    {

        return _getHttpServletResponse().encodeRedirectURL(url);

    }

    public String encodeUrl(String url)

    {

        return _getHttpServletResponse().encodeUrl(url);

    }

    public String encodeRedirectUrl(String url)

    {

        return _getHttpServletResponse().encodeRedirectUrl(url);

    }

    public void sendError(int sc, String msg)

        throws IOException

    {

        _getHttpServletResponse().sendError(sc, msg);

    }

    public void sendError(int sc)

        throws IOException

    {

        _getHttpServletResponse().sendError(sc);

    }

    public void sendRedirect(String location)

        throws IOException

    {

        _getHttpServletResponse().sendRedirect(location);

    }

    public void setDateHeader(String name, long date)

    {

        _getHttpServletResponse().setDateHeader(name, date);

    }

    public void addDateHeader(String name, long date)

    {

        _getHttpServletResponse().addDateHeader(name, date);

    }

    public void setHeader(String name, String value)

    {

        _getHttpServletResponse().setHeader(name, value);

    }

    public void addHeader(String name, String value)

    {

        _getHttpServletResponse().addHeader(name, value);

    }

    public void setIntHeader(String name, int value)

    {

        _getHttpServletResponse().setIntHeader(name, value);

    }

    public void addIntHeader(String name, int value)

    {

        _getHttpServletResponse().addIntHeader(name, value);

    }

    public void setStatus(int sc)

    {

        _getHttpServletResponse().setStatus(sc);

    }

    public void setStatus(int sc, String sm)

    {

        _getHttpServletResponse().setStatus(sc, sm);

    }

}

从这几个包装器的定义中能够发现:

(1)包装器分别实现被包装接口:ServletRequestWrapper实现了ServletRequest接口、ServletResponse Wrapper实现了ServletResponse接口、HttpServletRequestWrapper实现了HttpServletRequest接口、HttpServletResponseWrapper实现了HttpServletResponse接口;

(2)包装器的继承关系与被包装者的继承关系一致:因为HttpServletRequest和HttpServletResponse分别继承了ServletRequest和ServletResponse,因此HttpServletRequestWrapper和HttpServletResponse Wrapper也分别继承了ServletRequestWrapper和ServletResponseWrapper;

(3)在包装器中都定义了一个被包装者的对象,而且都经过包装器的构造函数传递初始值。HttpServletRequestWrapper和HttpServletResponseWrapper没有定义被包装者的对象,这是由于它们使用了其父类中的对象。

(4)包装器能够经过调用被包装对象的方法来实现其所声明的方法,也能够自行添加任何代码以覆盖其所包装对象的该方法,从而体现与被包装对象不一样的行为。

将包装器用到过滤器中,其工做原理是:过滤器接收传递进来的ServletRequest对象和ServletResponse对象,分别实现两个包装器将ServletRequest对象和ServletResponse对象包装起来,而且在包装器的相关方法中添加本身想要的行为,而后将包装器对象传递给Servlet,因为包装器对象都继承自被包装者,故Servlet仍是把包装器对象当成原始的对象进行调用,但实质上调用的是包装器的对应方法,从而过滤器达到了干涉Servlet处理请求/响应的目的。

仔细研究以上列出的四个包装器的方法会发现,这些方法并无添加任何本身的实现,而是单纯地调用被包装者的对应方法。这是由于Servlet提供这几个包装器只是一个包装器的默认实现,开发人员要开发本身的包装器的话,只须要继承对应的包装器而且覆盖本身感兴趣的方法便可,而不用本身去实现一个完整的包装器。

一个简单的例子就是利用过滤器添加日志功能。假设开发者但愿在Servlet发生重定向时记录日志,那就须要实现一个HttpServletResponseWrapper对原始的HttpServletResponse进行包装,实现以下:

示例8.2

public class LogHttpResponseWrapper extends HttpServletResponseWrapper {

public LogHttpResponseWrapper(HttpServletResponse response) {

super(response);

}

@Override

public void sendRedirect(String location) throws IOException {

Log("Redirect to: " + location);

super.sendRedirect(location);

}

}

这个包装器很简单,只须要继承HttpServletResponseWrapper对象,而且覆盖其sendRedirect(String location)方法便可,在方法中先记录日志,而后调用父类的sendRedirect(String location)方法。当该包装器对象被传递到Servlet时,Servlet若是调用sendRedirect(String location)方法对响应进行重定向,那么该包装器对象的该方法就会被调用。调用结果就是首先记录日志,而后调用原始被包装者的该方法,因此这个记录日志的过程对Servlet是彻底透明的。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

编写过滤器

 

Servlet过滤器必须符合Filter接口,全部实现了Filter接口的Java类均可以做为Servlet过滤器。以上面提到的记录日志的过滤器为例,其实现以下:

示例8.3

public class LogFilter implements Filter {

public void init(FilterConfig arg0) throws ServletException {

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException

{

HttpServletResponse res = (HttpServletResponse) arg1;

LogHttpResponseWrapper wrapper = new LogHttpResponseWrapper(res);

arg2.doFilter(arg0, wrapper);

}

public void destroy() {

}

}

Filter接口定义了三个方法,在实现类中分别实现这三个方法。

在init()中实现初始化操做,若是没有初始化操做则方法体为空。

在doFilter()方法中实现过滤方法体,更改请求/响应对象或构造包装器对象;在处理结束后必定要调用FilterChain.doFilter()操做,这表示将参数中给定的请求和响应对象传递到过滤器链的下一个过滤器/Servlet中;因此,若是在过滤方法中对请求/响应对象作了包装,此处就应该使用包装对象向下传递;

在destroy()方法实现一些清理操做,若是没有清理操做则方法体为空。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

部署和运行过滤器

 

同Servlet同样,过滤器编辑好后,若是没有部署到Tomcat中,Tomcat是不会知道过滤器存在的,过滤器也不会起到任何做用。将过滤器部署到Tomcat中的方法与Servlet同样:将其配置到web.xml文件中。

在web.xml文件中,web-app根元素的第一层子元素中能够定义filter元素,配置代码段以下例所示:

<filter>

        <filter-name>logFilter</filter-name>

        <filter-class>

          cn.csai.web.servlet. LogFilter

        </filter-class>

        <init-param>

          <param-name>logPrefix</param-name>

          <param-value>csai-log:</param-value>

        </init-param>

        <init-param>

          <param-name>max-line</param-name>

          <param-value>256</param-value>

        </init-param>

</filter>

<filter-mapping>

        <filter-name>logFilter</filter-name>

        <url-pattern>/*</url-pattern>

</filter-mapping>

filter元素定义Filter,filter-mapping定义须要过滤的请求的URL模式。其中,filter-name是该过滤器的名称,用于匹配filter元素和filter-mapping元素,同时用于当FilterConfig的getFilterName()方法被调用时返回该名称;filter-class表示该过滤器的实现类,该类必须实现了Filter接口;init-param定义了一系列初始化参数,能够经过FilterConfig的getInitParameter(String)方法和getInitParameterNames()方法得到;url-pattern定义了一个URL模式,只有与该URL模式匹配成功的请求才会通过该过滤器,这个url-pattern的格式和意义与Servlet配置中url-pattern的格式和意义相同。

将Filter部署到Tomcat中后,Filter就已经开始起做用了。当Tomcat处在运行状态时,向Tomcat提交符合Filter URL模式的请求就会激活Filter的doFilter()方法。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

Servlet实践

 

本章在前面几节中介绍了Servlet的一些基本概念以及Servlet中一些关键的接口和类。可是,若是只看到这些,读者可能对Servlet还只停留在概念认识的阶段,而离实际深刻使用Servlet技术还有必定的距离。本节将使用一些精心设计的实例向读者展现Servlet技术在实践中的使用方式和一些使用技巧。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

从头开发Servlet

 

可能在其余不少介绍Servlet的材料中告诉读者,开发Servlet的大体步骤以下:

新建一个Java类,继承HttpServlet类;

根据须要实现父类中的doGet()或者doPost()方法。

这样,在读者心目中可能会造成一个定势,就是全部的Servlet都必须继承HttpServlet类。实质上,这个认识是不正确的。

正如本章前面所介绍的,一个Java类是Servlet的充分必要条件就是该类或者其父类实现了Servlet接口。了解了这一点,读者就能够有充分的灵活性为所欲为地开发Servlet了。

前面在介绍Hello World Servlet时,实现了一个TestServlet,与其余大多数Servlet同样,它继承自HttpServlet。那么,如今咱们将要实现的HelloWorldPrinterServlet是一个不继承HttpServlet的Servlet,它的功能是不管接收到任何请求,都向客户端打印“Hello World”。这将是一个最基本最简单的Servlet。

首先,在ServletTest工程中新建一个Java类HelloWorldPrinterServlet,而且让这个类实现Servlet接口。实现Servlet接口的方法(可是全部方法体不包含任何实质内容)以下:

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class HelloWorldPrinterServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

}

}

理论上说,这就已经实现了一个完整的Servlet,若是将该Servlet部署到Web应用中,该Servlet就能够正常工做了,只是该Servlet不提供任何有意义的功能。参照TestServlet的部署,将HelloWorldPrinterServlet部署到应用的web.xml文件中:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" 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 http://java.sun.com /xml/ns/j2ee/web-app_2_4.xsd">

<display-name>ServletTest</display-name>

<servlet>

<description></description>

<display-name>HelloWorldPrinterServlet</display-name>

<servlet-name>HelloWorldPrinterServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.HelloWorldPrinterServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>HelloWorldPrinterServlet</servlet-name>

<url-pattern>/HelloWorldPrinterServlet/*</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

</web-app>

从新部署Web应用,而后访问URL:

http://localhost:8080/ServletTest/HelloWorldPrinterServlet

将得到一个空白页面,如图8.6所示。

因为当请求到达Servlet时,Servlet的service()方法会被调用,因此若是想要从Servlet得到响应,只要在service()方法体中添加适当的内容。例如,实现简单的利用HttpServletResponse参数向客户端打印一个HelloWorld字符串的过程以下:

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletResponse resp = (HttpServletResponse) arg1;

PrintWriter writer = resp.getWriter();

writer.println("Hello World");

writer.flush();

writer.close();

}

图8.6  访问HttpWorldPrinterServlet得到的页面

service()方法的参数是一个ServletRequest对象和一个ServletResponse对象,这两个参数类型是由Servlet接口定义的,在这里因为咱们要开发的Servlet是用于处理Http请求和响应的,因此这里的ServletRequest对象和ServletResponse对象确定分别是HttpServletRequest对象和HttpServletResponse对象,因此在使用这两个参数时能够直接对其进行造型;得到了resp对象后,取出其中的PrintWriter对象,该对象用于向客户端打印字符串输出;而后经过writer对象的println()方法输出字符串;最后刷新和关闭输出对象。

不用改变前面对web.xml的配置,直接从新部署Web应用,使用上面的URL访问HelloWorldPrinterServlet,得到的页面如图8.7所示:

图8.7  访问修改后的HttpWorldPrinterServlet得到的页面

假如使用继承自HttpServlet的方法来实现具备相同功能的Servlet,其实现代码大体以下:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class HelloWorldPrinterServlet extends HttpServlet {

@Override

protected void doDelete(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

printHelloWorld(resp);

}

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

printHelloWorld(resp);

}

@Override

protected void doHead(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

printHelloWorld(resp);

}

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

printHelloWorld(resp);

}

@Override

protected void doPut(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

printHelloWorld(resp);

}

private void printHelloWorld(HttpServletResponse resp) throws IOException {

PrintWriter writer = resp.getWriter();

writer.println("Hello World");

writer.flush();

writer.close();

}

也就是说,不管HTTP请求是Delete、Get、Head、Post或Put,该Servlet的处理都是调用printHelloWord()经过响应向客户端输出“Hello World”。比较这个实现和前面实现,前面的实现还显得稍微简单,因此并非实现全部Servlet时都要经过继承HttpServlet来实现的,有时候直接实现Servlet接口还会更简单并且更直接。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

在Servlet中使用ServletConfig

 

ServletConfig表示一个Servlet的配置信息,从ServletConfig中能够得到当前Servlet在web.xml中的一些配置信息。一个Servlet的ServletConfig信息会在Servlet被建立时由Servlet容器进行建立和初始化,经过Servlet的init(ServletConfig)方法传递给Servlet。

1.在Servlet中引入ServletConfig

若是经过实现Servlet接口来开发Servlet,那么程序员就须要在Servlet中管理ServletConfig对象。本身在代码中管理ServletConfig很是简单,模仿GenericServlet类的方法,在Servlet中定义一个ServletConfig对象,而后在init(ServletConfig)方法中将传入的ServletConfig赋给定义的对象,而且在getServletConfig()方法中返回定义的对象。代码以下:

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class HelloWorldPrinterServlet1 implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

this.config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

}

}

这样,在service()方法中就能够直接经过config对象得到Servlet的配置信息。

若是使用继承HttpServlet的方法开发Servlet,那么在Servlet中引入ServletConfig就更简单了,由于HttpServlet的父类GenericServlet已经替咱们完成了管理ServletConfig的工做,开发人员只须要在待开发的Servlet中直接使用getServletConfig()或super.getServletConfig()得到ServletConfig对象便可。

2.使用ServletConfig

在Servlet中引入ServletConfig的主要目的是经过ServletConfig对象得到Servlet的配置信息,而后根据配置信息决定Servlet的行为。开发人员能够编写相对通用的Servlet,让Servlet的行为可根据配置信息不一样而不一样,当使用Servlet时可根据具体的需求添加不一样的配置信息。

例如,在HelloWorldPrinterServlet的基础上提升一点系统的需求:当有请求到达时,向客户端输出一个消息,该消息可经过应用的web.xml进行配置。

根据这个需求,能够考虑Servlet提供的一种初始化参数机制。Servlet在配置时,容许向Servlet中配置初始化参数,这些参数由参数名和参数值组成,这些参数能够在Servlet运行期间被Servlet得到。因此,能够在Servlet的初始化参数中定义待输出的消息内容,而后在Servlet运行期间经过ServletConfig对象读取该消息内容,而后输出到客户端。

将该Servlet命名为MessagePrinterServlet;为该Servlet定义一个名为message的初始化参数用于定义待输出的消息。那么,在web.xml中添加以下一段配置信息:

<servlet>

<description></description>

<display-name>MessagePrinterServlet</display-name>

<servlet-name>MessagePrinterServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.MessagePrinterServlet</servlet-class>

<init-param>

            <param-name>message</param-name>

            <param-value>Hello China</param-value>

</init-param>

</servlet>

<servlet-mapping>

<servlet-name>MessagePrinterServlet</servlet-name>

<url-pattern>/MessagePrinterServlet/*</url-pattern>

</servlet-mapping>

在包cn.csai.web.servlet中新建一个MessagePrinterServlet,实现内容以下:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class MessagePrinterServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

this.config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

writer.println(config.getInitParameter("message"));

writer.flush();

writer.close();

}

}

该类自行管理了ServletConfig对象,而且在service方法中经过config读取参数名为message的初始化参数,而且将该参数值输出到客户端,根据上面的配置,将“Hello China”输出到客户端,如图8.8所示。

图8.8  经过MessagePrinterServlet输出Hello China

假如修改该Servlet的配置信息,则将message参数的值改成Hello America,以下所示:

<servlet>

<description></description>

<display-name>MessagePrinterServlet</display-name>

<servlet-name>MessagePrinterServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.MessagePrinterServlet</servlet-class>

<init-param>

            <param-name>message</param-name>

            <param-value>Hello America</param-value>

        </init-param>

</servlet>

<servlet-mapping>

<servlet-name>MessagePrinterServlet</servlet-name>

<url-pattern>/MessagePrinterServlet/*</url-pattern>

</servlet-mapping>

而不改变MessagePrinterServlet的实现。从新部署Web应用的配置,而且重启Tomcat后,再次访问该Servlet得到页面如图8.9所示:

图8.9  经过MessagePrinterServlet输出Hello America

ServletConfig的主要功能是提供给Servlet用来获取Servlet在web.xml中配置的初始化参数信息。前面展现了如何经过参数名获取参数值,此外,ServletConfig还能够获取全部初始化参数的参数名和参数值。当程序员想获取全部的初始化参数或者在不知道特定参数的参数名时想经过遍历全部初始化参数的参数名获取参数参数值时,均可以经过ServletConfig的getInitParameterNames()方法得到全部初始化参数的一个Enumeration对象。

下面将展现一个InitParamPrinterServlet,该Servlet将打印全部初始化参数的参数值和参数名:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Enumeration;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class InitParamPrinterServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

this.config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

Enumeration<String> initParams = config.getInitParameterNames();

while(initParams.hasMoreElements()) {

String param = initParams.nextElement();

writer.println(param + " = " + config.getInitParameter(param));

writer.println("<br>");

}

writer.flush();

writer.close();

}

}

该Servlet的service()方法经过config的getInitParameterNames()方法得到一个Enumeration对象,该对象的每个成员是一个初始化参数的参数名,而后根据参数名再得到参数值,并将参数名和参数值以“参数名 = 参数值”的格式输出到客户端页面上。

将该Servlet配置到web.xml中,而且添加一些初始化参数,以下所示:

<servlet>

<description>

</description><display-name>InitParamPrinterServlet</display-name>

<servlet-name>InitParamPrinterServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.InitParamPrinterServlet</servlet-class>

<init-param>

            <param-name>param1</param-name>

            <param-value>value1</param-value>

       </init-param>

        <init-param>

            <param-name>param2</param-name>

            <param-value>5</param-value>

        </init-param>

        <init-param>

            <param-name>param3</param-name>

            <param-value>1/2</param-value>

        </init-param>

</servlet>

<servlet-mapping>

<servlet-name>InitParamPrinterServlet</servlet-name>

<url-pattern>/InitParamPrinterServlet/*</url-pattern>

</servlet-mapping>

这里配置了三个初始化参数,读者也能够定义本身想定义的任何参数。从新部署Web应用并重启Tomcat后,得到的页面如图8.10所示:

图8.10  InitParamPrinterServlet响应页面

另外,程序员还能够在Servlet的配置中为Servlet添加一个名字,而后经过ServletConfig的getServletName()方法得到,以下NamePrinterServlet将Servlet的名字做为页面标题输出到客户端。

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class NamePrinterServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

this.config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

writer.println("<h1>" + config.getServletName() + "</h1>");

writer.flush();

writer.close();

}

}

配置片断以下:

<servlet>

<description></description>

<display-name>NamePrinterServlet</display-name>

<servlet-name>NamePrinterServlet</servlet-name>

<servlet-class>cn.csai.web.servlet.NamePrinterServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>NamePrinterServlet</servlet-name>

<url-pattern>/NamePrinterServlet/*</url-pattern>

</servlet-mapping>

从新部署Web应用,重启Tomcat,访问该Servlet得到的页面如图8.11所示:

图8.11  NamePrinterServlet响应页面

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用ServletContext获取信息

 

顾名思义,ServletContext表示了一个Servlet运行的上下文环境,经过该对象能够得到Servlet乃至整个Tomcat所运行的大环境的信息。ServletContext接口的定义已在前面进行了介绍,下面咱们分别用几个示例展现如何在Servlet中使用ServletContext。

1.使用ServletContext得到服务器的信息

ServletContext第一个功能就是能够经过它获取服务器的信息,这也是在Servlet中惟一一个能够获取服务器信息的途径。ServletContext的getServerInfo()方法经过一个字符串返回当前所运行的Web服务器的信息;getMajorVersion()和getMinorVersion()分别获取当前的Servlet容器所支持的Servlet的主版本号和次版本号。下面的GetServerInfoServlet展现了这几个方法的用法以及返回的值:

package cn.csai.web.servlet.context;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class GetServerInfoServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

PrintWriter writer = arg1.getWriter();

writer.print("Server Info: " + context.getServerInfo());

writer.print("<br>");

writer.print("Version: " 

+ context.getMajorVersion() + "." + context.getMinorVersion());

writer.flush();

writer.close();

}

}

显示页面如图8.12所示:

图8.12  GetServerInfoServlet页面效果

2.使用ServletContext得到Web应用的信息

这里所说的Web应用就是指Servlet所在的Web应用。在Servlet中,经过ServletContext对象能够得到当前Web应用的一些信息。经过ServletContext的getServletContextName()方法能够得到当前ServletContext的上下文路径名;经过ServletContext的getMimeType(file)能够得到指定文件名的文件所属的MIME类型。下面的GetApplicationInfoServlet展现了这些方法的用法:

package cn.csai.web.servlet.context;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class GetApplicationInfoServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

PrintWriter writer = arg1.getWriter();

writer.print("Servlet Context Name: " + context.getServletContextName());

writer.print("<br>");

writer.print("doc MIME: " + context.getMimeType("file.doc"));

writer.flush();

writer.close();

}

}

显示的页面如图8.13所示:

图8.13  GetApplicationInfoServlet页面效果

第一行显示的是当前的Web应用的上下文路径,即ServletTest;第二行显示的是doc文件的MIME类型。

3.使用ServletContext得到资源的信息

ServletContext还有一个很重要的功能就是获取Web应用中全部资源的信息。经过ServletContext对象提供的方法,Servlet能够得到当前Web应用中各个资源的包含关系、每一个资源的本地地址和URL,以及得到每一个资源的输入流对象等。

ServletContext的 getResourcePaths(path)能够得到指定URI下全部资源的URI;getRealPath(uri)能够得到指定URI所表明的资源在本地文件系统中的路径; getResource(uri)能够得到指定URI所表明的资源的全局URL;getResourceAsStream(uri)能够得到指定URI所表明资源的输入流,若是资源是一个目录则输入流为空。

下面的GetResourceInfoServlet得到当前Web应用根目录下全部一级资源的资源名、本地路径、URL和读取资源的InputStream对象:

package cn.csai.web.servlet.context;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Set;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class GetResourceInfoServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

PrintWriter writer = arg1.getWriter();

Set<String> resources = context.getResourcePaths("/");

writer.print("All resources in root directory:");

writer.print("<br>");

for (String res : resources) {

writer.print("<br>");

writer.print(res);

writer.print("<br>");

writer.print("------------");

writer.print("<br>");

writer.print("Path on local machine: " + context.getRealPath(res));

writer.print("<br>");

writer.print("URL: " + context.getResource(res));

writer.print("<br>");

writer.print("Input stream object: " + context.getResourceAsStream(res));

writer.print("<br>");

}

writer.flush();

writer.close();

}

}

显示的页面如图8.14所示。

从图8.14中的运行结果能够发现,在ServletTest应用中,根目录下只有三个一级资源:WEB_INF目录、build.xml文件和index.html文件。WEB_INF所对应的本地路径为:D:\tomcat\webapps\Servlet Test\WEB-INF;访问该目录的URL是:jndi:/localhost/ServletTest/WEB-INF/;因为该资源是一个目录,没法读取,因此读取该资源的InputStream对象是空。其余两个资源相似。

4.使用ServletContext进行请求前转

利用ServletContext除了能够得到资源的信息外,还能够将请求直接前转到指定的资源文件或者Servlet。ServletContext的getRequestDispatcher(uri)返回一个到指定URI所指向资源的RequestDispatcher对象,getNamedDispatcher(servlet)返回一个到指定Servlet的RequestDispatcher对象。经过返回的RequestDispatcher对象,Servlet能够将请求前转到指定的资源或者将资源的响应嵌入到当前响应中。

图8.14  GetResourceInfoServlet页面效果

使用RequestDispatcher对象进行前转和经过ServletResponse的sendRedirect()方法进行重定向,这两个操做是不同的。重定向是针对当前的请求向客户端返回一个响应,响应消息中告诉客户端须要从新请求另一个URL,而后客户端再向指定的URL从新发出请求从而得到响应;而前转是Servlet/JSP在得到请求后直接在Web服务器内部向另一个资源发出请求而且将请求结果返回给客户端。虽然前转和重定向都是将另一个资源的响应返回给客户端,可是前转操做只有一次请求/响应过程而重定向会有两次请求/响应过程。

下面的FileDispatcherServlet将客户端请求前转到应用中的一个文件:

package cn.csai.web.servlet.context;

import java.io.IOException;

import javax.servlet.RequestDispatcher;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class FileDispatcherServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

RequestDispatcher disp = context.getRequestDispatcher("/Blue hills.jpg");

disp.forward(arg0, arg1);

}

}

将Windows图片收藏夹中自带的Blue hills.jpg复制到ServletTest工程的WebContent中,而后从新发布Web应用到Tomcat中。发布完后,图片就会在ServletTest应用的根目录中。打开Tomcat,访问FileDispatcherServlet,会获得如图8.15所示的页面。

图8.15  FileDispatcherServlet页面效果

能够发现得到的响应页面中包含一个图片,这个图片就是Blue hill.jpg。

下面的ServletDispatcherServlet将客户端请求前转到另外一个Servlet:

package cn.csai.web.servlet.context;

import java.io.IOException;

import javax.servlet.RequestDispatcher;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class ServletDispatcherServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

RequestDispatcher disp = 

context.getNamedDispatcher("GetResourceInfoServlet");

disp.forward(arg0, arg1);

}

}

从代码中能够发现,该Servlet将请求前转到前面开发的GetResourceInfoServlet。访问该Servlet得到的页面如图8.16所示。

图8.16  ServletDispatcherServlet页面效果

能够发现,得到页面同访问GetResourceInfoServlet得到的页面效果如出一辙。

5.使用ServletContext记录日志

Tomcat自带有日志功能,在Tomcat的logs目录中保存了Tomcat的全部日志文件。默认的日志文件分为5类,分别以以下名字开头:admin、catalina、host-manager、localhost和manager,后面是当天的日期。这些日志会记录Tomcat服务器的启动、中止、出现的错误信息、打印的提示信息,等等。

经过ServletContext对象,开发人员就一样能够利用Tomcat的这个日志功能为本身开发的代码记录日志。ServletContext的几个log()经过不一样的参数向localhost类日志中写入日志信息。

下面的LogServlet展现了如何使用ServletContext对象进行日志记录:

package cn.csai.web.servlet.context;

import java.io.IOException;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class LogServlet implements Servlet {

private ServletConfig config;

public void destroy() {

}

public ServletConfig getServletConfig() {

return config;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

config = arg0;

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

ServletContext context = getServletConfig().getServletContext();

context.log("cn.csai.web.servlet.context.LogServlet is running!");

try {

String s = null;

s.length();

} catch(NullPointerException e) {

context.log(e, "s is null");

}

}

}

在该Servlet中,首先经过调用log(String)方法记录了一条运行信息,而后在捕捉到Exception时经过log(Exception, String)方法记录了一条异常信息。为了查看该Servlet记录日志的效果,能够在准备访问Servlet以前,先将Tomcat原有的日志文件所有删除。而后启动Tomcat再访问该Servlet,访问完后查看logs目录中会有几个日志文件,其中有一个文件名是localhost+日期,打开该文件,内容以下:

Jun 23, 2008 3:25:13 PM org.apache.catalina.core.ApplicationContext log

INFO: cn.csai.web.servlet.context.LogServlet is running!

Jun 23, 2008 3:25:13 PM org.apache.catalina.core.ApplicationContext log

SEVERE: s is null

java.lang.NullPointerException

at cn.csai.web.servlet.context.LogServlet.service(LogServlet.java:39)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263)

at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)

at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:584)

at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)

at java.lang.Thread.run(Thread.java:595)

这就是记录LogServlet调用log()方法记录的日志内容。其中前两行是第一次调用生成的日志,第一行是日期,第二行是日志级别(INFO)和日志消息内容;后面的是第二次调用生成的日志,一样是以一行日期开始,后面有日志级别(SEVERE)、日志消息内容以及所报的异常的栈信息。这两个日志的级别是不同的,调用log(String)记录的日志是INFO级别的,调用log(Exception, String)记录的日志是SEVERE级别的。

 

 

在HTTP Servlet体系中,HttpServletResponse表明一个向客户端发出的响应,它与HTTP响应消息不能彻底等同,可是因为HTTP协议的特色,对HttpServletResponse对象的相关操做基本上能够与修改HTTP响应消息的相关内容对应起来。因此,在编写Servlet时,对响应所作的任何操做基本上都应该从HttpServletResponse对象着手,该对象对HTTP响应消息的内容和行为提供了很是好的封装。

响应的主要目的就是向客户端返回响应消息,最多见的响应消息就是文本消息(HTML页面内容),还有多是二进制形式内容、错误信息或者重定向操做等。

1.返回文本消息

使用文本消息响应客户端是最经常使用的一种响应方式,前面的几个Servlet也都是使用这种方式。

经过HttpServletResponse的getWriter()方法能够得到一个向客户端输出字符信息的输出流,它是一个PrintWriter对象。经过这个对象,Servlet能够向客户端输出文本响应消息,响应消息的文本内容一般都是按HTML结构进行组织的。以下例的HtmlResponseServlet向客户端返回一个HTML页面的响应消息。

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class HtmlResponseServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

writer.write(getHtml());

writer.flush();

writer.close();

}

private String getHtml() {

StringBuilder html = new StringBuilder();

html.append("<html>");

html.append("<head><title>HtmlResponseServlet</title></head>");

html.append("<body>");

html.append("<h1 align=\"center\">HtmlResponseServlet</h1>");

html.append("<hr size=\"3\"/>");

html.append("<div align=\"center\">Content</div>");

html.append("</body>");

html.append("</html>");

return html.toString();

}

}

该Servlet的getHtml()是一个字符串,字符串的内容是一个HTML文档,service()方法经过PrintWriter将字符串输出到客户端。显示的页面如图8.17所示:

图8.17  HtmlResponseServlet响应页面

查看该页面的源文件,以下所示:

<html>

        <head><title>HtmlResponseServlet</title></head>

        <body>

                <h1 align="center">HtmlResponseServlet</h1>

                <hr size="3"/>

                <div align="center">Content</div>

        </body>

</html>

能够发现,该源文件的内容就是getHtml()方法返回的字符串的内容。

2.返回二进制数内容

Servlet不只能够向客户端返回HTML格式的文本消息,还能够返回二进制数的内容,例如图片、Word文档等。

在Java中,字符输出流是用Writer,而字节输出流就是OutputStream。针对二进制数的字节内容,HttpServletResponse也提供了一个getOutputStream()方法,该方法返回一个ServletOutputStream对象,经过这个对象Servlet能够向客户端返回一个二进制数的响应消息。

为了演示这个功能,首先在工程中新建一个包cn.csai.web.servlet.resource,挑选一个图片放在该包中,假设图片名取为test.jpg。而后编写一个名为PictureResponseServlet,以下所示:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.InputStream;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletOutputStream;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class PictureResponseServlet implements Servlet {

private static final String picPath = "/cn/csai/web/servlet/resource/test.jpg";

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

InputStream is = this.getClass().getResourceAsStream(picPath);

ServletOutputStream os = arg1.getOutputStream();

int avail = 0;

while((avail=is.available()) > 0) {

byte[] buff = new byte[avail];

is.read(buff);

os.write(buff);

}

os.flush();

is.close();

os.close();

}

}

访问该Servlet返回的页面如图8.18所示:

图8.18  PictureResponseServlet响应页面

3.返回错误信息

当Servlet在处理用户请求时,可能会遇到一些问题致使请求没法被正常处理,这些问题多是用户请求的格式不正确,也多是服务器端发生了一些异常。这种状况下,Servlet须要向客户端返回错误报告响应消息,这种错误报告响应消息具备不一样于正常响应的响应码(不一样类别响应码的含义在1.3.2节中已作介绍)。

HttpServletResponse提供了sendError()方法向客户端返回错误报告响应消息,该方法容许Servlet指定错误消息的错误码以及一个可选的错误文本信息。以下的SendErrorResponseServlet向客户端返回一个错误报告响应消息,错误码表示用户的请求消息不正确,而且反馈一个错误信息文本。

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

public class SendErrorResponseServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletResponse resp = (HttpServletResponse) arg1;

resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request!");

}

}

响应页面如图8.19所示,sendError()方法中提供的提示信息“Bad Request!”会显示在错误提示页面中:

4.返回重定向操做

重定向操做就是向客户端返回一个响应,响应要求客户端浏览器的访问请求从新指向另外一个URL。例如,将请求一个Servlet的请求从新定向到另外一个Servlet或另外一个图片,甚至是另外一个网站的某个页面。

图8.19  SendErrorResponseServlet响应页面

HttpServletResponse提供了sendRedirect()方法,该方法接受一个字符串参数,该参数表示要重定向的URL。下面的SendRedirectResponseServlet将用户请求重定向到csai网的主页:

package cn.csai.web.servlet;

import java.io.IOException;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

public class SendRedirectResponseServlet implements Servlet {

private static final String redirectURL = "http://www.csai.cn";

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletResponse resp = (HttpServletResponse) arg1;

resp.sendRedirect(redirectURL);

}

}

访问该Servlet后,请求会被重定向到“http://www.csai.cn”,若是客户端能够打开csai的主页,那么响应的页面将会是csai的主页,地址栏也会显示csai主页的地址,如图8.20所示:

图8.20  SendRedirectResponseServlet响应页面

5.设置响应消息的内容类型

在介绍HTTP消息的头域时,提到过一个Content-Type头域,该头域说明了消息内容的MIME类型,这是HTTP消息的发送方告知HTTP消息接收方有关消息体格式的惟一方法;HTTP消息接收方会根据Content-Type中携带的MIME类型决定在接收到消息体后对接收到的内容所采起的处理方式,能够是在浏览器中打开、调用某个本地应用程序打开、直接保存为本地文件,等等。

Servlet能够显式地为响应消息设置一种MIME类型,若是Servlet没有显式地设置,那么客户端就没法获知消息体的MIME类型,这种状况下大部分浏览器会根据得到的消息体的实际内容判断消息体的MIME类型,而且使用合适的方式对消息体进行处理。

前面在介绍Tomcat的web.xml文件配置时,发如今${TOMCAT_HOME}\conf目录下的web.xml文件中列出了许多有关文件后缀与MIME类型的映射关系(mime-mapping元素,见7.3.2节),这些映射关系是用于配置DefaultServlet行为的,当程序员本身开发的Servlet向客户端返回文件时这些设置并不会起做用。当客户端请求的是某个文件(而不是Servlet)时,DefaultServlet会根据所请求文件的后缀得到所对应的MIME类型,并将这个类型设置到响应中。假如请求访问的是程序员本身开发的Servlet时,这些映射并不会起做用。例如PictureResponseServlet最终向客户端返回了一个图片,可是Tomcat并不会自动向响应消息设置image/jpeg的MIME类型,由于读取二进制数内容以及返回二进制数内容的工做都是由程序员的Servlet负责的,Tomcat并不知道二进制数消息体的具体类型,并且也没有机会设置MIME类型。图片返回给客户端后,之因此IE可以将图片正确显示,那是由于IE经过分析返回的二进制数内容判断出来了文件的类型(大部分类型文件的前几个字节都具备必定的特征)。并且在有些浏览器中(例如IE),本身判断得到的MIME类型有可能还会覆盖掉Servlet显式设置的类型,这取决于浏览器的实现。

例如,下面的XMLDisplayServlet向客户端输出一段文本:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class XMLDisplayServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

writer.write(getContent());

writer.flush();

writer.close();

}

private String getContent() {

StringBuilder html = new StringBuilder();

html.append("<persons>");

html.append("<person id=\"1\">");

html.append("<name>kevin</name>");

html.append("<age>26</age>");

html.append("</person>");

html.append("<person id=\"2\">");

html.append("<name>tom</name>");

html.append("<age>22</age>");

html.append("</person>");

html.append("</persons>");

return html.toString();

}

}

文本内容是:

<persons>

        <person id="1">

                <name>kevin</name> 

                <age>26</age> 

        </person>

        <person id="2">

                <name>tom</name> 

                <age>22</age> 

        </person>

</persons>

若是不设置响应的MIME类型,显示的页面如图8.21所示:

因为IE将返回响应的内容自动识别为“text/html”MIME类型,也就是说,IE将返回的内容按HTML进行显示,因此全部的标签以及回车都被忽略了。

图8.21  XMLDisplayServlet响应页面

可是假如在service()中将响应的MIME类型显式地设置为“application/xml”,那么IE就会按XML显示。因此将XMLDisplayServlet修改成以下的XMLDisplayServlet2:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class XMLDisplayServlet2 implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

arg1.setContentType("application/xml");

writer.write(getContent());

writer.flush();

writer.close();

}

private String getContent() {

StringBuilder html = new StringBuilder();

html.append("<persons>");

html.append("<person id=\"1\">");

html.append("<name>kevin</name>");

html.append("<age>26</age>");

html.append("</person>");

html.append("<person id=\"2\">");

html.append("<name>tom</name>");

html.append("<age>22</age>");

html.append("</person>");

html.append("</persons>");

return html.toString();

}

}

其响应页面如图8.22所示:

图8.22  XMLDisplayServlet2响应页面

因为在XMLDisplayServlet2中设置了MIME类型为XML,因此IE就会以XML的格式正确显式内容。

6.设置响应消息的字符编码

若是返回的响应消息是文本类型,那么浏览器就会涉及使用哪种字符编码对消息中的文本进行解析的问题,这种情形通常会有三种解决方式:

浏览器使用本地操做系统默认的编码方式:这种方式最简单并且对响应提供者也没有限制,但在互联网上可能会出现使用任意一种编码的响应文本,因此这种方式就限制了浏览器显式其余编码的能力。

浏览器根据内容猜想编码方式:虽然在不少浏览器中已经提供了这种功能,可是并不能依赖于这种功能,由于毕竟是由浏览器猜想的,可能会出现猜想不许确的状况。

响应提供者在响应消息中指定编码方式:这是一种最直接也是使用最广泛的方式,这种方式能够根据相应提供者的意图对响应文本进行解析,可是就对响应提供者提出了要求。

实际上,在现实的使用中,是将这三种方式共同使用的:若是响应中指定了编码方式,就按指定的编码方式解析响应,不然浏览器会猜想一种编码方式,若是提供的信息不足以猜想出编码方式,那么浏览器就会使用系统默认的编码方式解析。

可见,若是开发人员在编写Web系统时若是直接指定了编码方式,将大大简化了浏览器的工做量,并且也能保证须要显示的内容可以被正确解析。

前面提到了HTTP响应消息用Content-Type头域指定响应内容的MIME类型,但同时Content-Type头域还会指定一个charset,例如:

Content-Type: text/html; charset=iso-8859-1

这个HTTP消息头域指定了消息内容的MIME格式是text/html,内容编码为ISO—8859—1。

ServletResponse的setCharacterEncoding(String encoding)方法是用来设置响应的字符编码的,在Servlet中直接调用ServletResponse的改方法设置所使用的编码方式,以下面的SetEncodingServlet所示:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class SetEncodingServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

arg1.setCharacterEncoding("UTF-8");

writer.write(getMessage());

writer.flush();

writer.close();

}

private String getMessage() {

StringBuffer sb = new StringBuffer();

sb.append("<html><head>");

sb.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" );

sb.append("</head><body><h1>中文</h1></body></html>");

return sb.toString();

}

}

其响应页面如图8.23所示:

7.向响应消息中添加头域

HTTP的响应消息由响应头和响应消息体组成,响应头经过头域来设定响应消息的一系列属性。前面讲到的设置响应消息的内容类型就是经过在响应消息的头域中添加Content-Type头域实现的,以及返回重定向消息时重定向的URL也是经过设置Location头域实现的。

图8.23  SetEncodingServlet响应页面

对于一些特殊的而且经常使用的头域HttpServletResponse提供了专门的方法用于设置,同时HttpServletResponse还提供了通用的方法用于设置任意的头域,这些方法包括:

经过这些方法,开发人员能够向响应消息中添加任何头域,能够是HTTP协议中预约义的头域,也能够是产品自定义或者用户本身定义的头域。例以下面的SetHeaderServlet向响应消息中添加Last-Modified头域,以指定返回内容的最后修改时间:

package cn.csai.web.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Date;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

public class SetHeaderServlet implements Servlet {

private Date now;

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletResponse resp = (HttpServletResponse) arg1;

now = new Date();

resp.setDateHeader("Last-Modified", now.getTime());

PrintWriter writer = resp.getWriter();

writer.write(getContent());

writer.flush();

writer.close();

}

private String getContent() {

StringBuilder html = new StringBuilder();

html.append("Last-Modified:" + now.toString());

return html.toString();

}

}

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用HttpSession实现会话级信息管理

 

Session在HTTP协议中是一个很是重要的概念,本书已经在1.5.2节中介绍了Session的概念。在Servlet中,用HttpSession接口及其实现类用于表示Web开发中Session的概念,而且使用该接口提供的方法能够实现对会话的管理,以及存取会话级的属性。HttpSession接口的定义以下:

package javax.servlet.http;

import java.util.Enumeration;

import javax.servlet.ServletContext;

public interface HttpSession

{

    public abstract long getCreationTime();

    public abstract String getId();

    public abstract long getLastAccessedTime();

    public abstract ServletContext getServletContext();

    public abstract void setMaxInactiveInterval(int i);

    public abstract int getMaxInactiveInterval();

    public abstract Object getAttribute(String s);

    public abstract Enumeration getAttributeNames();

    public abstract void setAttribute(String s, Object obj);

    public abstract void removeAttribute(String s);

    public abstract void invalidate();

    public abstract boolean isNew();

}

其中:

getCreationTime():返回该session对象建立的时间。

getId():返回该session对象对应的Session ID。

getLastAccessedTime():返回该session对应的客户端最后一次访问服务器的时间。

getServletContext():返回该session对象对应应用的ServletContext对象。

setMaxInactiveInterval(int i):设置最大的不活动时间(session对象失效的最大间隔),以秒为单位。假如某个客户端持续不访问服务器的时间超过了设置的时间间隔,那么服务器就会让该客户端对应的session失效。

getMaxInactiveInterval():得到当前设置的最大不活动时间。

getAttribute(String s):得到该session中具备指定属性名的属性。

getAttributeNames():返回Enumeration对象,包含该session中全部属性的名称。

setAttribute(String s, Object obj):将属性名为s,属性值为obj的属性设置到session对象中。

removeAttribute(String s):删除具备属性名s的属性。

invalidate():将该session失效,并将任何绑定到该session的对象与之解除绑定。

isNew():判断该session是不是新建立的。若是某session已经被建立但并未与任何客户端关联,则认为该session为新建立的。

在进行Web开发时,HttpSession最常被使用在保存会话级信息,即向Session中设置和获取属性对象,Session比Servlet更高一个级别,能够协调各个Servlet之间的行为。例如,能够在一个Servlet里面设置属性而在另一个Servlet里面获取属性。

下面的SetSessionServlet和GetSessionServlet分别向Session中设置一个属性和取出该属性,属性名为Date:

SetSessionServlet

package cn.csai.web.servlet.session;

import java.io.IOException;

import java.util.Date;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

public class SetSessionServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletRequest req = (HttpServletRequest) arg0;

HttpSession session = req.getSession();

session.setAttribute("Date", new Date());

}

}

GetSessionServlet

package cn.csai.web.servlet.session;

import java.io.IOException;

import java.io.PrintWriter;

import java.util.Date;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpSession;

public class GetSessionServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletRequest req = (HttpServletRequest) arg0;

HttpSession session = req.getSession();

Date date = (Date) session.getAttribute("Date");

PrintWriter writer = arg1.getWriter();

if(null == date) writer.print("null");

else writer.print(date.toString());

writer.flush();

writer.close();

}

}

将两个Servlet部署到Tomcat中而后对实现进行验证。

首先,打开一个浏览器窗口而且访问GetSessionServlet,得到的响应页面如图8.24所示:

图8.24  GetSessionServlet响应页面

因为,并无向Session中设置Date属性,因此直接访问GetSessionServlet时Date属性为空。

而后,从新启动一个窗口,首先访问SetSessionServlet,该Servlet只在Session中设置一个Date属性,并不返回任何响应内容,因此访问该Servlet得到的页面是一个空白页面,如图8.25所示:

图8.25  SetSessionServlet响应页面

最后,直接在打开SetSessionServlet的窗口中访问GetSessionServlet。因为在当前会话中已经经过SetSessionServlet设置了Date属性,因此此时GetSessionServlet将显示以设置的属性的值。如图8.26所示:

图8.26  GetSessionServlet响应页面

此时,从新打开一个窗口,而后再访问GetSessionServlet时,得到页面与图8.19相同。这是由于,虽然已经经过SetSessionServlet设置了Date属性,可是那是在上一个Session中设置的,新打开一个浏览器窗口后,已经不是同一个Session了,在新的Session中因为并无经过访问SetSessionServlet设置Date属性,因此访问GetSessionServlet时得到的Date属性是一个空值。

这个例子比较简单,可是也在必定程度上反映了Session的概念以及Session在Servlet中的使用方式。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用Cookie在客户端存储信息

 

Cookie是服务器端要求浏览器在本地存储的一段简短的文本,而且浏览器会在适当的时候在再次访问服务器时携带这段文本。这种机制使服务器得以在客户机本地保存客户机的状态信息。

在Servlet中,Java提供了Cookie类用于实现这个概念,与HttpSession同样,Cookie位于javax.servlet.http包中。Cookie类的结构以下:

package javax.servlet.http;

import java.text.MessageFormat;

import java.util.ResourceBundle;

public class Cookie implements Cloneable {

    public Cookie(String name, String value);

    public void setComment(String purpose);

    public String getComment();

    public void setDomain(String pattern);

    public String getDomain();

    public void setMaxAge(int expiry);

    public int getMaxAge();

    public void setPath(String uri);

    public String getPath();

    public void setSecure(boolean flag);

    public boolean getSecure();

    public String getName();

    public void setValue(String newValue);

    public String getValue();

    public int getVersion();

    public void setVersion(int v);

    public Object clone();

}

其中:

Cookie(String name, String value):构造函数,参数是该Cookie的名和值;

SetCommen(String purpose):一段注释用于说明该Cookie的目的;

getComment():返回该Cookie的注释;

setDomain(String pattern):设置该Cookie的域,参数是一个模式;

getDomain():返回该Cookie的域;

setMaxAge(int expiry):设置该Cookie最大的有效时长,单位是秒;

getMaxAge():得到该Cookie的最大有效时长;

setPath(String uri):设置该Cookie路径的URL;

getPath():得到该Cookie的路径;

setSecure(boolean flag):设置该Cookie的安全性,为浏览器提供指示,是否该Cookie只能在安全协议下使用;

getSecure():得到该Cookie的安全性设置;

getName():得到该Cookie的名称;

setValue(String newValue):为该Cookie设置新值;

getValue():得到该Cookie的值;

getVersion():得到该Cookie所使用的版本;

setVersion(int v):设置该Cookie所使用的版本;

clone():克隆一个Cookie对象。

在第1章介绍Cookie基本知识时已经提到,一个Cookie的基本内容包括:名称、值、域、过时时间和路径。从Cookie类的结构也能够发现,Java的Cookie类也基本上是对这些属性的设置和获取。

下面的SetCookieServlet演示了如何经过响应向客户端设置Cookie:

package cn.csai.web.servlet.cookie;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletResponse;

public class SetCookieServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

HttpServletResponse resp = (HttpServletResponse) arg1;

String cookieName = "TestCookieName";

String cookieValue = "TestCookieValue";

String cookieDomain = "csai.cn";

String path = "/";

int maxAge = 60 * 60;

Cookie c = new Cookie(cookieName, cookieValue);

c.setDomain(cookieDomain);

c.setPath(path);

c.setMaxAge(maxAge);

resp.addCookie(c);

PrintWriter writer = resp.getWriter();

writer.write("Set " + cookieName + "=" + cookieValue);

writer.flush();

writer.close();

}

}

该Servlet向响应中设置了一个Cookie,该Cookie设置TestCookieName=TestCookieValue,域为“csai.cn”,path为“/”,过时时间为1个小时。该Cookie设置至关于在HTTP响应消息头中添加:

Set-Cookie: TestCookieName=TestCookieValue; Domain=csai.cn; Expires=Tue, 17-Jun-2008 09:37:43 GMT; Path=/

其中Expires的值是根据响应发生的时间和设置的1个小时过时时间计算而得到的,因此每次执行得到的值会不同。设置好后,若是用户在Tue, 17-Jun-2008 09:37:43 GMT以前访问csai.cn域名下的任何资源都会在请求消息头携带TestCookieName=TestCookieValue的Cookie项,以下所示:

Tue, 17-Jun-2008 09:37:43 GMT

以下的PrintCookieServlet从用户的请求中获取携带的Cookie,而且打印出每个Cookie的名和值:

package cn.csai.web.servlet.cookie;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.Servlet;

import javax.servlet.ServletConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

public class PrintCookieServlet implements Servlet {

public void destroy() {

}

public ServletConfig getServletConfig() {

return null;

}

public String getServletInfo() {

return null;

}

public void init(ServletConfig arg0) throws ServletException {

}

public void service(ServletRequest arg0, ServletResponse arg1)

throws ServletException, IOException

{

PrintWriter writer = arg1.getWriter();

HttpServletRequest req = (HttpServletRequest) arg0;

Cookie[] cookies = req.getCookies();

writer.write("All Cookies: <br>");

if (null != cookies) {

for (Cookie c : cookies) {

writer.write(c.getName() + "=" + c.getValue() + "<br>");

}

}

writer.flush();

writer.close();

}

}

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用Servlet Filter实现登陆验证

 

Servlet Filter常被用于对过滤特定的请求和响应,一般一个Filter会具备一种特定的功能。多个Filter常以链的形式链接起来对请求和响应进行做用,但Filter之间是相互独立的,每一个Filter并不须要知道其余Filter的存在。Filter在Java Web编程中是很是重要的,其中一个最广泛的使用就是用来实现登陆验证。

登陆验证的功能是比较容易理解的。假设须要开发一个很是简单的SecretInfo系统,系统需求以下。

SecretInfo系统只有两个页面,/login.htm和/content/view.jsp,/login.htm提供登陆入口要求用户提交用户名和密码。若是用户提供的用户名和密码正确将会进入到/content/view.jsp页面,该页面会显示一段秘密的文本;若是用户提供的用户名密码错误,将不能看到一段秘密的文本。这个系统的需求能够说很是的简单,很快就能够拿出一个解决方案:login.htm提供一个登陆界面,而且将登陆信息提交给LoginServlet。LoginServlet用于判断登陆信息是否正确,若是登陆信息正确则将用于请求重定向到/content/view.jsp,不然将用户请求重定向回login.htm。/content/view.jsp只负责显示一段秘密的文本。具体实现以下:

login.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4 /loose.dtd">

<html>

<head>

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

<title>Insert title here</title>

</head>

<body>

<form action="LoginServlet">

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

密码: <input type="password" name="password"><br>

<input type="submit" value="提交">

</form>

</body>

</html>

view.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4 /loose.dtd">

<html>

<head>

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

<title>Insert title here</title>

</head>

<body>

A Secret Message.

</body>

</html>

LoginServlet

package cn.csai.web.secretinfo;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

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

String password = req.getParameter("password");

if (null != username && null != password && username.equals("admin")

&& password.equals("admin"))

resp.sendRedirect("content/view.jsp");

else

resp.sendRedirect("login.htm");

}

}

为了简单起见,以上提供的实现都只是最简单的实现。view.jsp提供的一段秘密文本是“A Secret Message.”,LoginServlet也只是简单的判断用户名和密码是否都等于“admin”。配置好web.xml,并将该应用部署到Tomcat中后,实验该系统的功能。打开login.htm如图8.27所示:

图8.27  login.htm页面

在用户名和密码输入框中输入任意的非“admin”的字符,页面将仍然返回到login.htm。若是用户名和密码都输入admin,将打开view.jsp页面,如图8.28所示。

图8.28  view.jsp页面

截止到目前的测试状况看,系统的工做与需求是一致的,但对于别有用心的用户,该系统存在的一个漏洞将是致命的:不用登陆也能够看到这段秘密的文本。如今从新打开一个浏览器,直接把浏览器地址栏中URL后面部分的“login.htm”改成“content/view.jsp”,view.jsp就会被打开,并且这一段秘密的文本也会被发现。这也就是说用户能够不须要用户名和密码而直接登陆系统。这在任何经过用户名和密码来管理权限的系统来讲都是一个致命的缺陷。并且,在不少Web系统中,使用用户名和密码进行权限管理是很是经常使用的。下面就介绍一种方法,在前面开发的系统的基础上修复这个缺陷。

这个方法就是使用Servlet Filter来验证登陆:当用户登陆成功后,在用户Session中添加一个属性用于指示用户已经成功登陆;为受限资源(须要登陆才能查看的资源,例如view.jsp)部署一个Servlet Filter,当用户访问这些受限资源时查看用户的Session是否已经登陆成功,若是已登陆成功则容许经过Servlet Filter,不然将用户响应重定向到一个错误页面。

在这个方法中,要注意如下几点。

1.login.htm和view.jsp都不须要改变任何内容。

2.因为在用户登陆成功后须要在Session中添加属性,因此须要在LoginServlet中作相应修改。以下所示。

package cn.csai.web.secretinfo;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

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

String password = req.getParameter("password");

if (null != username && null != password && username.equals("admin")

&& password.equals("admin"))

{

req.getSession().setAttribute("Loged", Boolean.TRUE);

resp.sendRedirect("content/view.jsp");

}

else

resp.sendRedirect("login.htm");

}

}

当用户登陆成功时,首先在Session中添加一个属性Loged=true。这个属性名和属性值能够随意设置,只要在检查登陆状况时获取相同的属性就能够了。

3.实现一个错误信息提示页面error.htm,提示用户“未登陆或者登陆已过时”。这只须要一个很是简单的HTML页面便可,以下所示。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/ loose.dtd">

<html>

<head>

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

<title>Insert title here</title>

</head>

<body>

未登陆或者登陆已过时。回到<a href="login.htm">登陆</a>页面。

</body>

</html>

在页面中提示错误信息,而且提供一个连接到登陆页面。

4.实现一个LoginFilter,使其对访问view.jsp的请求进行过滤,在Filter中检查当前Session是否已登陆,若是没有登陆则将用户响应重定向到error.htm。这是其中最重要的环节,可是LoginFilter的实现能够很是简单,以下所示。

package cn.csai.web.secretinfo;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class LoginFilter implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException

{

HttpServletRequest req = (HttpServletRequest) arg0;

Object loged = req.getSession().getAttribute("Loged");

if(null == loged) {

HttpServletResponse resp = (HttpServletResponse) arg1;

resp.sendRedirect(req.getContextPath() + "/error.htm");

}

else {

arg2.doFilter(arg0, arg1);

}

}

public void init(FilterConfig arg0) throws ServletException {

}

}

将LoginFilter部署到web.xml中,以下所示。

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4" 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 http://java.sun.com /xml/ns/j2ee/web-app_2_4.xsd">

<display-name>

SecretInfo</display-name>

<servlet>

<description>

</description>

<display-name>LoginServlet</display-name>

<servlet-name>LoginServlet</servlet-name>

<servlet-class>cn.csai.web.secretinfo.LoginServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>LoginServlet</servlet-name>

<url-pattern>/LoginServlet/*</url-pattern>

</servlet-mapping>

<filter>

        <filter-name>LoginFilter</filter-name>

        <filter-class>cn.csai.web.secretinfo.LoginFilter</filter-class>

</filter>

<filter-mapping>

        <filter-name>LoginFilter</filter-name>

        <url-pattern>/content/view.jsp</url-pattern>

</filter-mapping>

<welcome-file-list>

<welcome-file>login.htm</welcome-file>

</welcome-file-list>

</web-app>

将url-mapping设置为/content/view.jsp,即只对view.jsp一个页面进行登陆信息检查,这是由于在本系统中只有一个页面中包含了受限信息。但在实际的系统中会有许多页面包含受限信息,那么只须要修改这个url-pattern的值,使其符合全部包含受限信息的页面的URL。好比最简单的,将全部包含受限信息的页面放在一个目录中(例如content目录),那么就能够将url-pattern修改成/content/*。若是包含受限信息的页面比较多,程序员又不想将全部这些页面放在一个目录中,那么能够使用多个filter-mapping,以下所示。

<filter>

        <filter-name>LoginFilter</filter-name>

        <filter-class>cn.csai.web.secretinfo.LoginFilter</filter-class>

</filter>

<filter-mapping>

        <filter-name>LoginFilter</filter-name>

        <url-pattern>/content/*</url-pattern>

</filter-mapping>

<filter-mapping>

        <filter-name>LoginFilter</filter-name>

        <url-pattern>*.jsp </url-pattern>

</filter-mapping>

本例中对content目录下的全部页面以及全部jsp文件使用Filter进行登陆验证。

开发完成后将系统部署到Tomcat中,验证系统的功能。

打开login.htm,输入正确的登陆信息,提交后能够正常显示view.jsp及其内容。从新打开一个浏览器,直接在其中输入view.jsp的URL,如图8.29所示。

图8.29  直接输入view.jsp的URL

而后回车,浏览器显示如图8.30所示。

图8.30  error.htm页面

所打开的页面并非view.jsp而是error.htm,因而可知,当用户想在不登陆的状况下直接访问受限页面时,用户响应会被重定向到error.htm。

经过使用Servlet Filter来实现对受限页面进行登陆信息验证很是方便,只须要对登陆验证的Servlet稍加修改,以及适当地对Filter的url-mapping进行配置就能够在保持受限页面代码不用改变的状况下灵活控制对受限页面的访问。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用Servlet Filter改变请求

 

Servlet Filter的工做方式就是经过影响请求和响应进而改变Servlet的处理过程,最终影响到用户提交请求所产生的执行效果,以及对用户产生有别于Servlet原始响应结果的响应。

在没有Filter存在的状况下,Servlet所得到的ServletRequest是根据用户提交的原始请求构造而成的,这个ServletRequest对象反映了用户所提交请求的实际状况,好比请求的URL、参数、Cookie、消息体的输入流,等等。Servlet经过ServletRequest的相关get方法得到这些参数,好比getRequestURI()、getAttribute(name)、getCookies()、getInputStream()。

若是但愿Servlet在经过相关方法得到的值不一样于原始用户请求的话,就能够经过添加一个Filter对请求进行修改。经过添加Filter,能够在不修改Servlet实现的状况下使Servlet对用户的请求采起不一样的处理策略。

使用Filter修改请求的方式一般只有一种,就是使用ServletRequestWrapper从新构造一个ServletRequest对象,而且修改这个对象相应方法的行为,而后将这个对象看成用户提交的ServletRequest对象传递给Servlet。在Servlet调用ServletRequest的相应方法时,实质上调用的是ServletRequestWrapper的方法,因为ServletRequestWrapper的方法是根据开发人员的意图修改过的实现,当Servlet调用时得到结果会与调用原ServletRequest的相应方法不同,从而模拟了修改ServletRequest的功能。

使用这种方法修改请求的关键就是如何实现一个合适的ServletRequestWrapper。FilterTest演示了如何经过构造ServletRequestWrapper改变请求。最初,FilterTest实现了一个简单的用户登陆功能,在登陆页面中输入用户名和密码,而后提交给LoginServlet,LoginServlet判断用户名和密码是否正确,这个功能与SecretInfo系统的登陆功能同样,能够使用SecretInfo系统的login.htm和LoginServlet。在login.htm中用户名输入框的名称是username,密码输入框的名称是password,因此在LoginServlet中分别使用req.getParameter(“username”)和req.getParameter(“password”)得到用户输入的用户名和密码。假如如今须要在不修改Servlet的状况下,那就支持用以下的login2.htm进行登陆:

login2.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4 /loose.dtd">

<html>

<head>

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

<title>Insert title here</title>

</head>

<body>

<form action="LoginServlet">

用户名: <input type="text" name="usnm"><br>

密码: <input type="password" name="pswd"><br>

<input type="submit" value="提交">

</form>

</body>

</html>

login2.htm与login.htm惟一的区别就是为用户名和密码输入框取了两个不一样的名称。假如直接使用login2.htm进行登陆,就算输入的用户名和密码都是正确的(LoginServlet判断是否都等于admin)也没法登陆。查看LoginServlet的实现(不考虑登陆验证功能):

package cn.csai.web.filter;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

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

String password = req.getParameter("password");

if (null != username && null != password && username.equals("admin")

&& password.equals("admin"))

{

resp.sendRedirect("content/view.jsp");

}

else

resp.sendRedirect("login.htm");

}

}

LoginServlet会从请求中取username和password参数的值,因为在请求中取不到这两个参数,因此username和password都为null,那么LoginServlet就会看成登陆不成功对待。归根究底,就是由于不一样的参数名。这只须要修改LoginServlet的实现,将username改成usnm,将password改成pswd就能够了。

那假如不能修改Servlet、而但愿经过Filter来解决这个问题,该怎么办呢?最直接的就是修改请求,当Servlet使用req.getParameter(“username”)时返回req.getParameter(“usnm”),当使用req.getParameter(“password”)时返回req.getParameter(“pswd”)。

首先,实现一个HttpServletRequestWrapper的子类:

AttributeRequestWrapper

package cn.csai.web.filter;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

public class AttributeRequestWrapper extends HttpServletRequestWrapper {

public AttributeRequestWrapper(HttpServletRequest request) {

super(request);

}

@Override

public String getParameter(String name) {

if(name.equals("username")) {

return super.getParameter("usnm");

}

else if(name.equals("password")) {

return super.getParameter("pswd");

}

return super.getParameter(name);

}

}

AttributeRequestWrapper继承自HttpServletRequestWrapper而且覆盖Wrapper的getParameter(name)方法。若是请求的参数名为username则返回usnm参数的值,若是请求的参数名为password则返回pswd参数的值,不然就直接返回原参数值。

而后,实现一个Filter,在过滤时使用原ServletRequest构造一个AttributeRequestWrapper的对象并传递给Servlet:

AttributeFilter

package cn.csai.web.filter;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

public class AttributeFilter implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException

{

arg2.doFilter(new AttributeRequestWrapper((HttpServletRequest) arg0), arg1);

}

public void init(FilterConfig arg0) throws ServletException {

}

}

将该Filter配置到web.xml中而且部署应用后就能够正常工做了。

打开login2.htm,而且输入admin/admin,如图8.31所示:

图8.31  login2.htm页面

登陆后就能够打开view.jsp,如图8.32所示:

图8.32  登陆成功后的view.jsp页面

在这种状况下,login.htm又没法正常登陆。其实,只要对AttributeRequestWrapper稍做修改就可以使两个命名方式的login.htm和login2.htm都可以正确登陆。将AttributeRequestWrapper修改以下:

package cn.csai.web.filter;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

public class AttributeRequestWrapper extends HttpServletRequestWrapper {

public AttributeRequestWrapper(HttpServletRequest request) {

super(request);

}

@Override

public String getParameter(String name) {

if(name.equals("username")) {

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

String usnm = super.getParameter("usnm");

if(null == username) return usnm;

}

else if(name.equals("password")) {

String password = super.getParameter("password");

String pswd = super.getParameter("pswd");

if(null == password) return pswd;

}

return super.getParameter(name);

}

}

在这个实现中,分别获取username参数和usnm参数的值。若是第一个值为空则返回第二个值,密码的获取也同样,如此一来两种命名方式均可以支持。同理,开发人员能够经过Filter实现对各类命名方式的支持,而不须要修改Servlet;当不须要支持这些命名方式时只须要取消Filter的部署便可。

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

使用Servlet Filter改变响应

 

使用Servlet Filter对改变响应的使用范围远远多于用于改变请求,由于用Servlet Filter改变响应更直接地影响到用户体验。使用Servlet Filter改变响应的方式主要有两种,直接调用响应的相关方法和使用响应包装器。

1.直接调用响应的相关方法

ServletResponse提供了许多设置属性和执行操做的方法,经过这些方法能够设置响应的MIME类型、直接向客户端发送响应消息(包括重定向消息、错误消息、通常响应消息等)、设置响应消息的头域,等等。好比:setContentType(mime)、sendRedirect(url)、sendError(status)、setHeader(name, value)等。在Filter中,Filter能够直接对得到的ServletResponse对象调用这些方法进而改变用户得到的响应,甚至Filter能够直接拦截请求而且自主地为客户端发送响应,而不将请求传递给Serlvet。

在SecretInfo中,LoginFilter就是在判断到用户未登陆时就直接截断请求的传递,而是直接经过调用响应的sendRedirect(url)方法向客户端发送了一个重定向消息。

这种方法简单直接,但很难与Servlet配合工做。

2.使用响应包装器

这种方式与前面提到的修改请求的方式如出一辙,Servlet中也提供了SerlvetResponseWrapper对象,开发人员能够经过实现一个ServletResponseWrapper的子类并将其传递给Servlet以改变原ServletResponse的行为。

下面将使用一个示例来对这种方式进行说明。在这个例子将实现一个NewLineFilter。在前面的一些示例中读者可能已经发现,使用ServletResponse的PrintWriter直接输出回车符时,只是在响应的HTML页面中多一个回车符,可是当显示到页面中后这个回车符并不会被显示为一个换行符,由于在HTML中换行符是经过标签<br>表示的。这个NewLineFilter将替换响应消息中的回车符为<br>,使Servlet在输出回车符时就可以体现为页面中的一个新行。

首先,在没有NewLineFilter的时候查看一下效果。下面是一个很是简单的Servlet,它只向响应中输出两行文本:

TextPrinterServlet

package cn.csai.web.filter;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

public class TextPrinterServlet extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException

{

PrintWriter writer = resp.getWriter();

writer.println("line1");

writer.println("line2");

writer.flush();

writer.close();

}

}

该Servlet使用PrintWriter的println()方法向响应中输出两行文字。部署该Servlet并用浏览器访问该Servlet得到的结果,如图8.33所示:

图8.33  没有NewLineFilter的页面

查看该页的HTML源代码以下:

line1

line2

能够发如今HTML源代码中确实是被分为两行,可是显示到页面中后就只有一行了,这是由于HTML是经过标签进行格式化的。因此,在Servlet中向响应打印回车符时只能体如今源代码中,而没法体如今页面上。

为了解决这个问题,咱们就开发一个NewLineFilter,让它在过滤响应消息中将其中的回车符替换为HTML的换行标签<br>。也就是说当Servlet调用println(s)时,Filter使用print(s + “<br>”)进行替换。由于Servlet首先是经过ServletResponse的getWriter()方法,而后再使用PrintWriter的println()方法,因此必需要新建一个新的PrintWriter的子类,覆盖其默认的println()方法和print()方法,而后建立一个ServletResponseWrapper的子类,让它的getWriter()方法返回新建的PrintWriter类。在Filter中将ServletResponseWrapper的子类传递给Servlet。这几个类的实现以下:

ChangeNewLinePrintWriter

package cn.csai.web.filter;

import java.io.PrintWriter;

import java.io.Writer;

public class ChangeNewLinePrintWriter extends PrintWriter {

public ChangeNewLinePrintWriter(Writer arg0) {

super(arg0);

}

@Override

public void print(String s) {

super.print(s.replace("\n", "<br>"));

}

@Override

public void println(String x) {

super.println(x + "<br>");

}

}

NewLineResponseWrapper

package cn.csai.web.filter;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpServletResponseWrapper;

public class NewLineResponseWrapper extends HttpServletResponseWrapper {

public NewLineResponseWrapper(HttpServletResponse resp)

{

super(resp);

}

@Override

public PrintWriter getWriter() throws IOException {

return new ChangeNewLinePrintWriter(super.getWriter());

}

}

NewLineFilter

package cn.csai.web.filter;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

public class NewLineFilter implements Filter {

public void destroy() {

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException

{

arg2.doFilter(arg0, new NewLineResponseWrapper(

(HttpServletResponse) arg1));

}

public void init(FilterConfig arg0) throws ServletException {

}

}

将NewLineFilter配置到web.xml中:

<filter>

<filter-name>NewLineFilter</filter-name>

<filter-class>cn.csai.web.filter.NewLineFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>NewLineFilter</filter-name>

<url-pattern>/TextPrinterServlet</url-pattern>

</filter-mapping>

由于这里做为测试,因此其配置只对TextPrinterServlet进行过滤。

将这个Filter配置到应用中后,访问TextPrinterServlet的过程以下:

当有请求TextPrinterServlet的请求到达时,NewLineFilter拦截到请求,在进行过滤时,用原ServletResponse对象构造一个NewLineResponseWrapper对象传递给Servlet;

当Servlet的doGet()方法开始执行时,传递进来的ServletResponse对象实质上是NewLineResponse Wrapper对象;Servlet调用ServletResponse对象的getWriter()方法实质上是调用NewLineResponse Wrapper的getWriter()方法,因此得到的PrintWriter对象实质上是在NewLineResponseWrapper中构造的ChangeNewLinePrintWriter;

Servlet调用得到PrintWriter对象的println(s)方法,实质上是调用了ChangeNewLinePrintWriter的println(s)方法,因此当Servlet调用println(“line1”)时实质上至关于调用println(“line1” + "<br>"),一样println(“line2”)至关于调用println(“line2” + “<br>”);

因此,最终到响应中的是“line1<br>\nline2<br>\n”。

将Filter配置到应用中,从新访问TextPrinterServlet,得到的页面如图8.34所示:

图8.34  增长了NewLineFilter后的页面

查看HTML源文件以下:

line1<br>

line2<br>

 
 
 
 

第 8 章:Servlet基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

本章小结

 

Servlet是一种能够配置进Servlet容器中用于处理客户端请求的特殊Java对象。在接收到客户端请求后,Tomcat将请求封装为一个ServletRequest对象,同时构造一个指向客户端的空白ServletResponse对象,而后将两个对象同时传递给Servlet,Servlet分析并处理ServletRequest对象并将响应结果经过ServletResponse对象传递给客户端。

Servlet中最重要的几个概念分别是:Servlet接口表明一个Servlet;ServletConfig接口表明对一个Servlet的配置信息;ServletContext接口表明Servlet所运行的上下文信息;RequestDispatcher接口表示一个请求转发器。

GenericServlet和HttpServlet是两个对Servlet的默认实现,GenericServlet表示一个通用Servlet,HttpServlet对象表示一个专门处理HTTP请求的Servlet。ServletRequest和ServletResponse分别表示通用Servlet请求和响应,HttpServletRequest和HttpServletResponse分别表示HTTP请求和响应。

Servlet过滤器,是一种搭建在Servlet容器和Servlet之间的过滤器,它对Servlet容器分发给Servlet的请求和Servlet返回给Servlet容器的响应进行处理,而且这种处理对于目的Servlet和客户端都是透明的。

 

JSP是Java ServerPage的简称,是Java Web开发中又一个重要的基础技术。做为一种Web对象,JSP以JSP文件的形式与HTML文件同样存在于Web应用目录中。JSP文件的格式相似于HTML文件,它经过特殊标签在HTML文件中添加Java代码以实现动态处理功能。在服务器运行期间,JSP文件能够根据Java代码的执行状况动态地生成HTML文件的内容。

本章将首先揭开JSP文件的表象,对JSP的本质及其内部实现机制进行介绍,而后介绍JSP的基本语法以及JSP中隐含对象的使用。最后将介绍JSP中最强大的功能,那就是自定义标签的开发,包括JSP中标签的体系结构,以及经过实例介绍如何进行各个级别自定义标签的开发。

在Web应用中,JSP以单个文件存在,它的内容能够包含静态内容(HTML代码)也可包含动态内容(Java代码);客户端在访问JSP文件时,直接按照JSP文件的路径请求。test.jsp的一个简单的JSP文件实例以下所示:

示例9.1  test.jsp

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

<html>

<head>

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

<title>Test</title>

</head>

<body>

<%

out.print("Hello World");

%>

</body>

</html>

从表面上看JSP文件与HTML文件的格式很相似,只是在HTML文件中添加了由<%和%>括起来的内容。这些被括起来的内容就是JSP中的动态内容。

将该JSP部署到Web应用test中后,访问获得的页面效果如图9.1所示。

图9.1  test.jsp运行效果

在打开的页面上单击鼠标右键→查看源文件,获得该JSP文件在客户端浏览器中被打开时的HTML代码,内容以下:

<html>

    <head>

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

        <title>Test</title>

    </head>

    <body>

        Hello World

    </body>

</html>

比较JSP文件内容和打开后的源文件内容能够发现,打开后的源文件是一个标准的HTML文件,其中已经没有任何由<%和%>括起来的内容,而是被<%和%>之间的代码的执行结果所取代。

从这个现象不难推测,JSP文件在被请求时的执行过程如图9.2所示:

如图9.2所示,客户端请求服务器上的JSP文件,服务器首先执行JSP文件中的Java代码而且根据代码逻辑将输出结果写入代码所在位置;全部Java代码执行结束后JSP文件的内容便成为纯静态内容,变成一个HTML文件,而后服务器再将该HTML文件的内容返回给客户端。可见,JSP文件中虽然将静态的HTML内容和动态的Java代码混合,可是实质上它们是被分红两层的,静态的HTML内容是底层,动态的Java代码是上层。

图9.2  推断的JSP执行过程示意图

做为一个普通的Web开发人员,单纯从JSP文件执行的表象来看,这种方式已经彻底能够将JSP的工做过程解释清楚。但若是要深刻JSP文件在服务器端被处理的实质过程,这种解释并不彻底正确。JSP文件的本质其实是一个Servlet,JSP最终会在服务器中被转化为一个Servlet,而后由Servlet响应客户端的请求。因此,JSP的实际执行过程如图9.3所示。

图9.3  实际的JSP执行过程示意图

如图9.3能够发现,在有请求到达JSP文件时,服务器会将JSP文件首先转化成为一个Java文件,这个Java文件定义了一个Servlet;而后将该Java文件编译成一个class文件,服务器装载class文件为Servlet对象;Servlet对象根据请求对客户端进行响应。

在这个过程当中,编译和装载都比较好理解,这与普通的Servlet同样;而转化是这其中的关键步骤,也是比较难于理解的一步。其实,在前面介绍Tomcat的web.xml文件配置时已经介绍过在初始状态下,通用web.xml中配置了两个Servlet,一个是DefaultServlet,另外一个是JspServlet;JspServlet就负责将JSP文件转化为Java文件、调用JDK编译Java文件、而且装载Servlet。JspServlet同DefaultServlet同样,它也是Tomcat自带的一个Servlet,它专门负责响应客户端对JSP文件的访问。

在有客户端请求访问JSP文件时,JspServlet会在Tomcat的工做目录中生成对应的Java文件,而且编译成class文件。Tomcat的工做目录是${TOMCAT_HOME}/work,JSP文件转换而成的Java文件以及将Java文件编译生成的class文件都在该目录下的${Service}/${Host}/${Context}/org/apache/jsp目录中,其中${Service}表示使用的Tomcat Service的名称,${Host}表示使用的虚拟主机的名称,${Context}表示Web应用的上下文路径。以上面Hello World实例中的test.jsp文件为例,它使用的是Catalina的localhost,Web应用的上下文路径为test,因此test.jsp对应的Java文件和class文件都在${TOMCAT_HOME}/work/Catalina/localhost/test/org/apache/jsp目录中。test.jsp转换的Java文件以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

return _jspx_dependants;

  }

  public void _jspInit() {

_el_expressionfactory = 

_jspxFactory.getJspApplicationContext(getServletConfig().getServletContext())

.getExpressionFactory();

_jsp_annotationprocessor = 

(org.apache.AnnotationProcessor) getServletConfig().getServletContext()

.getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

PageContext _jspx_page_context = null;

try {

response.setContentType("text/html; charset=GB2312");

pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);

  null, true, 8192, true);

_jspx_page_context = pageContext;

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\n");

out.write("\n");

out.write("<html>\n");

out.write("<head>\n");

out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

out.write("<title>Test</title>\n");

out.write("</head>\n");

out.write("<body>\n");

out.print("Hello World");

out.write("\n");

out.write("</body>\n");

out.write("</html>");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

      out = _jspx_out;

      if (out != null && out.getBufferSize() != 0)

            try { out.clearBuffer(); } catch (java.io.IOException e) {}

      if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

}

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

}

能够发现,JSP文件最终被转化成为一个HttpJspBase的子类,HttpJspBase是一个抽象类,它的继承关系如图9.4所示。

图9.4  HttpJspBase类的继承层次图

因而可知,HttpJspBase实质上也是一个Servlet,自动生成的Java类test_jsp也会做为一个Servlet被部署到Tomcat中。在test_jsp中,最核心的一段代码是:

response.setContentType("text/html; charset=GB2312");

pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);

  null, true, 8192, true);

_jspx_page_context = pageContext;

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\n");

out.write("\n");

out.write("<html>\n");

out.write("<head>\n");

out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

out.write("<title>Test</title>\n");

out.write("</head>\n");

out.write("<body>\n");

out.write("\n");

out.write("</body>\n");

out.write("</html>");

其中,response.setContentType("text/html; charset=GB2312")一行对应test.jsp文件中page设置<%@ page language="java" contentType="text/html; charset=GB2312" pageEncoding="GB2312"%>中的contentType="text/html; charset=GB2312";下面几行是用于获取out对象;JSP文件中的Java代码直接搬移到Java文件中,将HTML内容经过out.write()方法写入到响应对象中。

JSP文件无论复杂仍是简单,JspServlet都会根据一样的规则将其转换成Servlet来响应客户端的请求。因此JSP文件表象是一个包含了Java代码片断的HTML文件,但实质上它是一个Servlet。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP的基本语法

 

虽然JSP本质上会被转化为一个Servlet,可是在编辑时仍是须要编辑成一个符合JSP格式规范的JSP文件。JSP文件是在HTML文件中经过<%和%>符号插入Java代码,HTML内容符合HTML文件的格式,Java代码内容符合Java语法。这都很是容易理解,可是除此以外JSP还规定了一些特殊的表现形式和一些特殊的标签,本节将对这些进行介绍。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP程序代码块

 

JSP文件中全部JSP使用的动态内容都由<%和%>符号包围着,在这两个符号之间开发人员能够编写任何符合Java规范和语法的代码。例如:

<%

for(int i=0; i<10; i++){

out.print("Hello World");

}

%>

这段代码最终会在页面上显示10个Hello World。

Java代码块能够被分开到两个<%...%>块中,例以下面这段代码也能够在页面上显示10个Hello World:

<%

for(int i=0; i<10; i++){

%>

Hello World

<%

}

%>

由于两段代码在JSP被转化为Servlet后分别对应以下两段Java代码:

for(int i=0; i<10; i++){

out.print("Hello World");

}

for(int i=0; i<10; i++){

out.write("Hello World");

}

这两段代码的执行结果彻底同样。

在代码块中能够定义变量但不能够定义方法。下例是容许的:

<%

int sum = 0;

sum += 2;

out.println(sum);

%>

而下例这样是不容许的:

<%

private int add(int x, int y){

return x+y;

}

out.println(add(1+2));

%>

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP声明代码块

 

JSP声明是指一块专门用来进行变量和方法定义的程序段,其格式以下:

<%! 

        int var;

        private int add(int x, int y){

                return x+y

        }

%>

声明代码块由<%!和%>包围,其中只能用来声明,而不能添加任何代码块。因此以下的代码块是错误的:

<%! 

        int var = 0;

        var++;

%>

JSP中全部关于方法的声明必须在声明代码块中定义,而变量能够在声明块中定义也能够在程序代码块中定义,那这两种的区别在哪里?下面构造一个JSP文件,将其转化生成的Servlet文件打开就能够发现其区别。JSP文件以下:

示例9.2  test.jsp

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

<html>

<head>

<title>Test</title>

</head>

<body>

<%!

int declareVar;

%>

<%

int programVar;

%>

</body>

</html>

在JSP文件中有一个声明块和一个程序块,声明块中定义了一个变量declareVar,在程序块中定义了一个变量programVar。将该JSP文件部署到Web应用中,经过Tomcat访问该JSP文件,在Tomcat的工做目录中就能够得到转化后的Java文件,查看其内容以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  int declareVar;

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

return _jspx_dependants;

  }

  public void _jspInit() {

_el_expressionfactory = 

_jspxFactory.getJspApplicationContext(getServletConfig().getServletContext())

.getExpressionFactory();

_jsp_annotationprocessor = 

(org.apache.AnnotationProcessor) getServletConfig().getServletContext()

.getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

JspWriter _jspx_out = null;

PageContext _jspx_page_context = null;

try {

response.setContentType("text/html; charset=GB2312");

pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);

  null, true, 8192, true);

_jspx_page_context = pageContext;

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\n");

out.write("<html>\n");

out.write("\t<head>\n");

out.write("\t\t<title>Test</title>\n");

out.write("\t</head>\n");

out.write("\t<body>\n");

out.write("\t");

out.write('\r');

out.write('\n');

int programVar;

out.write("\n");

out.write("</body>\n");

out.write("</html>");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

      out = _jspx_out;

      if (out != null && out.getBufferSize() != 0)

            try { out.clearBuffer(); } catch (java.io.IOException e) {}

      if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

}

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

}

如代码中粗体部分所示,这两个变量的区别是,declareVar被做为类中的一个域变量,而programVar被做为一个Service方法中的局部变量。域变量在JSP生成的Servlet被装载时生成直到Servlet被卸载时才会被销毁,而局部变量在每次service()方法执行时就会建立和销毁一次;因此在声明块中定义的变量在屡次请求时能保持其上次请求的值,而在程序块中定义的变量在屡次请求时每次都是初始值。下例能够帮助理解这种状况,编辑JSP文件内容以下:

示例9.3  test.jsp

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

<html>

<head>

<title>Test</title>

</head>

<body>

<%!

int declareVar = 0;

%>

<%

int programVar = 0;

%>

<%

declareVar++;

programVar++;

out.println("declareVar:" + declareVar);

out.println("programVar:" + programVar);

%>

</body>

</html>

部署该JSP文件到Web应用中,而后请求访问该JSP文件,第一次访问获得的结果如图9.5所示。

图9.5  第一次访问结果

第二次访问获得的结果如图9.6所示:

图9.6  第二次访问结果

在第一次访问时,两个变量都是从0开始,自增了一次,因此都是1;第二次访问时,declareVar的值是2,而programVar的值仍然是1,这是由于declareVar是域变量而programVar是service()方法的局部变量,每次访问JSP文件时,Servlet对象并不会销毁,只是再一次执行Servlet的service()方法,因此在每次请求时programVar都会被从新声明和赋值,而declareVar则不会被从新声明和赋值,除非Servlet被销毁(Tomcat重启或者因为闲置时间过长将Servlet清理出内存)。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP输出代码块

 

JSP输出块是由<%=和%>包围的代码块,代码块中包含一个表达式,输出代码块的意义与out.print()语句的意义是等价的。因此以下的两个代码片断是等价的:

<%= declareVar %>

<% out.print(declareVar); %>

并且在将JSP转换成Servlet后,输出代码块也会被转换为out.print()。可是须要注意的是,在程序代码块中每一条语句都要以分号结束,而在输出代码块中,中间包含的是一个表达式,因此结束不须要用分号。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP注释代码块

 

JSP文件中的注释是由<%--和--%>包围的代码块,其中不管是Java代码仍是HTML代码都会被注释掉,例如:

<%-- 这是一段注释 --%>

<% 

   ...

 %>

将“这是一段注释”做为注释。

<%--

<% 

for(int i=0; i<10; i++)

{

%>

Hello World

<% } %>

--%>

会将<%--和--%>当中的全部内容做为注释。

除此以外,在程序代码块内部还能够使用标准的Java注释方式,例如:

<% 

    String s = “Hello World”;

    //out.print(s);

%>

<% 

    String s = “Hello World”;

    /*out.print(s);*/

%>

都是正确的注释使用方式。

而在HTML代码块中,HTML的注释符号<!-- -->也一样是能够使用的,例如:

<% 

for(int i=0; i<10; i++)

{

%>

Hello <!--World-->

<% } %>

其实,只须要将带有各类注释的JSP文件转换为Servlet,就能够发现各类注释起做用的本质了,以以下一个JSP文件为例:

示例9.4

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

pageEncoding="GB2312"%>

<html>

<head>

<title>Test</title>

</head>

<body>

<%--

<%

for(int i=0; i<10; i++) { 

%>

Hello World

<%

out.print("Hello World1");

%>

 --%>

 <%

for(int i=0; i<10; i++) { 

%>

Hello <!--World2-->

<%

//out.print("Hello World3");

%>

</body>

</html>

生成的Servlet中相关的代码片断以下:

out.write("\r\n");

out.write("<html>\r\n");

out.write("<head>\r\n");

out.write("<title>Test</title>\r\n");

out.write("</head>\r\n");

out.write("<body>\r\n");

out.write("\r\n");

for(int i=0; i<10; i++) {

out.write("\r\n");

out.write("Hello <!--World2-->\r\n");

//out.print("Hello World3");

}

out.write("\r\n");

out.write("</body>\r\n");

out.write("</html>");

访问获得的页面如图9.7所示。

图9.7  test.jsp执行结果

页面的源文件以下:

<html>

<head>

<title>Test</title>

</head>

<body>

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

Hello <!--World-->

</body>

</html>

对比这几种文件的内容能够发现,由<%-- --%>注释的全部内容在转化为Servlet后就被彻底删除了,其中不管包含的是Java代码仍是HTML代码;而由//或/* */包围的Java代码会被转换到Servlet中,可是被注释掉的,因此在将Java文件编译成class文件后带注释的代码会被忽略;由<!-- -->包围的HTML代码会带着注释由out.write()语句输出,因此HTML文件中会有带着注释符号的内容,只是HTML文件在最终显示时会忽略掉这段内容。

因此,不一样类型的注释是在从JSP文件最终到页面展示的不一样阶段中起做用的,使用不一样类型的注释会影响到各个阶段的数据处理量。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP指令代码块

 

这里所谓的JSP指令代码块,是指由<%@和%>包围的JSP内容。在当前的JSP版本中,JSP指令有三种:page、taglib和include。

1.page指令

page指令用于设置JSP页面的一些属性,一般写在页面的顶端,page指令的格式以下:

<%@ page attr1="value1" attr2="value2" ... %>

一个page指令能够定义多个页面的属性,一个JSP页面也能够出现多个page指令。能够将多个属性写在一个page指令中,也能够将它们分开写在多个page指令中,这二者的效果是相同的。

page指令中能定义的属性及其含义以下。

language:表示脚本使用的语言,暂时只能用Java。

extends:代表JSP转化的Java类须要继承的父类,包含包名和类名。这个属性要当心使用,由于有些JSP容器在转化JSP时已经对其指定了父类。例如,Tomcat在将JSP转化为Java类时已经为其添加了HttpJspBase父类,因此在Tomcat中的JSP页面中使用extends属性会使得指定的父类覆盖了HttpJspBase父类,这将致使执行JSP时出错,因此使用Tomcat时不要为JSP页面定义extends属性。

import:须要引入的包或者类,做用与Java文件中的import语句同样。属性值也与Java文件中的import语句的内容一致,多个值用逗号隔开。例如:

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

session:设定客户端是否须要HTTP Session,取值true/false。若是设定为false,那么在JSP页面中就不能使用session对象也不能将任何变量的scope设置成session,不然会产生错误。默认是true。

buffer:设定JSP向客户端输出响应时所使用的缓冲区大小,不使用能够设置成none。默认是8kB。

autoFlush:设置若是buffer满的话是否自动将响应输出到客户端,true表示自动输出,false表示不自动输出。若是将该值设置为false,那么在当buffer满时就会有异常发生。若是buffer设置为none,那么就不能把autoFlush设置为false。默认是true。

isThreadSafe:说明该JSP文件是不是线程安全的,若是是线程安全的那么Web服务器就会容许多线程访问该文件,不然一次就只能容许一个请求访问JSP文件。默认是true。

info:一段用于描述JSP文件的文本,当JSP被转换为Servlet后,这段文本便成为Servlet的描述信息,能够经过Servlet.getServletInfo()方法得到。

errorPage:设置出错跳转页面。当该JSP文件出错后,请求会被跳转到该页面。

isErrorPage:设置本页是不是错误处理页面,若是设置为true,则本页能够访问exception对象。

contentType:设置该JSP页面内容类型,包括MIME类型和字符编码。一般格式是:contentType="mimeType; charset=encoding"。默认值是:text/html;charset=ISO-8859-1。

pageEncoding:设置JSP页面中的文本所使用的字符编码方式。即JspServlet将使用该编码方式读取和解析JSP文件的内容。

假设在JSP页面中定义了以下的page指令:

<%@ page language="java" buffer="10kb" import="java.util.*" %>

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

<%@ page autoFlush="true" errorPage="error.jsp" info="test jsp page"%>

<%@ page isThreadSafe="true" isErrorPage="false" session="true" %>

该JSP页面转换的Java文件以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import java.util.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  public String getServletInfo() {

    return "test jsp page";

  }

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _el_expressionfactory=

_jspxFactory.getJspApplicationContext(getServletConfig()

.getServletContext()).getExpressionFactory();

    _jsp_annotationprocessor = 

(org.apache.AnnotationProcessor) getServletConfig().getServletContext()

.getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=GB2312");

      pageContext = _jspxFactory.getPageContext(this, request, response, "error.jsp", true, 10240, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\r\n");

      out.write("\r\n");

      out.write("\r\n");

      out.write("\r\n");

      out.write("<html>\r\n");

      out.write("\t<head>\r\n");

      out.write("\t\t<title>Test</title>\r\n");

      out.write("\t</head>\r\n");

      out.write("\t<body>\r\n");

      out.write("\t\r\n");

      out.write("\t</body>\r\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

}

page中的import属性会致使Java文件中添加import语句;info属性会致使Java文件中的getServletInfo()方法返回其属性值;contentType属性会由response.setContentType()方法设置。

buffer、errorPage、autoflush和session会体如今_jspxFactory.getPageContext()方法中,_jspxFactory是JspFactory的对象,getPageContext()的方法声明以下:

public abstract PageContext getPageContext(Servlet servlet, ServletRequest request, 

ServletResponse response, String errorPageURL, 

boolean needsSession, int buffer, boolean autoflush)

其中的参数与page指令中的属性的对应关系是:errorPageURL →errorPage、needsSession →session、buffer →buffer、autoflush →autoFlush。

剩下的language、pageEncoding、isThreadSafe和isErrorPage属性都是对Web服务器的提示信息,不会体现到Java文件中。

2.taglib指令

在JSP中除了由<%和%>包围的动态内容外,还能够使用传统的HTML标签自定义的标签,只是这些自定义标签必须经过taglib指令引入进来。taglib指令用于定义JSP页面中用户自定义标签的标签库和标签前缀。

好比:

<%@ taglib uri="ctag.tld" prefix="c" %>

将标记库ctag.tld引入到JSP页面中,这样在JSP页面中就能够使用ctag.tld定义的标签了;prefix是指在使用标签时在标签名前加的前缀,全部拥有该前缀的标签都表示对ctag.tld中标签的引用,例如<c:tag1> ... </c:tag1>。uri惟一指定了一个标签库,其值能够是一个URL,也能够是一个标签库的惟一名称。

在JSP文件中,除了JSP预约义标签库之外的任何标签库都必须通过taglib定义后才可以使用;一个JSP文件中容许有多个taglib指令引入多个标签库,每一个标签库所定义的prefix必须惟一,并且prefix不能是:jsp, jspx, java, javax, servlet, sun, 和sunw中的任何一个,由于这些是Sun公司保留的前缀。

3.include指令

include指令是一种编辑层的包含指令,它能够在一个JSP文件中插入一个文本文件或者JSP文件的内容。把它称做编辑层的包含指令,是指这种包含方式至关于将被包含者的内容直接输入到JSP文件中的相应位置。假设有以下的JSP文件:

示例9.5  including.jsp

<%@ page language="java" pageEncoding="GB2312" %>

<HTML>

    <BODY>

        <%@ include file=”included.html” %>

    </BODY>

</HTML>

included.html

<H1> Hello World </H1>

那么在访问including.jsp时,Web服务器首先将included.html插入到including.jsp中,而后再将插入后的内容转换为Servlet,因此将including.jsp转换而成的including_jsp.java文件的内容是:

...

      out.write("<HTML>\r\n");

      out.write("\t<BODY>\r\n");

      out.write("\t\t<H1> Hello World </H1>\r\n");

      out.write("\t</BODY>\r\n");

      out.write("</HTML>");

...

include指令的属性只有一个file,它就表示所包含文件的路径。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP预约义标签

 

JSP预约义标签是一些自动引入的标签库中的标签,它们不须要经过taglib指令引入,而能够直接在JSP页面中经过jsp前缀使用。主要有:<jsp:forward>、<jsp:include>、<jsp:useBean>、<jsp:getProperty>、<jsp:setProperty>和<jsp:plugin>。

1.<jsp:forword>

该标签将一个客户请求前转到一个HTML文件、JSP文件或一个Servlet,前转的资源必须与当前JSP文件处于同一个Web应用中。其格式定义以下:

<jsp:forward page="relativeURL" />

<jsp:forward page="relativeURL" > 

<jsp:param name="parameterName" value="parameterValue" />

......

</jsp:forward>

其中,page属性的值就是前转页面的URL地址,可是该URL地址不能包含协议名和端口值,而是指向当前Web应用中的文件;该URL地址有两个格式,一种是以“/”开头,它表示从Web应用根目录开始的路径,另外一种不以“/”开头,它表示相对于当前JSP文件路径的相对路径。

假如目标文件是动态Web对象(JSP或Servlet等),那么能够为前转URL添加参数,参数将被目标Web对象得到。

示例:

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

<jsp:forward page="login">

        <jsp:param name="username" value="kevin" />

</jsp:forward>

2.<jsp:include>

将一个静态页面或者动态页面包含到本JSP文件中。该标签与include指令不一样的是:include指令不管被包含者是静态仍是动态,只是单纯地将文件内容放在include指令处;而<jsp:include>标签则是向被包含页面发出一个请求,将请求返回的响应结果放在标签处,因此当被包含页面是静态页面时其效果同include指令,而当被包含页面是动态页面时,它会将页面执行结束返回的内容包含在标签处。

除此以外,当包含的页面是动态页面时,<jsp:include>标签还能够经过<jsp:param>标签向被包含页面提供参数。

其格式以下:

<jsp:include page="relativeURL"   flush="true" />

<jsp:include page="relativeURL "   flush="true" >

        <jsp:param name="parameterName" value="parameterValue" />

        ......

</jsp:include>

此处关于relativeURL的定义与<jsp:forward>同样;flush="true"在这里是一个必须给出并且也不能修改其值的属性,在使用时直接加上便可。

3.<jsp:useBean>、<jsp:getProperty>和<jsp:setProperty>

JavaBean组件是用Java开发的一个用于完成特定工做的Java类。同使用的Java库很相似,JavaBean组件在开发完毕后能够经过对JavaBean组件实例化进而使用JavaBean组件中的功能。JavaBean组件存在的目的也是为了使Java代码的普遍复用。JavaBean组件在开发时须要遵守必定的要求:

针对一个名为xxx的域属性,一般要为其定义两个函数:getXxx()用于获取该属性的值,setXxx(...)用于设置该属性的值;

对外界公布的其余方法没有命名要求,但须要定义为public。

例以下面的类能够看做一个JavaBean组件:

public class Person{

        private String name;

        private int age;

        public Date getBirthday(){

                ......

        }

        public String getFamilyName(){

                ......

        }

        public String getName(){

                return name;

        }

        public int getAge(){

                return age;

        }

        public void setName(String n){

                name = n;

        }

        public void setAge(int a){

                age = a;

        }

        private void analyzeName(){

                ......

        }

}

该类中定义了两个属性name和age,而且分别为两个属性定义了getName()/getAge()/set Name()/setAge()方法;对外提供了两个方法getBirthday()和getFamilyName();同时,还有一个默认的无参构造函数。外界程序若是须要使用这个组件的功能时,只须要经过无参构造函数实例化一个Person的实例,而后经过setName()和setAge()方法将属性值传入,就能够经过调用Person定义的public方法使用该组件提供的功能了。

<jsp:useBean>标签就是JSP提供的一种在JSP文件中声明JavaBean组件的方法,JSP文件经过该标签声明JavaBean对象及其ID,经过<jsp:setProperty>标签设置JavaBean对象的属性值,而后就能够经过JavaBean的ID调用该JavaBean的方法了。

<jsp:useBean>标签的格式以下:

<jsp:useBean 

id="beanInstanceName" 

scope="page | request | session | application" 

{

        class="package.class" |

        type="package.class" | 

        class="package.class" type="package.class" | 

        beanName="package.class" type="package.class"

}

>

        ......

</jsp:useBean> 

id定义了引用该JavaBean对象的代号;scope定义了该JavaBean对象的做用域大小:page是页面域(默认),request是请求域,session是HTTP Session域,application是整个Web应用域。

<jsp:getProperty>用于得到JavaBean组件的属性值并将其打印到页面,其格式:

<jsp:getProperty name="beanInstanceName" property="propertyName" />

示例:

<jsp:useBean id="me" scope="session" class="Person" />

<jsp:setProperty name="me" property="name" value="kevin"/> 

<jsp:setProperty name="me" property="age" value="25"/>

<% out.print(me.getBirthday()); %>

4.<jsp:plugin>

该标签用于在浏览器中运行或显示一个Applet或JavaBean,在执行过程当中若是浏览器没法运行则能够到指定的连接下载插件。该标签的定义以下:

<jsp:plugin 

          type="bean | applet" 

          code="classFileName" 

          codebase="classFileDirectoryName" 

          [ name="instanceName" ] 

          [ archive="URIToArchive, ..." ] 

          [ align="bottom | top | middle | left | right" ] 

          [ height="displayPixels" ] 

          [ width="displayPixels" ] 

          [ hspace="leftRightPixels" ] 

          [ vspace="topBottomPixels" ] 

          [ jreversion="JREVersionNumber | 1.1" ] 

          [ nspluginurl="URLToPlugin" ] 

          [ iepluginurl="URLToPlugin" ] > 

          [ <jsp:params> 

                [ <jsp:param name="parameterName" value="parameterValue" /> ]

          </jsp:params> ]   

          [ <jsp:fallback> text message for user </jsp:fallback> ] 

</jsp:plugin>

其中:

type:表示plugin的类型,是JavaBean仍是Applet。

code:表示plugin所须要执行的Java类。

codebase:包含待执行Java类的文件目录。

name:该Bean/Applet的名称,使用此名称便于与JSP文件交流。

archive:包文件在codebase中的位置,能够定义多个包文件,之间用逗号隔开。这些包文件会使用类装载器进行加载。

align:但愿待打开对象在页面中的位置。

height:待打开对象在页面中显示时的初始高度。

width:待打开对象在页面中显示时的初始宽度。

hspace:待打开对象在页面中显示时的左右边距。

vspace:待打开对象在页面中显示时的上下边距。

jreversion:Applet或者Bean运行时使用的JRE的版本,默认是1.1。

nspluginurl:客户端下载Netscape Navigator JRE插件的URL。

iepluginurl:客户端下载Internet Explorer JRE插件的URL。

<jsp:params>标签用来定义须要向Applet或Bean传递的参数。<jsp:fallback>用来定义若是插件没法运行时向客户端显示的文本消息。

示例:

<jsp:plugin type=applet code="Show.class" codebase="/applets">

        <jsp:params>

                <jsp:param name="id" value="2134" />

        </jsp:params>

        <jsp:fallback> 

                <p>没法装载Applet</p>

        </jsp:fallback>

</jsp:plugin>

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

request、response、config和application对象

 

request对象是HttpServletRequest类的实例,至关于Servlet的service()方法中传递进来的HttpServletRequest参数。经过该对象JSP能够得到或设置请求相关属性,例如:经过getParameter()方法能够得到请求中的参数;经过getHeader()能够得到HTTP请求消息的头域等。

response对象是HttpServletResponse类的实例,至关于Servlet的service()方法中传递来的HttpServletResponse参数,经过该对象JSP能够得到或设置响应相关的属性,以及向响应中添加内容,例如:经过setHeader()方法向响应消息中设置HTTP头域,经过sendRedirect()方法向客户端发出重定向消息等。

config对象是ServletConfig类的实例,至关于使用Servlet.getServletConfig()方法得到的对象。经过该对象JSP能够得到或设置当前JSP页面所表示Servlet的属性。具体的方法可参见Servlet中关于ServletConfig类的介绍。

application对象是ServletContext类的实例,至关于Servlet中使用Servlet.getServlet Config().getServletContext()得到的对象。经过该对象JSP能够得到或设置与当前Web应用相关的属性。具体的方法可参见Servlet中关于ServletContext类的介绍。

out对象是JspWriter对象的实例。它表示向响应消息中输出字符内容的输出流。JspWriter是一个抽象类,其定义以下:

package javax.servlet.jsp;

import java.io.IOException;

import java.io.Writer;

public abstract class JspWriter extends Writer

{

    protected JspWriter(int bufferSize, boolean autoFlush)

    {

        this.bufferSize = bufferSize;

        this.autoFlush = autoFlush;

    }

    public abstract void newLine()

        throws IOException;

    public abstract void print(boolean flag)

        throws IOException;

    public abstract void print(char c)

        throws IOException;

    public abstract void print(int i)

        throws IOException;

    public abstract void print(long l)

        throws IOException;

    public abstract void print(float f)

        throws IOException;

    public abstract void print(double d)

        throws IOException;

    public abstract void print(char ac[])

        throws IOException;

    public abstract void print(String s)

        throws IOException;

    public abstract void print(Object obj)

        throws IOException;

    public abstract void println()

        throws IOException;

    public abstract void println(boolean flag)

        throws IOException;

    public abstract void println(char c)

        throws IOException;

    public abstract void println(int i)

        throws IOException;

    public abstract void println(long l)

        throws IOException;

    public abstract void println(float f)

        throws IOException;

    public abstract void println(double d)

        throws IOException;

    public abstract void println(char ac[])

        throws IOException;

    public abstract void println(String s)

        throws IOException;

    public abstract void println(Object obj)

        throws IOException;

    public abstract void clear()

        throws IOException;

    public abstract void clearBuffer()

        throws IOException;

    public abstract void flush()

        throws IOException;

    public abstract void close()

        throws IOException;

    public int getBufferSize()

    {

        return bufferSize;

    }

    public abstract int getRemaining();

    public boolean isAutoFlush()

    {

        return autoFlush;

    }

    public static final int NO_BUFFER = 0;

    public static final int DEFAULT_BUFFER = -1;

    public static final int UNBOUNDED_BUFFER = -2;

    protected int bufferSize;

    protected boolean autoFlush;

}

其中:

print():向输出流中写入数据,该方法接受多种类型的参数。

println():该方法与print()方法相似,只是在写入数据后再写入一个回车符。这里须要注意的是,这个回车符是指在HTML文件中写入回车符,因为HTML文件中的回车符只是用于显示格式而不会最终体现到页面上,因此若是程序员但愿在最终的页面上添加回车符,应该使用:print(“<BR>”)或println(“<BR>”)。

clear():清除输出缓冲区中的全部数据。若是清除时缓冲区中的数据已经被发出到客户端了,则该方法会抛出一个IOException异常,由于已经将不该该发送给客户端的数据发出。

clearBuffer():清除输出缓冲区中当前的数据,与clear()方法不一样的是,即便数据已经被发出了该方法也不会抛出异常。

flush():刷新输出缓冲区中的数据,将输出缓冲区中的数据当即发送给客户端。若是容许自动刷新,当缓冲区满时该方法会被自动调用。

close():关闭当前输出流,在关闭以前刷新缓冲区。

getBufferSize():返回该对象使用的缓冲区大小。

getRemaining():返回缓冲区中还没有使用的字节数。

isAutoFlush():返回该对象是否自动刷新。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

page对象

 

在上面列出的隐含对象对照表中将page对象的类型列为Object。在一个特定的系统中,使用Object对象的目的主要有两种:一是该对象不明确表示某种类型的对象,也不须要明确表示为某种类型的对象;二是该对象在具体的环境中可能会表示不一样种类型的对象。此处的page对象属于第二类,它在不一样的JSP文件中表示的类型不同,可是属于同一类型的子类型。

page对象表明JSP文件自己,具体来讲就是表明JSP所转换成Servlet的对象,因为不一样的JSP文件转换成为Servlet的类型不同(类名根据JSP文件的名称而定,例如test.jsp的类名为test_jsp),因此该page对象在不一样的JSP文件中所具备的类型也不同。

在test.jsp中打印page对象的类型以下例所示:

示例9.6  test.jsp

<%@ page language="java" pageEncoding="GB2312" %>

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

<html>

<head>

<title>Test</title>

</head>

<body>

<%= page.toString() %>

</body>

</html>

访问该页面,获得运行结果如图9.8所示。

图9.8  在页面中打印page对象

由于test.jsp被转换成Servlet后,Servlet的类名为test_jsp,并且在包org.apache.jsp中,因此此处输出了page对象所表明的类,以及表明page对象的一个数字。

查看之前任何一个从JSP文件转换而成的java文件,或者打开test.jsp转换成的java文件,能够发现其中都有关于page的定义,以下:

Object page = this;

即将page对象定义为当前对象。

在JSP文件中,开发人员能够将page对象造型为Servlet对象,而后就能够将page对象当成当前Servlet的对象进行使用,进而能够使用Servlet接口中定义的全部方法,得到Servlet相关的信息。例如:

示例9.7

<%@ page language="java"  pageEncoding="GB2312" %>

<html>

<head>

<title>Test</title>

</head>

<body>

<% Servlet s = (Servlet)page; %>

<%= s.getServletConfig().getServletContext().getServletContextName() %>

</body>

</html>

得到页面内容如图9.9所示。

图9.9  在页面中打印ServletContextName

将page对象造型成Servlet后,就能够在JSP页面中调用Servlet的方法,包括经过Servlet得到ServletConfig(至关于config隐含对象)和ServletContext对象(至关于application隐含对象)。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

session对象

 

这里的session对象与HTTP协议中定义的Session一一对应,它表示客户端与Web服务器的一次会话过程;经过session对象,JSP页面能够获取此次会话相关的Session信息。在同一段时间内,与服务器交互的一个客户端只有一个session对象,session对象由服务器建立并管理;当客户端第一次访问服务器时对应的session对象就被建立,而且将该session对象与该客户端一一对应;日后同一客户端在访问服务器时,同一session对象将会与之关联。

经过session对象,开发人员能够得到服务器与某一客户端本次会话的相关信息,而且能够设置和获取session做用域内的属性。session是HttpSession的对象,HttpSession的定义和使用已在第8章作了介绍。

Java经过try/catch语句和Exception类体系提供了比较完善的异常处理机制。JSP文件最终被转换为Java文件,因此在JSP文件中固然也能够使用Java固有的异常处理机制。除此以外,JSP还提供了一种更宏观的异常处理机制,那就是errorPage。

在前面介绍page指令时,讲到page指令中有两个与errorPage相关的属性:errorPage和isErrorPage。其中errorPage指定了一个错误处理页面,假如当前页面中出现了JSP错误那么请求就会转向这个错误处理页面;isErrorPage指定当前页面是不是错误处理页面,假如当前页面是错误处理页面,那么当前页面就能够访问exception对象,而且经过分析exception对象对各类异常进行处理。这种异常处理机制将对异常的处理集中到一个页面,更加提升了代码的抽象度,更便于系统的维护。这种异常处理的逻辑如图9.10所示:

图9.10  JSP异常处理示意图

exception是标准的Exception类的对象,其方法和使用可参见Exception类的相关资料或Java文档。

假如某个JSP页面配置了errorPage属性,例如errorPage="errorHandler.jsp",那么转换成的Java文件中会有:

pageContext = _jspxFactory.getPageContext(this, request, response, "errorHandler.jsp", true, 8192, true);

其中的errorHandler.jsp是以参数的形式传递给了pageContext对象,而后将pageContext对象赋值给_jspx_page_context对象:

_jspx_page_context = pageContext;

最后在作异常处理时使用:

_jspx_page_context.handlePageException(t);

在该方法中,请求会被重定向到errorHandler.jsp,而且会将异常对象传递给该页面。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

PageContext对象

 

PageContext对象是PageContext类的实例。该对象是JSP页面各个对象和方法的门面对象,它提供了:

(1)得到JSP页面中各隐含对象的公有方法;

(2)方便地进行各类与当前页面相关的操做。

PageContext是个抽象类,其定义以下:

package javax.servlet.jsp;

import java.io.IOException;

import javax.servlet.*;

import javax.servlet.http.HttpSession;

import javax.servlet.jsp.tagext.BodyContent;

public abstract class PageContext extends JspContext

{

    public PageContext()

    {

    }

    public abstract void initialize(Servlet servlet, ServletRequest servletrequest, ServletResponse servletresponse, String s, boolean flag, int i, boolean flag1)

        throws IOException, IllegalStateException, IllegalArgumentException;

    public abstract void release();

    public abstract HttpSession getSession();

    public abstract Object getPage();

    public abstract ServletRequest getRequest();

    public abstract ServletResponse getResponse();

    public abstract Exception getException();

    public abstract ServletConfig getServletConfig();

    public abstract ServletContext getServletContext();

    public abstract void forward(String s)

        throws ServletException, IOException;

    public abstract void include(String s)

        throws ServletException, IOException;

    public abstract void include(String s, boolean flag)

        throws ServletException, IOException;

    public abstract void handlePageException(Exception exception)

        throws ServletException, IOException;

    public abstract void handlePageException(Throwable throwable)

        throws ServletException, IOException;

    public BodyContent pushBody()

    {

        return null;

    }

    public ErrorData getErrorData()

    {

        return new ErrorData((Throwable)getRequest().getAttribute("javax.servlet.error.exception"), ((Integer) getRequest().getAttribute("javax.servlet.error.status_code")).intValue(), (String)getRequest().getAttribute("javax.servlet.error.request_uri"), (String)getRequest().getAttribute("javax.servlet.error.servlet_name"));

    }

    public static final int PAGE_SCOPE = 1;

    public static final int REQUEST_SCOPE = 2;

    public static final int SESSION_SCOPE = 3;

    public static final int APPLICATION_SCOPE = 4;

    public static final String PAGE = "javax.servlet.jsp.jspPage";

    public static final String PAGECONTEXT = "javax.servlet.jsp.jspPageContext";

    public static final String REQUEST = "javax.servlet.jsp.jspRequest";

    public static final String RESPONSE = "javax.servlet.jsp.jspResponse";

    public static final String CONFIG = "javax.servlet.jsp.jspConfig";

    public static final String SESSION = "javax.servlet.jsp.jspSession";

    public static final String OUT = "javax.servlet.jsp.jspOut";

    public static final String APPLICATION = "javax.servlet.jsp.jspApplication";

    public static final String EXCEPTION = "javax.servlet.jsp.jspException";

}

其中:

initialize(Servlet servlet, ServletRequest servletrequest, ServletResponse servletresponse, String s, boolean flag, int i, boolean flag1):对该PageContext对象进行初始化。由于PageContext的构造函数是无参数构造函数,因此PageContext的对象并无被初始化,该方法将PageContext须要的参数传递给PageContext,并对PageContext对象进行初始化。该方法至关于JSP转化成的Servlet中的:

pageContext = _jspxFactory.getPageContext(this, request, response, "errorHandler.jsp", true, 8192, true);

而且其中参数的个数、顺序和含义均相同。

release():释放该PageContext对象。该方法对PageContext对象的内部状态进行重置,使该PageContext对象能够在initialize()后被从新使用。

getSession():得到session隐含对象。

getPage():得到page隐含对象。

getRequest():得到request隐含对象。

getResponse():得到response隐含对象。

getException():得到exception隐含对象。

getServletConfig():得到config隐含对象。

getServletContext():得到application隐含对象。

forward(String s):将请求前转到s指定的Web对象,其效果等同于<jsp:forward>标签。

include(String s):将s指定的Web对象包含处处理中,至关于该JSP页面向指定的Web对象发出请求,而且处理返回的响应。效果等同于<jsp:include>标签。

include(String s, boolean flag):除了起到上面方法的效果外,还提供了一个额外效果,即当flag为true时,该方法同时刷新输出缓冲区。

handlePageException(Exception exception):处理页面级的异常,即将参数提供的异常与请求一块儿转发给错误处理页面。

handlePageException(Throwable throwable):同上方法。

pushBody():返回一个新的BodyContent对象,保存当前out对象的内容。而且更新out属性的值。

getErrorData():返回一个ErrorData对象,在错误处理页面中该对象用于表示错误内容。在非错误处理页面中返回的对象是无心义的。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

对象属性的做用域

 

在JSP的这些隐含对象中,有四个对象都定义了相似的getAttribute()和setAttribute()方法。这四个对象均可以经过属性的名称设置和获取属性,但不一样的是这四个对象设置的属性具备不一样的做用域。这四个对象分别是:pageContext、request、session、application;分别表明四个做用域:页面、请求、会话、应用。

若是简单理解和记忆这几种做用域是很是低效的,并且很容易出错。其实,从本质上看,属性是针对对象进行设置的,调用某对象的setAttribute()方法是将某属性保存在该对象中,只有再次调用该对象的getAttribute()方法才可以获取此属性,调用其余对象的getAttribute()固然没法获取同一属性。因此,从本质上说,属性的做用域就是对象的做用域。在某个范围内若是使用的某个类的对象都是一个对象,那么该对象的做用域就是该范围,同时该范围也是对该对象设置的属性的做用域。图9.11示意了pageContext、request、session、application四种对象的做用域。

图9.11  四种对象做用域对比示意图

图9.11示意了四种对象的做用域。

application对象表示应用,在同一个应用中只有一个application对象,因此任何客户端的任何请求只要访问的是同一个application,那么访问的application对象就是一个,因此对application设置的属性也同样。在图9.11中,服务器中有两个应用:application1和application2。任何客户端访问application1中任何文件使用的application对象都是application1。

session对象表示会话,在同一个客户端访问同一个应用时(在Session超时时间内),只有一个session对象;而对于同一个应用来讲,不一样的客户端访问就会产生不一样的session对象。如图9.11中,客户端1访问application1的session对象是session1,而客户端2访问application1的session对象是session2。

request对象表示一次客户端请求,不管请求从哪里来以及访问哪里,一次请求就会产生一个request对象。如图9.11中,客户端2向1.jsp发出两次请求,那么每次请求就会产生一个request对象,即便1.jsp将request2传递给了2.jsp,仍是不会产生新request对象的。

pageContext对象表示一个页面对一次请求的处理。如图9.11中,客户端2经过request1访问1.jsp,1.jsp对request1进行处理就产生了pageContext1;客户端2经过request2访问1.jsp,1.jsp对request2进行处理就产生了pageContext2,进而1.jsp将request2传递给了2.jsp,2.jsp对request2进行处理就产生了pageContext3。

因此,经过9.11示意图能够发现,各个对象根据其表明意义的不一样,其做用域也不一样,因此对其进行设置的属性的做用域也不一样。总的来讲这四个对象的做用域从大到小依次为:application > session > request > pageContext。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发自定义标签

 

从前面对JSP的介绍能够发现,JSP是基于标签的语法,所谓标签就是用尖括号“<”和“>”括起的一个标识符;一般标签都是成对出现的,有起始标签和结束标签,例如<tag> ... </tag>,但若是标签中没有包含其余任何内容,也能够将起始标签和结束标签合并为一个标签,例如<tag />;起始标签中还能够添加属性。

最基本的,JSP继承了HTML的标签集,而且在其基础上扩充了本身的标签集,例如9.2.6节中介绍的JSP预约义标签。除此以外,JSP还提供一种程序员本身开发标签而且在JSP页面中使用标签的途径。JSP预约义标签是以jsp为前缀、具备特定功能的系统预约义标签,这些能够直接在JSP页面中使用。除此以外,程序员能够自行开发具备特定功能的标签,将这些标签引入到应用中,而后就能够在JSP页面中使用。

经过开发和使用自定义标签,开发人员能够将功能开发和页面开发的工做分开,功能开发人员关注开发功能组件,页面开发人员关注页面设计而且将开发的标签组件直接在页面中使用。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

自定义标签简介

 

自定义标签是JSP提供的一种自我扩展的方式,它容许开发人员本身定义标签以及标签的功能,而后在JSP页面中像预约义标签同样使用。

一个自定义标签在代码中就用一个javax.servlet.jsp.tagext.Tag表示,Tag中定义了标签的行为,当包含标签的JSP页面被转化为Servlet时,Servlet会在标签引用处调用Tag的相应方法进而实现标签的功能。

开发和使用一套自定义标签的步骤以下:

开发自定义标签类(或称自定义标签处理类);

定义一个标签订义文件(tld文件),在其中定义待使用的标签,而且将相应的标签处理类指定给标签;每一个标签具备一个惟一的名称;

将标签订义文件在web.xml中使用taglib进行声明;

在须要使用标签的JSP页面中使用<%@ taglib uri="..." prefix="..." %>进行声明,而后再用指定的prefix和名称使用标签。

由此能够知道,在定义一个标签以前首先须要肯定的是:标签的名称以及标签所须要进行的工做。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP标签体系

 

在JSP标签体系中根据标签的表现形式和功能定义了多种不一样的标签层次和类别,每个层次和类别都定义了一些标签所要进行的操做。JSP的标签都必须实现JspTag接口,这个接口是一个概念级别的接口,并无定义任何实质的操做,所以基本上全部标签都实现自JspTag接口的子接口,Tag接口,这个接口定义了一些方法,它们表示标签在使用时与标签相关联的一些操做。同时JSP还实现了TagSupport类和BodyTagSupport,用于为Tag提供一些默认的实现,方便开发人员在此基础上进行开发。与标签相关的接口和类的层次结构如图9.12所示:

图9.12  与标签相关的接口和类的层次结构

从图9.12中能够发现,JSP标签的顶级接口是JspTag,Tag接口继承自JspTag,IterationTag接口继承自Tag,BodyTag继承自IterationTag;TagSupport和BodyTagSupport是两个实现类,TagSupport分别实现了IterationTag和Serializable,说明TagSupport的对象是一个JSP标签而且这个对象能够序列化;BodyTagSupport实现了BodyTag接口而且继承自TagSupport,说明BodyTagSupport确定是一个TagSupport,并且它提供的功能比TagSupport要多,能够说BodyTagSupport是一个特殊的TagSupport。

【注意】

JSP自定义标签相关的接口和类都被封装在jsp-api.jar,它与servlet-api.jar同样,位于Tomcat根目录下的lib目录中。

1.JspTag

javax.servlet.jsp.tagext.JspTag接口表示一个概念上的JSP标签,它的定义以下:

package javax.servlet.jsp.tagext;

public interface JspTag {

}

该接口并无定义任何方法,该接口的存在只是为了定义一个概念,它是全部其余JSP标签接口和类的根接口,它主要用于组织类结构和类型检查等目的。

2.Tag

javax.servlet.jsp.tagext.Tag接口表示一个具备明确起始位置和结束位置的JSP标签,该接口定义了一些方法,这些方法会在处理标签引用时被调用。它的定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

public interface Tag extends JspTag {

    public abstract void setPageContext(PageContext pagecontext);

    public abstract void setParent(Tag tag);

    public abstract Tag getParent();

    public abstract int doStartTag() throws JspException;

    public abstract int doEndTag() throws JspException;

    public abstract void release();

    public static final int SKIP_BODY = 0;

    public static final int EVAL_BODY_INCLUDE = 1;

    public static final int SKIP_PAGE = 5;

    public static final int EVAL_PAGE = 6;

}

其中:

setPageContext(PageContext pagecontext):将JSP页面的PageContent对象设置到对象中,该方法一般由JSP容器调用。在Tag对象被初始化成功后,在调用其余任何处理方法以前被调用。

setParent(Tag tag):设置该Tag的父标签,也是有JSP容器调用。

getParent():返回该Tag的父标签。

doStartTag():在处理JSP页面时,在标签起始处调用的代码;该方法会返回标志EVAL_BODY_INCLUDE或SKIP_BODY,用于决定是继续处理标签(EVAL_BODY_INCLUDE)仍是跳过该标签(SKIP_BODY)。

doEndTag():在处理JSP页面时,在标签结束处调用的代码;该代码会返回标志EVAL_PAGE或SKIP_PAGE,用于决定是继续处理页面剩下的内容(EVAL_PAGE)仍是跳过页面剩下部分的处理(SKIP_PAGE)。

release():用于释放状态,在标签处理完成后会被JSP容器调用。

SKIP_BODY:标志位,表示跳过标签体的处理。

EVAL_BODY_INCLUDE:标志位,表示继续标签体的处理。

SKIP_PAGE:标志位,表示跳过页面剩余部分的处理。

EVAL_PAGE:标志位,表示继续页面剩余部分的处理。

从该接口定义的方法能够发现,该接口定义了一个标签处理器的框架,实现者须要在doStartTag()和doEndTag()中添加本身的代码,用于实现标签的功能。经过实现该接口子类能够实现一个对标签进行处理的最基本的能力。

3.IterationTag

javax.servlet.jsp.tagext.IterationTag继承自Tag,而且在Tag的基础上又增长了一个方法的定义。IterationTag是一个Tag而且增长了对Tag的处理,不只提供了在标签开始和结束时方法,并且还提供了一个在处理方法体后执行的方法。定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

public interface IterationTag extends Tag {

    public abstract int doAfterBody()  throws JspException;

    public static final int EVAL_BODY_AGAIN = 2;

}

其中:

doAfterBody():在标签体运行完后执行的代码,该方法返回标志EVAL_BODY_AGAIN或SKIP_BODY,用于决定是再次运行标签体仍是再也不运行标签体而直接转向doEndTag()。

EVAL_BODY_AGAIN:标志位,表示再次运行标签体。

该接口提供了一种实现迭代标签的途径,在迭代标签中,标签体内的内容或代码能够以迭代的形式屡次出现或执行。经过实现该接口子类能够实现一个具备迭代功能的标签。

4.BodyTag

javax.servlet.jsp.tagext.BodyTag继承自IterationTag接口,它在IterationTag的基础上又多定义了两个方法,提供了对标签体中内容进行操纵的支持。定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

public interface BodyTag extends IterationTag

{

    public abstract void setBodyContent(BodyContent bodycontent);

    public abstract void doInitBody() throws JspException;

    public static final int EVAL_BODY_TAG = 2;

    public static final int EVAL_BODY_BUFFERED = 2;

}

其中:

setBodyContent(BodyContent bodycontent):设置一个BodyContent对象,该对象表示标签体中的内容;该方法由JSP容器进行调用,使得实现BodyTag的类能够操纵标签的内容;该方法先于doInitBody()被调用。

doInitBody():在处理标签时,该方法会在运行标签体以前被调用,用觉得运行标签体作准备。

EVAL_BODY_BUFFERED:标志位,是BodyTag对Tag中定义的SKIP_BODY和EVAL_BODY_INCLUDE的扩展。当BodyTag的doStartTag()执行时,能够返回该标志位用于申请一个新的缓冲区,提供执行标签体时放置BodyContent。

EVAL_BODY_TAG:与EVAL_BODY_BUFFERED具备相同的值,这是之前使用的名字,如今已过时。

该接口的父接口更近了一步,它定义的标签能够对标签体中的内容进行操纵。经过实现这个接口子类能够实现一个能够对标签体中内容进行操纵的标签。

5.TagSupport

javax.servlet.jsp.tagext.TagSupport是一个具体的类,它实现了IterationTag,也就是说它是一个可迭代的标签。但TagSupport只是一个默认的实现,其实现并无实质性的逻辑。TagSupport是为了方便程序员的开发而存在的,程序员在开发一个IterationTag时就能够直接从TagSupport继承而不须要从实现接口开始。该类的声明以下:

package javax.servlet.jsp.tagext;

import java.io.Serializable;

import java.util.Enumeration;

import java.util.Hashtable;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

public class TagSupport implements IterationTag, Serializable {

    private Tag parent;

    private Hashtable values;

    protected String id;

    protected PageContext pageContext;

    public static final Tag findAncestorWithClass(Tag from, Class class);

    public TagSupport();

    public int doStartTag() throws JspException;

    public int doEndTag() throws JspException;

    public int doAfterBody() throws JspException;

    public void release();

    public void setParent(Tag t);

    public Tag getParent();

    public void setId(String id);

    public String getId();

    public void setPageContext(PageContext pageContext);

    public void setValue(String k, Object o);

    public Object getValue(String k);

    public void removeValue(String k);

    public Enumeration getValues();

}

其中:

parent:用于保存该标签的父标签。

values:一个属性表,其中经过键值对能够保存该Tag的属性,键是一个字符串,值是一个对象。

id:该标签id属性的值,或者是null。

pageContext:所在页面的PageContext对象。

findAncestorWithClass(Tag from, Class class):静态方法,该方法经过Tag接口的getParent()方法,从标签from开始一直寻找上级标签,直到寻找到第一个具备class类型的标签返回。

TagSupport():无参数构造方法,该方法体没有提供任何操做。

doStartTag():实现Tag接口的方法;TagSupport中的该方法直接返回SKIP_BODY,即默认实现将忽略标签。

doEndTag():实现Tag接口的方法;TagSupport中的该方法直接返回EVAL_PAGE,即继续对页面剩下的部分进行执行。因此,结合doStartTag()的实现,默认实现中TagSupport对标签不作任何处理,其效果至关于标签不存在。

doAfterBody():实现IterationTag接口的方法;TagSupport中的该方法直接返回SKIP_BODY,即任什么时候候都不重复执行标签体;其目的同前两个方法同样,就是效果等同于标签不存在。

release():实现Tag接口的方法;TagSupport中的该方法对本身的全部状态进行释放,恢复到初始状态,这包括:将parent设置为空,将id设置为空,清空values中的全部属性。

setParent(Tag t):实现Tag接口的方法;为TagSupport设置父标签,该方法只是将t设置给域变量parent。

getParent():实现Tag接口的方法;得到TagSupport的父标签,该方法只是返回域变量parent。

setId(String id):设置TagSupport的id,将参数id设置给域变量id。

getId():得到TagSupport的id,返回域变量id的值。

setPageContext(PageContext pageContext):实现Tag接口的方法;为TagSupport设置PageContext,该方法将参数pageContext设置给域变量pageContext。

setValue(String k, Object o):将键为k值为o的属性添加到values中。

getValue(String k):得到键为k的值。

removeValue(String k):删除键为k的属性。

getValues():得到一个Enumeration对象,其中包含全部属性的键;若是没有属性则返回空。

可见,TagSupport具有了一个IterationTag的概念,但并无提供任何实质性的实现,若是单纯地将一个TagSupport实现的标签添加到JSP页面中,那么这个标签并不会有任何效果,其结果至关于没有添加任何标签。TagSupport的存在是为了方便开发人员,当开发人员须要开发一个IterationTag时,只须要继承TagSupport类而且重写其中的一些标签处理方法(doStartTag()、doEndTag()和doAfterBody());另外TagSupport还提供了一些方便的属性和操做,好比静态方法findAncestorWithClass(),开发人员能够在开发标签时直接调用该方法;TagSupport还为其子类提供了对id、属性和PageContext对象的管理。经过继承TagSupport开发新的标签能够使程序员只关注标签的业务逻辑。

6.BodyTagSupport

与TagSupport相似,javax.servlet.jsp.tagext.BodyTagSupport也是一个具体的类,它继承了TagSupport类,而且还另外实现了BodyTag接口,这使得BodyTagSupport具有了TagSupport的属性和处理能力,并且又增长了BodyTag的特性。该类的声明以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.JspWriter;

public class BodyTagSupport extends TagSupport implements BodyTag {

    protected BodyContent bodyContent;

    public BodyTagSupport();

    public int doStartTag() throws JspException;

    public int doEndTag() throws JspException;

    public void setBodyContent(BodyContent b);

    public void doInitBody()  throws JspException;

    public int doAfterBody() throws JspException;

    public void release();

    public BodyContent getBodyContent();

    public JspWriter getPreviousOut();

}

其中:

bodyContent:BodyContent的对象,经过setBodyContent()方法设置进来,能够在其余方法中使用,但不能在构造方法中使用,由于在构造方法中setBodyContent()方法还不可能被调用,因此bodyContent对象仍是空。

BodyTagSupport():构造方法,同TagSupport同样,该类的构造方法体也为空。

doStartTag():覆盖TagSupport中的同名方法,不是返回SKIP_BODY,而是返回EVAL_BODY_BUFFERED,由于BodyTagSupport是一个BodyTag,因此返回该标志用于申请一个缓冲区。

doEndTag():覆盖TagSupport中的同名方法,可是这里仍是返回了EVAL_PAGE,其用意与TagSupport也是同样的。

setBodyContent(BodyContent b):实现BodyTag的方法;将参数b设置给域变量bodyContent。

doInitBody():实现BodyTag的方法;方法体中并无提供任何操做,对于该方法而言没有任何操做就是一种恰当的默认实现。

doAfterBody():覆盖TagSupport中的同名方法,可是这里仍是返回了SKIP_BODY,默认实现。

release():覆盖TagSupport中的同名方法,在该方法中除了调用父类中的同名方法用于释放父类中的域,还要将bodyContent置为空。

getBodyContent():得到该标签的BodyContent对象,该方法中返回域变量bodyContent。

getPreviousOut():得到BodyContent所封装的JspWriter对象,经过该对象能够向BodyContent中写入内容。

BodyTagSupport是在TagSupport的基础上又添加了对BodyTag接口的默认实现。BodyTagSupport与TagSupport的区别主要是在处理标签时是否须要与标签体进行交互,若是不须要交互就用TagSupport,若是须要交互就要用BodyTagSupport。或者能够反过来讲,若是开发人员想开发一个须要标签体交互的标签就用BodyTagSupport,不然就用TagSupport。这里所谓的交互就是处理标签时是否要读取标签体的内容或改变标签体的内容。因为BodyTagSupport是TagSupport的子类,因此全部用TagSupport实现的标签也能够用BodyTagSupport来实现,只是将BodyTagSupport实现BodyTag接口的方法保持为默认实现便可。可是,仍是建议读者对于不须要交互的标签使用TagSupport实现,由于这样会避免多余的操做。

7.BodyContent

在实现了BodyTag的类中,包括BodyTagSupport。BodyContent是一个很是重要的结构,在须要和标签内容进行交互的标签中,BodyContent就是BodyTag子类与所处理标签内容进行交互的媒介;BodyTag子类经过BodyContent类的方法对标签的内容进行获取、写入、清空等操做。

BodyContent实质上是一个对JspWriter的封装,它对向JSP页面内容进行写入的Writer封装起来而且向外提供一个JspWriter的接口。在BodyContent的实现中,它保持了一个缓冲,在外部调用其写入方法时写入的内容会被放入BodyContent的缓冲中;同时BodyContent还提供了读取方法,读取方法会返回一个读取缓冲区内容的Reader;清空内容的方法会将缓冲中的内容清空。

一个标签的内容就是一个BodyContent对象,嵌套标签会产生BodyContent对象之间的相互包含;BodyContent的初始内容是标签中原本已有的内容,在对标签进行处理时能够读取、写入和清空这些内容,标签中的最终的内容就是标签处理完后BodyContent的内容。

这里须要注意的是,BodyContent的内容是JSP原始内容的执行结果而非原始内容。例如:

<tag>

<% out.print(“Hello”) %>

</tag>

在处理tag标签时,BodyContent的内容是“Hello”而不是“<% out.print(“Hello”) %>”。

BodyContent类的声明以下:

package javax.servlet.jsp.tagext;

import java.io.*;

import javax.servlet.jsp.JspWriter;

public abstract class BodyContent extends JspWriter {

    private JspWriter enclosingWriter;

    protected BodyContent(JspWriter e);

    public void flush() throws IOException;

    public void clearBody();

    public abstract Reader getReader();

    public abstract String getString();

    public abstract void writeOut(Writer writer) throws IOException;

    public JspWriter getEnclosingWriter();

}

其中:

enclosingWriter:JspWriter的对象,是BodyContent对象封装的JspWriter;

BodyContent(JspWriter e):构造函数,将e传递给封装的enclosingWriter;

flush():实现了JspWriter的flush()方法,在BodyContent中该方法被声明为无效方法,由于对BodyContent调用该方法没有意义,因此在使用BodyContent时不要调用flush()方法;

clearBody():清空BodyContent的内容;

getReader():得到读取BodyContent内容的Reader;

getString():将BodyContent的内容做为字符串返回;

writeOut(Writer writer):将BodyContent的内容写入到指定的writer;

getEnclosingWriter():返回被BodyContent封装的JspWriter,即返回enclosingWriter对象。

BodyContent类是一个抽象类,其中的getReader()、getString()、writeOut()都没有提供具体的实现,它们的实现由软件提供商实现,但方法的做用和意义是不会改变的。做为开发Web应用的开发人员,应该针对BodyContent提供的接口进行编程。

8.标签处理流程

在前面介绍了三种标签接口:Tag、IterationTag和BodyTag,它们分别定义了一些方法,这些方法分别在标签处理过程当中的不一样时间点被调用,并且某些方法是否被调用以及调用的次数还可能取决于前面方法返回的结果。这些方法包括:Tag接口的doStartTag()方法和doEndTag()方法、IterationTag的doAfterBody()、BodyTag的setBodyContent()和doInitBody();返回值包括:EVAL_BODY、SKIP_BODY、EVAL_BODY_AGAIN、EVAL_BODY_BUFFERED。

下面咱们分别针对不一样类型标签的处理过程,详细介绍各个方法被调用的状态转换图。

IterationTag继承Tag,BodyTag又继承IterationTag,因此某个类对这几个接口的实现可能存在以下几种状况:

Tag:实现类只实现了Tag标签。对于这种状况而言所实现的标签是一个不可迭代并且不须要与内容进行交互的标签。因此,它的处理方法只有doStartTag()和doEndTag()。执行的流程图如图9.13所示。

如图9.13所示,Tag子类的在被建立之后,首先会经过setPageContext()、setParent()、setId()等方法将Tag的初始信息传递给Tag;而后在处理到标签开始处时调用doStartTag()方法,根据方法的返回值肯定是否要继续执行标签体内的内容;在标签结束处调用doEndTag()方法,根据方法的返回值肯定是否继续处理页面的剩余部分。

IterationTag:实现类实现了IterationTag,那实现类也自动实现了Tag接口。这说明所实现的标签是一个可迭代但无需与标签体进行交互的标签。该类中的处理方法会增长一个doAfterTag()方法。处理的流程图如图9.14所示:

如图9.14所示,IterationTag的执行流程图是在Tag执行流程图的基础上添加了对doAfterTag()方法的调用和判断,添加了对标签体中内容的屡次执行逻辑。

图9.13  Tag子类执行流程图    

        图9.14  IterationTag子类执行流程图

BodyTag:实现类实现了BodyTag,也就自动实现了Tag和IterationTag。这说明实现的标签是一个可迭代并且须要与标签内容进行交互的标签。这种标签又在IterationTag的基础上增长了setBodyContent()方法和doInitBody()方法,并且在实现类的其余方法中还能够引用BodyContent对象对标签的内容进行操纵。处理的流程图如图9.15所示:

从图9.15能够发现,与IterationTag对Tag的扩展相似,BodyTag也在doStartTag()与doEngTag()之间对Tag进行了扩展。BodyContent是经过setBodyContent()方法被设置到类中的,因此在BodyContent对象只有在setBodyContent()方法调用后才能被使用,从流程图中能够很容易地发现,在doStartTag()被执行时BodyContent尚未被赋值,所以是不能使用的;而在doInitBody()、doAfterBody()和doEndTag()中就能够使用。

图9.15  BodyTag子类执行流程图

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发Tag级别的标签

 

从前面对JSP标签体系的介绍能够发现,在JSP中标签根据其功能分为三个级别:Tag、IterationTag和BodyTag。从本小节开始的如下三个小节将分别举例介绍这三个级别标签的开发。本小节首先介绍Tag级别的标签。

Tag标签是只实现了Tag接口而没有实现IterationTag和BodyTag接口的标签。Tag标签只定义了标签的起始和结束,并分别在标签的起始和结束处调用接口的方法对标签进行处理。下面针对一种特定的标签使用场景开发一个Tag标签。

1.标签使用场景

假设在开发的系统中要添加一种日志功能:要求为每一个JSP页面添加访问日志,在JSP页面被访问时在Tomcat的日志文件中记录一条日志,包括日志内容访问时间以及在访问该JSP页面的客户端主机名。

关于日志功能,咱们在第8.7.3节中已经介绍了如何用ServletContext对象记录日志。完成这个功能能够经过在JSP页面中添加一个代码块,在代码块中获取ServletContext对象进行日志记录;日志功能自己会记录当前时间,客户端主机名能够经过ServletRequest的getRemoteHost()方法得到。开发的test.jsp以下:

test.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<% 

String host = request.getRemoteHost();

config.getServletContext().log("test.jsp: " + host); 

%>

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

<title>Insert title here</title>

</head>

<body>

...

</body>

</html>

在该test.jsp中添加一块Java代码块,得到host后,将JSP文件名和host一同写入日志,日志内容以下:

Jun 23, 2008 4:05:10 PM org.apache.catalina.core.ApplicationContext log

INFO: test.jsp: 127.0.0.1

若是在全部JSP页面中都添加这么一段代码来实现日志功能,那将会很烦琐并且还很容易出错。因此,但愿开发一个通用标签,在JSP页面中只须要将这个标签添加到页面中就能够完成一样的日志功能,使用格式以下:

<mytag:loghost jspfile="test.jsp"/>

2.开发标签

前面已经将标签的功能描述得很是清楚,能够发现该标签不须要迭代处理也不须要与标签内容进行交互,因此只须要实现Tag接口便可。具体的实现以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.Tag;

public class LogTag implements Tag {

private PageContext context;

private String fileName;

public int doEndTag() throws JspException {

String host = context.getRequest().getRemoteHost();

context.getServletContext().log(fileName + ": " + host);

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

return EVAL_BODY_INCLUDE;

}

public Tag getParent() {

return null;

}

public void release() {

}

public void setPageContext(PageContext arg0) {

context = arg0;

}

public void setParent(Tag arg0) {

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

实现Tag接口必须实现它的全部方法,但这里只须要为doStartTag()和doEndTag()方法添加适当的内容便可。另外,因为该标签还有一个jspfile的属性,因此还必须为其提供一个setter方法。

doStartTag():该方法是在遇到标签起始位置时被调用的。在该标签的实现中咱们将实现日志功能的代码放到标签结束位置,因此该方法不须要提供任何实现代码,直接返回便可。因为该标签不支持标签中包含的任何内容,因此此处返回SKIP_BODY和EVAL_BODY_INCLUDE都不影响标签功能的实现。

doEndTag():该实现没有在doStartTag()中添加功能代码,而是选择在doEndTag()中实现标签的功能,因此该方法中就必须实现日志功能。具体的实现代码与在JSP中添加Java代码块实现的代码相似。最后,返回EVAL_PAGE让JSP继续对页面其余内容进行解析,保证添加该标签后不影响页面其余内容的正常处理。

setJspfile():因为该标签中包含了一个jspfile的属性用于指定记录日志的JSP文件的文件名,以便于在日志中包含JSP文件名。JSP的实现规定,标签的实现必须为标签的每一个属性定义一个setter方法,并且setter方法的方法名也应该符合命名规范:set + 属性名(首字母变大写)。例如jspfile属性的setter方法就是setJspfile()。这个方法名是大小写敏感的,因此在实现时要注意方法名中字母的大小写。即便将setJspfile()写成setJspFile()也会致使错误。可是,在实现类中所定义的对应属性的名称没有任何限制,例如本实现中的fileName也是能够的。

在该实现中将标签功能的实现放在了doEndTag()中。其实对于该标签来讲,因为它的标签体中不会包含任何内容,因此将标签功能的实现放在doStartTag()或doEndTag()中都是可行的。将标签功能放在doStartTag()方法中实现,而且让doStartTag()方法返回SKIP_BODY的实现以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.Tag;

public class LogTag implements Tag {

private PageContext context;

private String fileName;

public int doEndTag() throws JspException {

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

String host = context.getRequest().getRemoteHost();

context.getServletContext().log(fileName + ": " + host);

return SKIP_BODY;

}

public Tag getParent() {

return null;

}

public void release() {

}

public void setPageContext(PageContext arg0) {

context = arg0;

}

public void setParent(Tag arg0) {

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

这个实现所得到的效果与上一个实现相同。

这两个实现都是直接从实现Tag接口开始的,这会使得这其中的许多方法都显得不少余,好比:getParent()、release()、setParent(Tag)方法。为了不这个问题,也能够经过继承TagSupport类来实现标签,虽然这个标签实现了IterationTag接口,但正如前面提到的,TagSupport对IterationTag中定义方法的默认实现并不会对标签产生任何附加的影响。因此若是经过继承TagSupport类来实现一个不可迭代的标签,只须要不对TagSupport的doAfterBody()方法提供覆盖实现便可。这样,开发人员只须要实现本身关心的方法,而不用在实现类中列举多余的方法实现。经过继承TagSupport实现LogTag的代码以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class LogTag extends TagSupport {

private String fileName;

public int doEndTag() throws JspException {

String host = pageContext.getRequest().getRemoteHost();

pageContext.getServletContext().log(fileName + ": " + host);

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

return EVAL_BODY_INCLUDE;

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

3.配置和使用标签

完成了标签类的开发并不能直接在JSP页面中使用标签,由于Tomcat服务器并不知道标签类的存在,也并不知道标签的格式,因此必需要对标签进行定义而且将定义标签的标签库的声明添加到web.xml中。

在开发完标签类后,首先要将标签添加到一个标签库中,也能够本身建立一个新的标签库。

标签库一般是一个tld文件,其内容是一个XML文档,例以下面是一个自定义的标签文件的内容:

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com /j2ee/dtds/web-jsptaglibrary_1_1.dtd"> 

<taglib> 

    <tlibversion>1.2</tlibversion> 

    <jspversion>1.1</jspversion> 

    <shortname>MyTag</shortname> 

    <uri>/mytag</uri> 

    <tag> 

        <name>log</name> 

        <tagclass>cn.csai.web.jsp.LogTag</tagclass> 

        <bodycontent>empty</bodycontent> 

        <attribute> 

            <name>jspfile</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

</taglib>

标签订义的XML文档的根元素是taglib,tlibversion是标签库格式的版本,jspversion是JSP标准的版本,shortname是为标签库定义的一个名称,uri是引用该标签库的URI。一个标签库能够定义多个标签,每一个标签使用一个tag元素,tag元素的name子元素是标签的名称;tagclass子元素是实现该标签的类的全路径;bodycontent是指定该标签内容的形式,能够是tagdependent、JSP和empty,empty就表示该标签不该该包含有内容;attribute为该标签订义了该标签可能会包含的属性,name是属性的名称,required说明该属性是不是必须的。这个示例就是LogTag的标签订义。在定义文件中定义的属性要与标签实现类中定义的属性setter方法相一致,这里定义了多少的属性就须要在实现类中定义相同数量的setter方法,并且setter方法的名字要符合命名规范。

将该标签订义文件命名为MyTag.tld,将其放在Web应用的WEB_INF目录中。而且还须要在web.xml中添加对标签订义文件的引用:

<web-app ...>

<jsp-config>

<taglib>

<taglib-uri>/mytag</taglib-uri>

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

</taglib>

</jsp-config>

...

</web-app>

接下来就能够在JSP页面中使用自定义标签了,格式以下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<mytag:log jspfile="test.jsp" />

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

<title>Insert title here</title>

</head>

<body>

...

</body>

</html>

在JSP页面中使用标签,首先要在JSP页面中经过taglib添加标签应用声明,taglib中的uri就对应web.xml中taglib-uri所指定的uri,prefix是开发人员本身定义的一个前缀,这里能够随意定义。

在使用标签时,标签名是taglib中声明的prefix加上标签在标签库中定义的名称,这里就是mytag:log。

对于Tomcat来讲,它是一次性就将一个标签库引入进JSP页面中,而使用标签时一次只会使用一个标签,因此当有多个标签时,能够将多个标签订义到同一个标签库定义文件中,这样就能够经过一次引用而使用标签库中的全部标签。

将Web应用部署到Tomcat中后,访问test.jsp文件,而后查看Tomcat的日志文件就能够发现:在localhost当前日志的最后多了一条日志记录:

Jun 23, 2008 5:03:39 PM org.apache.catalina.core.ApplicationContext log

INFO: test.jsp: 127.0.0.1

4.标签处理的本质

在前面已经讲过,全部的JSP页面最终都会被转换为一个Servlet进行工做,因此只要查看JSP所转化的Servlet就能够明白自定义标签处理的本质。以上面使用LogTag的test.jsp为例,它转化的Servlet以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  static {

    _jspx_dependants = new java.util.ArrayList(1);

    _jspx_dependants.add("/WEB-INF/MyTag.tld");

  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmytag_005flog_ 005fjspfile_005fnobody;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody = org.apache.jasper. runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());

    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()). getExpressionFactory();

    _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext(). getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.release();

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=ISO-8859-1");

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

      null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\n");

      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/ TR/html4/loose.dtd\">\n");

      out.write("<html>\n");

      out.write("<head>\r\n");

      if (_jspx_meth_mytag_005flog_005f0(_jspx_page_context))

        return;

      out.write("\n");

      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

      out.write("<title>Insert title here</title>\n");

      out.write("</head>\n");

      out.write("<body>\n");

      out.write("\n");

      out.write("</body>\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

  private boolean _jspx_meth_mytag_005flog_005f0(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LogTag _jspx_th_mytag_005flog_005f0 = (cn.csai.web.jsp.LogTag) _005fjspx_ 005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.get(cn.csai.web.jsp.LogTag.class);

    _jspx_th_mytag_005flog_005f0.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flog_005f0.setParent(null);

    _jspx_th_mytag_005flog_005f0.setJspfile("test.jsp");

    int _jspx_eval_mytag_005flog_005f0 = _jspx_th_mytag_005flog_005f0.doStartTag();

    if (_jspx_th_mytag_005flog_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.reuse(_jspx_th_mytag_005flog_005f0);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.reuse(_jspx_th_mytag_005flog_005f0);

    return false;

  }

}

查看代码中的黑体部分。首先,Servlet中添加了一个TagHandlerPool类的对象,该对象专门用于处理自定义标签的转换。该对象在_jspInit()中被初始化,在_jspDestory()中被释放。

而对标签处理的核心代码都在_jspService()中,体如今以下这句:

if (_jspx_meth_mytag_005flog_005f0(_jspx_page_context))

        return;

该句调用方法_jspx_meth_mytag_005flog_005f0(PageContext),而且判断返回值,若是返回值为true则返回_jspService()方法,终止对页面剩余部分的处理,不然继续执行代码剩下的部分。这就天然会与doEndTag()方法的两个返回值(SKIP_PAGE和EVAL_PAGE)联系起来。

_jspx_meth_mytag_005flog_005f0(PageContext)方法的定义就在类声明中,下面咱们详细考察一下这个方法。这个方法接受一个PageContext对象做为参数,返回值是一个boolean变量。在方法中,首先以LogTag类为参数得到一个LogTag类的对象:

cn.csai.web.jsp.LogTag _jspx_th_mytag_005flog_005f0 = 

      (cn.csai.web.jsp.LogTag) _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody

      .get(cn.csai.web.jsp.LogTag.class);

而后分别调用LogTag对象的setPageContext()方法和setParent()方法,将PageContext对象和当前标签的父标签赋给LogTag对象。实质上这两个方法是Tag接口的方法。接下来调用LogTag的属性setter方法setJspfile(),将属性参数设置进LogTag。这些都是执行标签处理代码前的准备工做。

准备工做作完了之后就调用LogTag对象的doStartTag()方法和doEndTag()方法,而且判断doEndTag()方法的返回值;若是返回值为SKIP_PAGE则返回true,不然返回false。这偏偏应证了前面的猜想。

因此,对于含有自定义标签的JSP页面来讲,它的处理就是在JSP页面转化而成的Servlet中添加了对标签处理类相关方法调用,而且将调用的返回值用于影响_jspService()方法的处理流程。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发IterationTag级别的标签

 

Tag标签是标签内容不可迭代执行的标签或者不包含标签内容的标签,多数用于实现功能比较单一的标签。而从IterationTag实现的标签会比Tag标签稍微复杂一些,它的标签内容能够迭代执行。下面咱们用一个IterationTag的实例介绍IterationTag的开发。

1.标签使用场景

考虑以下的使用场景:在页面中常常须要让一段内容重复出现屡次,例如重复打印一段文本。直接使用JSP代码块也能够实现这个功能,例以下面的test2.jsp在页面上连续输出三行Hello World:

test2.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<%for(int i=0;i<3;i++) { %>

Hello world!<br>

<%} %>

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

<title>Insert title here</title>

</head>

<body>

</body>

</html>

页面显示如图9.16所示。

图9.16  test2.jsp页面效果

这种实现显然是能够达到目的的,可是因为须要在JSP页面中添加Java代码,就会使页面显得比较凌乱;假如须要重复的内容再复杂一些,就更容易出现错误。下面咱们就介绍如何使用一个自定义标签来实现这个功能,使用方式以下:

<mytag:repeat times="3">

Hello world!<br>

</mytag:repeat>

其中,times表示要重复的次数,标签之间的内容就是要重复的内容。

2.开发标签

显而易见,该标签是须要进行迭代的标签,因此不能使用Tag接口,而使用IterationTag。TagSupport实现了IterationTag,因此这里就能够直接继承TagSupport来实现。实现的标签以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class RepeatTag extends TagSupport {

private int counter = 0;

private int repeatTimes;

@Override

public int doAfterBody() throws JspException {

counter++;

if (skip())

return SKIP_BODY;

return EVAL_BODY_AGAIN;

}

@Override

public int doEndTag() throws JspException {

return EVAL_PAGE;

}

@Override

public int doStartTag() throws JspException {

if (skip())

return SKIP_BODY;

return EVAL_BODY_INCLUDE;

}

public void setTimes(String rep) {

try {

repeatTimes = Integer.parseInt(rep);

} catch (NumberFormatException e) {

repeatTimes = 0;

}

}

private boolean skip() {

if (counter >= repeatTimes) {

return true;

}

return false;

}

}

给该标签类取名为RepeatTag。该标签有一个参数times,用于说明内容须要重复的次数,因此在标签类中首先必须声明一个setter方法,setTimes()用于将times属性的值传递给域变量repeatTimes。在类中定义另外一个域变量counter用于记录重复执行内容的次数,当counter大于等于repeatTimes时就退出重复执行内容的循环,因此实现了一个skip()方法用于判断是否该退出循环。

在doStartTag()方法中,首先判断是否该退出循环,这是由于若是指定的times小于等于0那就是不执行内容。因此若是在doStartTag()调用skip()时输出为true则返回SKIP_BODY,表示不执行内容;不然返回EVAL_BODY_INCLUDE便表示执行内容。

在doAfterBody()方法中,首先将counter自增1,由于执行到doAfterBody()方法时标签体已经执行了一遍了,而后再判断是否应该退出,若是不该该退出则返回EVAL_BODY_AGAIN再继续执行标签体,直到执行的次数达到为止,此时返回SKIP_BODY退出循环。

在doEndTag()方法中,不须要作其余操做,只要返回EVAL_PAGE保证页面剩余部分被正确解析。

3.配置和使用标签

不管是什么标签,配置都是相似的,都须要放在标签订义文件中。在第9.4.4节中已经定义了MyTag.tld,并且在其中定义了LogTag;如今开发的新的标签只须要在MyTag.tld中再添加RepeatTag的定义就能够了,以下所示:

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee /dtds/web-jsptaglibrary_1_1.dtd"> 

<taglib> 

    <tlibversion>1.2</tlibversion> 

    <jspversion>1.1</jspversion> 

    <shortname>MyTag</shortname> 

    <uri>/mytag</uri> 

    <tag> 

        <name>log</name> 

        <tagclass>cn.csai.web.jsp.LogTag</tagclass> 

        <bodycontent>empty</bodycontent> 

        <attribute> 

            <name>jspfile</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

    <tag> 

        <name>repeat</name> 

        <tagclass>cn.csai.web.jsp.RepeatTag</tagclass> 

        <bodycontent>JSP</bodycontent> 

        <attribute> 

            <name>times</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

</taglib>

只须要在taglib元素下再添加一个tag元素就能够了,tag元素中的声明含义不变,只是因为该标签是包含标签体内容的,因此bodycontent不能设为empty,而应该设为JSP。

在test2.jsp中使用RepeatTag的格式以下:

test2.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4 /loose.dtd">

<html>

<head>

<mytag:repeat times="3">

Hello world!<br>

</mytag:repeat>

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

<title>Insert title here</title>

</head>

<body>

</body>

</html>

如其中黑体部分所示,其含义就是将“Hello world!<br>”输出3次,页面如图9.17所示:

图9.17  使用RepeatTag标签的test2.jsp页面效果

4.标签处理的本质

为了探究IterationTag标签处理的本质,咱们对test2.jsp所转化的Servlet进行了研究,Servlet以下所示。

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test2_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  static {

    _jspx_dependants = new java.util.ArrayList(1);

    _jspx_dependants.add("/WEB-INF/MyTag.tld");

  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes = org.apache.jasper.runtime.TagHandlerPool. getTagHandlerPool(getServletConfig());

    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()). getExpressionFactory();

    _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext(). getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.release();

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=ISO-8859-1");

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

      null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\n");

      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org /TR/html4/loose.dtd\">\n");

      out.write("<html>\n");

      out.write("<head>\r\n");

      if (_jspx_meth_mytag_005frepeat_005f0(_jspx_page_context))

        return;

      out.write("\n");

      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

      out.write("<title>Insert title here</title>\n");

      out.write("</head>\n");

      out.write("<body>\n");

      out.write("\n");

      out.write("</body>\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

  private boolean _jspx_meth_mytag_005frepeat_005f0(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.RepeatTag _jspx_th_mytag_005frepeat_005f0 = (cn.csai.web.jsp.RepeatTag) _005fjspx_ 005ftagPool_005fmytag_005frepeat_005ftimes.get(cn.csai.web.jsp.RepeatTag.class);

    _jspx_th_mytag_005frepeat_005f0.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005frepeat_005f0.setParent(null);

    _jspx_th_mytag_005frepeat_005f0.setTimes("3");

    int _jspx_eval_mytag_005frepeat_005f0 = _jspx_th_mytag_005frepeat_005f0.doStartTag();

    if (_jspx_eval_mytag_005frepeat_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      do {

        out.write("\r\n");

        out.write("Hello world!<br>\r\n");

        int evalDoAfterBody = _jspx_th_mytag_005frepeat_005f0.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

    }

    if (_jspx_th_mytag_005frepeat_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.reuse(_jspx_th_mytag_005frepeat_005f0);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.reuse(_jspx_th_mytag_005frepeat_005f0);

    return false;

  }

}

从代码中能够发现,整体的结构与test.jsp的Servlet相似。只是标签处理函数有了一些变化,添加了一些处理代码。观察方法中的黑体部分,首先仍是调用doStartTag()方法,若是doStartTag()返回值不等于SKIP_BODY则继续执行标签体。但这里处理标签体倒是使用了一个do-while循环;首先输出标签体中的内容而后调用doAfterBody()方法,若是返回值不是EVAL_BODY_AGAIN则跳出循环,不然继续循环输出标签体。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发BodyTag级别的标签

 

BodyTag是在IterationTag的基础上又添加了与标签体中内容的交互。

1.标签使用场景

考虑下面的一种使用场景:但愿在页面中显示一段文字,并且这段文字能够根据客户端浏览器的语言设置而动态变化,好比若是客户端浏览器使用的是英文语言设置则返回“Hello,××!”,若是客户端浏览器使用的是中文语言设置则返回“你好,××!”。

浏览器所使用的语言能够在浏览器的选项中进行设置,在IE的菜单Tools(工具)→Internet Options(Internet选项)...选项打开的窗口中的General(常规)页的下部有一个 Languages(语言)...按钮,如图9.18所示。

图9.18  Internet Options窗口

点击Languages...按钮打开Language Preference窗口,如图9.19所示。

图9.19  Language Preference窗口

能够经过Add...添加新的语言,经过Move Up和Move Down移动语言的上下顺序,在最上面的语言是最优选的语言。

另外在服务器端,在Servlet中,能够经过ServletRequest的getLocale()方法得到客户端所使用的语言,如图9.19中显示的两个语言设置分别是中文和英文,所对应的Locale就是Locale.Chinese和Locale.US,两个Locale的缩写代码分别是zh_CN和en_US。程序员能够经过为浏览器设置不一样的Locale,而后测试开发的功能是否正确。

假设咱们要开发一个JSP页面,页面根据客户端设置的不一样Locale分别显示“你好,王先生!”和“Hello, Mr. Wang!”。

根据不一样的Locale显示不一样语言的字符串,这在Java中已经提供了支持。首先,为每一个Locale建立一个资源properties文件,文件名包含Locale的缩写代码,例如MessageBundle_zh_CN.properties和MessageBundle_en_US.properties;这两个文件的文件名都是MessageBundle,但它们分别定义了针对中文和英文的两套资源文件。而后,在每一个资源文件中为每个须要进行多语言支持的字符串定义一个键值对,每一个资源文件中的键名同样,但值分别用资源文件对应的语言进行表达,例如:

表9.2  不一样资源文件中键值对的描述

同时,还能够定义一个默认的properties,即当客户端设置的既不是中文也不是英文时,就用默认的语言值,一般默认的与英文的同样,只是文件名不包含任何Locale的缩写,而直接是MessageBundle.properties。

定义完资源文件,而后实现一个类用于从资源文件中获取值,以下面的Message类:

package cn.csai.web.jsp;

import java.util.Locale;

import java.util.MissingResourceException;

import java.util.ResourceBundle;

public class Messages {

private static final String BUNDLE_NAME = "cn.csai.web.jsp.resource.MessageBundle"; 

private Messages() {

}

public static String getString(String key, Locale locale) {

try {

ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);

return bundle.getString(key);

} catch (MissingResourceException e) {

return "";

}

}

}

该类定义了一个静态方法getString(),该方法接受一个String和一个Locale对象做为参数,表示取Locale属性文件中键为key的值。例如getString(“hello”, Locale.Chinese)就是“你好”,而getString(“mrWang”, Locale.US)就是Mr. Wang。

经过这一套体系就能够完成根据不一样Locale获取不一样语言的文字的功能。下面就关注于如何在JSP页面中添加这个功能。

假如不使用自定义标签,那么只能在页面中添加Java代码以实现这个功能:

test3.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ page import="cn.csai.web.jsp.Messages, java.util.Locale" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<% 

Locale locale = request.getLocale();

String s = Messages.getString("hello", locale) + ", " + Messages.getString("mrWang", locale) + "!";

%>

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

<title>Insert title here</title>

</head>

<body>

<%= s %>

</body>

</html>

下面咱们将介绍如何使用自定义标签的方式实现这个功能,以使这个功能可以被通用。

2.开发标签

根据该功能要求,须要开发一个能够根据客户端浏览器Locale设置输出多种语言的标签。设计标签的格式以下:

<mytag:locale>hello</mytag:locale>

标签的内容包含的是所须要输出消息的键。

因为该标签须要输出标签内容,因此首先能够确定该标签必须是一个BodyTag标签,为了简单起见,继承BodyTagSupport来实现LocaleTag。标签的实现以下:

package cn.csai.web.jsp;

import java.io.IOException;

import java.util.Locale;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class LocaleTag extends BodyTagSupport {

@Override

public int doStartTag() throws JspException {

return EVAL_BODY_BUFFERED;

}

@Override

public int doEndTag() throws JspException {

String key = bodyContent.getString().trim();

Locale locale = pageContext.getRequest().getLocale();

String message = Messages.getString(key, locale);

try {

bodyContent.getEnclosingWriter().print(message);

} catch (IOException e) {

}

return SKIP_BODY;

}

}

标签不含任何属性,因此也不须要实现任何setter方法;在doStartTag()中,因为后期须要对标签内容进行处理,因此返回EVAL_BODY_BUFFERED;主要的代码集中在doEndTag()中,当标签处理结束后首先得到标签体中的内容,而后结合获取的Locale;接着经过Messages方法获取适当的消息值,而后经过bodyContent的输出对象输出到标签内容中。

3.配置和使用标签

与RepeatTag同样,LocaleTag能够定义在MyTag.tld中,以下所示:

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com /j2ee/dtds/web-jsptaglibrary_1_1.dtd"> 

<taglib> 

    <tlibversion>1.2</tlibversion> 

    <jspversion>1.1</jspversion> 

    <shortname>MyTag</shortname> 

    <uri>/mytag</uri> 

    ...

    <tag> 

        <name>locale</name> 

        <tagclass>cn.csai.web.jsp.LocaleTag</tagclass> 

        <bodycontent>JSP</bodycontent> 

    </tag> 

</taglib>

该标签实现后,具备根据所给出的key值从MessageBundle中获取与客户端Locale设置对应的消息的能力。在本应用中,须要向客户端输出“你好,王先生!”或“Hello, Mr. Wang!”,须要进行多语言支持的有四个元素,分别是:“你好”和“Hello”、“,”和“,”、“王先生”和“Mr. Wang”、“!”和“!”。因此,首先须要将这四个键值添加到MessageBundle属性文件中,如表9.3所示:

表9.3  资源文件内容配置

而后在test3.jsp中的使用以下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<mytag:locale>hello</mytag:locale>

<mytag:locale>dh</mytag:locale>

<mytag:locale>mrWang</mytag:locale>

<mytag:locale>th</mytag:locale> 

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

<title>Insert title here</title>

</head>

<body>

</body>

</html>

当把浏览器设为中文时,显示的页面如图9.20所示。

图9.20  中文Locale访问得到的test3.jsp页面

在显示中文时,可能会因为浏览器的编码识别问题产生乱码。若是出现乱码,只须要在菜单View(视图) → Encoding(编码)中选择合适的编码方式(GB2312或UTF-8)便可。

若是将浏览器设为英文,显示的页面如图9.21所示。

图9.21  英文Locale访问得到的test3.jsp页面

该例中为了使应用尽可能通用化,因此将一个消息分解成了四段进行获取。实际上最简单的能够只用一个键值对(键名为message),在英文的properties文件中值设为“Hello,Mr.Wang!”,而在中文中设为“你好,王先生!”。这样在test3.jsp文件中只须要用一次mytag:locale标签就能够实现消息的多语言显示了。

4.标签处理的本质

test3.jsp转化的Servlet以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test3_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  static {

    _jspx_dependants = new java.util.ArrayList(1);

    _jspx_dependants.add("/WEB-INF/MyTag.tld");

  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmytag_005flocale;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _005fjspx_005ftagPool_005fmytag_005flocale = org.apache.jasper.runtime.TagHandlerPool.getTagHandler Pool(getServletConfig());

    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()). getExpressionFactory();

    _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext(). getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

    _005fjspx_005ftagPool_005fmytag_005flocale.release();

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=ISO-8859-1");

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

      null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\n");

      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3. org/TR/html4/loose.dtd\">\n");

      out.write("<html>\n");

      out.write("<head>\r\n");

      out.write("\r\n");

      if (_jspx_meth_mytag_005flocale_005f0(_jspx_page_context))

        return;

      out.write('\r');

      out.write('\n');

      if (_jspx_meth_mytag_005flocale_005f1(_jspx_page_context))

        return;

      out.write('\r');

      out.write('\n');

      if (_jspx_meth_mytag_005flocale_005f2(_jspx_page_context))

        return;

      out.write('\r');

      out.write('\n');

      if (_jspx_meth_mytag_005flocale_005f3(_jspx_page_context))

        return;

      out.write("\r\n");

      out.write("\r\n");

      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

      out.write("<title>Insert title here</title>\n");

      out.write("</head>\n");

      out.write("<body>\n");

      out.write("\n");

      out.write("</body>\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

  private boolean _jspx_meth_mytag_005flocale_005f0(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LocaleTag _jspx_th_mytag_005flocale_005f0 = (cn.csai.web.jsp.LocaleTag) _005fjspx_ 005ftagPool_005fmytag_005flocale.get(cn.csai.web.jsp.LocaleTag.class);

    _jspx_th_mytag_005flocale_005f0.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flocale_005f0.setParent(null);

    int _jspx_eval_mytag_005flocale_005f0 = _jspx_th_mytag_005flocale_005f0.doStartTag();

    if (_jspx_eval_mytag_005flocale_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      if (_jspx_eval_mytag_005flocale_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_ INCLUDE) {

        out = _jspx_page_context.pushBody();

        _jspx_th_mytag_005flocale_005f0.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);

        _jspx_th_mytag_005flocale_005f0.doInitBody();

      }

      do {

        out.write("hello");

        int evalDoAfterBody = _jspx_th_mytag_005flocale_005f0.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

      if (_jspx_eval_mytag_005flocale_005f0 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.popBody();

      }

    }

    if (_jspx_th_mytag_005flocale_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f0);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f0);

    return false;

  }

  private boolean _jspx_meth_mytag_005flocale_005f1(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LocaleTag _jspx_th_mytag_005flocale_005f1 = (cn.csai.web.jsp.LocaleTag) _005fjspx_ 005ftagPool_005fmytag_005flocale.get(cn.csai.web.jsp.LocaleTag.class);

    _jspx_th_mytag_005flocale_005f1.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flocale_005f1.setParent(null);

    int _jspx_eval_mytag_005flocale_005f1 = _jspx_th_mytag_005flocale_005f1.doStartTag();

    if (_jspx_eval_mytag_005flocale_005f1 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      if (_jspx_eval_mytag_005flocale_005f1 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.pushBody();

        _jspx_th_mytag_005flocale_005f1.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);

        _jspx_th_mytag_005flocale_005f1.doInitBody();

      }

      do {

        out.write('d');

        out.write('h');

        int evalDoAfterBody = _jspx_th_mytag_005flocale_005f1.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

      if (_jspx_eval_mytag_005flocale_005f1 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.popBody();

      }

    }

    if (_jspx_th_mytag_005flocale_005f1.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f1);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f1);

    return false;

  }

  private boolean _jspx_meth_mytag_005flocale_005f2(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LocaleTag _jspx_th_mytag_005flocale_005f2 = (cn.csai.web.jsp.LocaleTag) _005fjspx_ 005ftagPool_005fmytag_005flocale.get(cn.csai.web.jsp.LocaleTag.class);

    _jspx_th_mytag_005flocale_005f2.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flocale_005f2.setParent(null);

    int _jspx_eval_mytag_005flocale_005f2 = _jspx_th_mytag_005flocale_005f2.doStartTag();

    if (_jspx_eval_mytag_005flocale_005f2 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      if (_jspx_eval_mytag_005flocale_005f2 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.pushBody();

        _jspx_th_mytag_005flocale_005f2.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);

        _jspx_th_mytag_005flocale_005f2.doInitBody();

      }

      do {

        out.write("mrWang");

        int evalDoAfterBody = _jspx_th_mytag_005flocale_005f2.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

      if (_jspx_eval_mytag_005flocale_005f2 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.popBody();

      }

    }

    if (_jspx_th_mytag_005flocale_005f2.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f2);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f2);

    return false;

  }

  private boolean _jspx_meth_mytag_005flocale_005f3(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LocaleTag _jspx_th_mytag_005flocale_005f3 = (cn.csai.web.jsp.LocaleTag) _005fjspx_ 005ftagPool_005fmytag_005flocale.get(cn.csai.web.jsp.LocaleTag.class);

    _jspx_th_mytag_005flocale_005f3.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flocale_005f3.setParent(null);

    int _jspx_eval_mytag_005flocale_005f3 = _jspx_th_mytag_005flocale_005f3.doStartTag();

    if (_jspx_eval_mytag_005flocale_005f3 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      if (_jspx_eval_mytag_005flocale_005f3 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.pushBody();

        _jspx_th_mytag_005flocale_005f3.setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);

        _jspx_th_mytag_005flocale_005f3.doInitBody();

      }

      do {

        out.write('t');

        out.write('h');

        int evalDoAfterBody = _jspx_th_mytag_005flocale_005f3.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

      if (_jspx_eval_mytag_005flocale_005f3 != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {

        out = _jspx_page_context.popBody();

      }

    }

    if (_jspx_th_mytag_005flocale_005f3.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f3);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flocale.reuse(_jspx_th_mytag_005flocale_005f3);

    return false;

  }

}

从代码中能够发现,因为test3.jsp使用了四个mytag:locale标签,因此在类中定义了四个标签处理方法,同时在_jspService()方法中也调用了这四个处理方法。每一个处理方法中的实现内容大体相同,只是在调用out.write()方法向标签体中写入的内容不同。

仔细分析第一个处理方法中的黑体部分能够发现:调用doStartTag()时,若是返回SKIP_BODY则不会对标签体作任何处理而是直接跳转到执行doEndTag();若是返回的不是EVAL_BODY_INCLUDE(而是EVAL_BODY_BUFFERED)才会调用BodyTag的setBodyContent()方法和doInitBody()方法,不然就直接跳转到do-while循环处理标签体和doAfterBody()方法,这正好应证了前面画的流程图。最后调用doEndTag()处理标签结束,因为LocaleTag是在doEndTag()方法中读取标签内容和写入标签新内容的,因此标签内容是在执行到这个时候才被更新的。

结合JSP文件内容、标签实现类、JSP所转换的Servlet以及前面介绍的标签处理流程图,能够很是清晰地了解JSP中对自定义标签的处理机制和流程。

 

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

本章小结

 

本章介绍了Java Web开发中又一项重要的基础技术,JSP技术。从JSP文件的内容格式和执行JSP文件时的表现来看,不少人会认为JSP文件的执行过程是首先执行JSP文件中的Java代码,将执行完后得到的HTML文件返回给客户端;但实际上JSP的执行过程并不是如此,而是JSP文件在被请求时将会被彻底转化为Servlet,并经过Servlet响应客户段的请求。

为了可以使JSP文件被正确解析,程序员编辑的JSP文件必须符合JSP的语法规范。JSP定义了许多语法结构,包括:程序代码、声明代码、输出代码、注释代码、指令代码、预约义代码等。并且,为了方便代码访问HTTP请求、Web应用及Web服务器的参数和设置,JSP提供了若干隐含对象;在JSP文件中,这些对象能够直接被用来获取各类参数和设置。

自定义标签是JSP对自身系统的一种扩展。JSP的自定义标签体系包含三个级别,Tag是有明确起始位置和结束位置的标签,IterationTag是在Tag的基础上标签内容能够迭代执行的标签,BodyTag是在IterationTag的基础上能够与标签内容进行交互的标签。

 

 

Java经过try/catch语句和Exception类体系提供了比较完善的异常处理机制。JSP文件最终被转换为Java文件,因此在JSP文件中固然也能够使用Java固有的异常处理机制。除此以外,JSP还提供了一种更宏观的异常处理机制,那就是errorPage。

在前面介绍page指令时,讲到page指令中有两个与errorPage相关的属性:errorPage和isErrorPage。其中errorPage指定了一个错误处理页面,假如当前页面中出现了JSP错误那么请求就会转向这个错误处理页面;isErrorPage指定当前页面是不是错误处理页面,假如当前页面是错误处理页面,那么当前页面就能够访问exception对象,而且经过分析exception对象对各类异常进行处理。这种异常处理机制将对异常的处理集中到一个页面,更加提升了代码的抽象度,更便于系统的维护。这种异常处理的逻辑如图9.10所示:

图9.10  JSP异常处理示意图

exception是标准的Exception类的对象,其方法和使用可参见Exception类的相关资料或Java文档。

假如某个JSP页面配置了errorPage属性,例如errorPage="errorHandler.jsp",那么转换成的Java文件中会有:

pageContext = _jspxFactory.getPageContext(this, request, response, "errorHandler.jsp", true, 8192, true);

其中的errorHandler.jsp是以参数的形式传递给了pageContext对象,而后将pageContext对象赋值给_jspx_page_context对象:

_jspx_page_context = pageContext;

最后在作异常处理时使用:

_jspx_page_context.handlePageException(t);

在该方法中,请求会被重定向到errorHandler.jsp,而且会将异常对象传递给该页面。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

PageContext对象

 

PageContext对象是PageContext类的实例。该对象是JSP页面各个对象和方法的门面对象,它提供了:

(1)得到JSP页面中各隐含对象的公有方法;

(2)方便地进行各类与当前页面相关的操做。

PageContext是个抽象类,其定义以下:

package javax.servlet.jsp;

import java.io.IOException;

import javax.servlet.*;

import javax.servlet.http.HttpSession;

import javax.servlet.jsp.tagext.BodyContent;

public abstract class PageContext extends JspContext

{

    public PageContext()

    {

    }

    public abstract void initialize(Servlet servlet, ServletRequest servletrequest, ServletResponse servletresponse, String s, boolean flag, int i, boolean flag1)

        throws IOException, IllegalStateException, IllegalArgumentException;

    public abstract void release();

    public abstract HttpSession getSession();

    public abstract Object getPage();

    public abstract ServletRequest getRequest();

    public abstract ServletResponse getResponse();

    public abstract Exception getException();

    public abstract ServletConfig getServletConfig();

    public abstract ServletContext getServletContext();

    public abstract void forward(String s)

        throws ServletException, IOException;

    public abstract void include(String s)

        throws ServletException, IOException;

    public abstract void include(String s, boolean flag)

        throws ServletException, IOException;

    public abstract void handlePageException(Exception exception)

        throws ServletException, IOException;

    public abstract void handlePageException(Throwable throwable)

        throws ServletException, IOException;

    public BodyContent pushBody()

    {

        return null;

    }

    public ErrorData getErrorData()

    {

        return new ErrorData((Throwable)getRequest().getAttribute("javax.servlet.error.exception"), ((Integer) getRequest().getAttribute("javax.servlet.error.status_code")).intValue(), (String)getRequest().getAttribute("javax.servlet.error.request_uri"), (String)getRequest().getAttribute("javax.servlet.error.servlet_name"));

    }

    public static final int PAGE_SCOPE = 1;

    public static final int REQUEST_SCOPE = 2;

    public static final int SESSION_SCOPE = 3;

    public static final int APPLICATION_SCOPE = 4;

    public static final String PAGE = "javax.servlet.jsp.jspPage";

    public static final String PAGECONTEXT = "javax.servlet.jsp.jspPageContext";

    public static final String REQUEST = "javax.servlet.jsp.jspRequest";

    public static final String RESPONSE = "javax.servlet.jsp.jspResponse";

    public static final String CONFIG = "javax.servlet.jsp.jspConfig";

    public static final String SESSION = "javax.servlet.jsp.jspSession";

    public static final String OUT = "javax.servlet.jsp.jspOut";

    public static final String APPLICATION = "javax.servlet.jsp.jspApplication";

    public static final String EXCEPTION = "javax.servlet.jsp.jspException";

}

其中:

initialize(Servlet servlet, ServletRequest servletrequest, ServletResponse servletresponse, String s, boolean flag, int i, boolean flag1):对该PageContext对象进行初始化。由于PageContext的构造函数是无参数构造函数,因此PageContext的对象并无被初始化,该方法将PageContext须要的参数传递给PageContext,并对PageContext对象进行初始化。该方法至关于JSP转化成的Servlet中的:

pageContext = _jspxFactory.getPageContext(this, request, response, "errorHandler.jsp", true, 8192, true);

而且其中参数的个数、顺序和含义均相同。

release():释放该PageContext对象。该方法对PageContext对象的内部状态进行重置,使该PageContext对象能够在initialize()后被从新使用。

getSession():得到session隐含对象。

getPage():得到page隐含对象。

getRequest():得到request隐含对象。

getResponse():得到response隐含对象。

getException():得到exception隐含对象。

getServletConfig():得到config隐含对象。

getServletContext():得到application隐含对象。

forward(String s):将请求前转到s指定的Web对象,其效果等同于<jsp:forward>标签。

include(String s):将s指定的Web对象包含处处理中,至关于该JSP页面向指定的Web对象发出请求,而且处理返回的响应。效果等同于<jsp:include>标签。

include(String s, boolean flag):除了起到上面方法的效果外,还提供了一个额外效果,即当flag为true时,该方法同时刷新输出缓冲区。

handlePageException(Exception exception):处理页面级的异常,即将参数提供的异常与请求一块儿转发给错误处理页面。

handlePageException(Throwable throwable):同上方法。

pushBody():返回一个新的BodyContent对象,保存当前out对象的内容。而且更新out属性的值。

getErrorData():返回一个ErrorData对象,在错误处理页面中该对象用于表示错误内容。在非错误处理页面中返回的对象是无心义的。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

对象属性的做用域

 

在JSP的这些隐含对象中,有四个对象都定义了相似的getAttribute()和setAttribute()方法。这四个对象均可以经过属性的名称设置和获取属性,但不一样的是这四个对象设置的属性具备不一样的做用域。这四个对象分别是:pageContext、request、session、application;分别表明四个做用域:页面、请求、会话、应用。

若是简单理解和记忆这几种做用域是很是低效的,并且很容易出错。其实,从本质上看,属性是针对对象进行设置的,调用某对象的setAttribute()方法是将某属性保存在该对象中,只有再次调用该对象的getAttribute()方法才可以获取此属性,调用其余对象的getAttribute()固然没法获取同一属性。因此,从本质上说,属性的做用域就是对象的做用域。在某个范围内若是使用的某个类的对象都是一个对象,那么该对象的做用域就是该范围,同时该范围也是对该对象设置的属性的做用域。图9.11示意了pageContext、request、session、application四种对象的做用域。

图9.11  四种对象做用域对比示意图

图9.11示意了四种对象的做用域。

application对象表示应用,在同一个应用中只有一个application对象,因此任何客户端的任何请求只要访问的是同一个application,那么访问的application对象就是一个,因此对application设置的属性也同样。在图9.11中,服务器中有两个应用:application1和application2。任何客户端访问application1中任何文件使用的application对象都是application1。

session对象表示会话,在同一个客户端访问同一个应用时(在Session超时时间内),只有一个session对象;而对于同一个应用来讲,不一样的客户端访问就会产生不一样的session对象。如图9.11中,客户端1访问application1的session对象是session1,而客户端2访问application1的session对象是session2。

request对象表示一次客户端请求,不管请求从哪里来以及访问哪里,一次请求就会产生一个request对象。如图9.11中,客户端2向1.jsp发出两次请求,那么每次请求就会产生一个request对象,即便1.jsp将request2传递给了2.jsp,仍是不会产生新request对象的。

pageContext对象表示一个页面对一次请求的处理。如图9.11中,客户端2经过request1访问1.jsp,1.jsp对request1进行处理就产生了pageContext1;客户端2经过request2访问1.jsp,1.jsp对request2进行处理就产生了pageContext2,进而1.jsp将request2传递给了2.jsp,2.jsp对request2进行处理就产生了pageContext3。

因此,经过9.11示意图能够发现,各个对象根据其表明意义的不一样,其做用域也不一样,因此对其进行设置的属性的做用域也不一样。总的来讲这四个对象的做用域从大到小依次为:application > session > request > pageContext。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发自定义标签

 

从前面对JSP的介绍能够发现,JSP是基于标签的语法,所谓标签就是用尖括号“<”和“>”括起的一个标识符;一般标签都是成对出现的,有起始标签和结束标签,例如<tag> ... </tag>,但若是标签中没有包含其余任何内容,也能够将起始标签和结束标签合并为一个标签,例如<tag />;起始标签中还能够添加属性。

最基本的,JSP继承了HTML的标签集,而且在其基础上扩充了本身的标签集,例如9.2.6节中介绍的JSP预约义标签。除此以外,JSP还提供一种程序员本身开发标签而且在JSP页面中使用标签的途径。JSP预约义标签是以jsp为前缀、具备特定功能的系统预约义标签,这些能够直接在JSP页面中使用。除此以外,程序员能够自行开发具备特定功能的标签,将这些标签引入到应用中,而后就能够在JSP页面中使用。

经过开发和使用自定义标签,开发人员能够将功能开发和页面开发的工做分开,功能开发人员关注开发功能组件,页面开发人员关注页面设计而且将开发的标签组件直接在页面中使用。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

自定义标签简介

 

自定义标签是JSP提供的一种自我扩展的方式,它容许开发人员本身定义标签以及标签的功能,而后在JSP页面中像预约义标签同样使用。

一个自定义标签在代码中就用一个javax.servlet.jsp.tagext.Tag表示,Tag中定义了标签的行为,当包含标签的JSP页面被转化为Servlet时,Servlet会在标签引用处调用Tag的相应方法进而实现标签的功能。

开发和使用一套自定义标签的步骤以下:

开发自定义标签类(或称自定义标签处理类);

定义一个标签订义文件(tld文件),在其中定义待使用的标签,而且将相应的标签处理类指定给标签;每一个标签具备一个惟一的名称;

将标签订义文件在web.xml中使用taglib进行声明;

在须要使用标签的JSP页面中使用<%@ taglib uri="..." prefix="..." %>进行声明,而后再用指定的prefix和名称使用标签。

由此能够知道,在定义一个标签以前首先须要肯定的是:标签的名称以及标签所须要进行的工做。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

JSP标签体系

 

在JSP标签体系中根据标签的表现形式和功能定义了多种不一样的标签层次和类别,每个层次和类别都定义了一些标签所要进行的操做。JSP的标签都必须实现JspTag接口,这个接口是一个概念级别的接口,并无定义任何实质的操做,所以基本上全部标签都实现自JspTag接口的子接口,Tag接口,这个接口定义了一些方法,它们表示标签在使用时与标签相关联的一些操做。同时JSP还实现了TagSupport类和BodyTagSupport,用于为Tag提供一些默认的实现,方便开发人员在此基础上进行开发。与标签相关的接口和类的层次结构如图9.12所示:

图9.12  与标签相关的接口和类的层次结构

从图9.12中能够发现,JSP标签的顶级接口是JspTag,Tag接口继承自JspTag,IterationTag接口继承自Tag,BodyTag继承自IterationTag;TagSupport和BodyTagSupport是两个实现类,TagSupport分别实现了IterationTag和Serializable,说明TagSupport的对象是一个JSP标签而且这个对象能够序列化;BodyTagSupport实现了BodyTag接口而且继承自TagSupport,说明BodyTagSupport确定是一个TagSupport,并且它提供的功能比TagSupport要多,能够说BodyTagSupport是一个特殊的TagSupport。

【注意】

JSP自定义标签相关的接口和类都被封装在jsp-api.jar,它与servlet-api.jar同样,位于Tomcat根目录下的lib目录中。

1.JspTag

javax.servlet.jsp.tagext.JspTag接口表示一个概念上的JSP标签,它的定义以下:

package javax.servlet.jsp.tagext;

public interface JspTag {

}

该接口并无定义任何方法,该接口的存在只是为了定义一个概念,它是全部其余JSP标签接口和类的根接口,它主要用于组织类结构和类型检查等目的。

2.Tag

javax.servlet.jsp.tagext.Tag接口表示一个具备明确起始位置和结束位置的JSP标签,该接口定义了一些方法,这些方法会在处理标签引用时被调用。它的定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

public interface Tag extends JspTag {

    public abstract void setPageContext(PageContext pagecontext);

    public abstract void setParent(Tag tag);

    public abstract Tag getParent();

    public abstract int doStartTag() throws JspException;

    public abstract int doEndTag() throws JspException;

    public abstract void release();

    public static final int SKIP_BODY = 0;

    public static final int EVAL_BODY_INCLUDE = 1;

    public static final int SKIP_PAGE = 5;

    public static final int EVAL_PAGE = 6;

}

其中:

setPageContext(PageContext pagecontext):将JSP页面的PageContent对象设置到对象中,该方法一般由JSP容器调用。在Tag对象被初始化成功后,在调用其余任何处理方法以前被调用。

setParent(Tag tag):设置该Tag的父标签,也是有JSP容器调用。

getParent():返回该Tag的父标签。

doStartTag():在处理JSP页面时,在标签起始处调用的代码;该方法会返回标志EVAL_BODY_INCLUDE或SKIP_BODY,用于决定是继续处理标签(EVAL_BODY_INCLUDE)仍是跳过该标签(SKIP_BODY)。

doEndTag():在处理JSP页面时,在标签结束处调用的代码;该代码会返回标志EVAL_PAGE或SKIP_PAGE,用于决定是继续处理页面剩下的内容(EVAL_PAGE)仍是跳过页面剩下部分的处理(SKIP_PAGE)。

release():用于释放状态,在标签处理完成后会被JSP容器调用。

SKIP_BODY:标志位,表示跳过标签体的处理。

EVAL_BODY_INCLUDE:标志位,表示继续标签体的处理。

SKIP_PAGE:标志位,表示跳过页面剩余部分的处理。

EVAL_PAGE:标志位,表示继续页面剩余部分的处理。

从该接口定义的方法能够发现,该接口定义了一个标签处理器的框架,实现者须要在doStartTag()和doEndTag()中添加本身的代码,用于实现标签的功能。经过实现该接口子类能够实现一个对标签进行处理的最基本的能力。

3.IterationTag

javax.servlet.jsp.tagext.IterationTag继承自Tag,而且在Tag的基础上又增长了一个方法的定义。IterationTag是一个Tag而且增长了对Tag的处理,不只提供了在标签开始和结束时方法,并且还提供了一个在处理方法体后执行的方法。定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

public interface IterationTag extends Tag {

    public abstract int doAfterBody()  throws JspException;

    public static final int EVAL_BODY_AGAIN = 2;

}

其中:

doAfterBody():在标签体运行完后执行的代码,该方法返回标志EVAL_BODY_AGAIN或SKIP_BODY,用于决定是再次运行标签体仍是再也不运行标签体而直接转向doEndTag()。

EVAL_BODY_AGAIN:标志位,表示再次运行标签体。

该接口提供了一种实现迭代标签的途径,在迭代标签中,标签体内的内容或代码能够以迭代的形式屡次出现或执行。经过实现该接口子类能够实现一个具备迭代功能的标签。

4.BodyTag

javax.servlet.jsp.tagext.BodyTag继承自IterationTag接口,它在IterationTag的基础上又多定义了两个方法,提供了对标签体中内容进行操纵的支持。定义以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

public interface BodyTag extends IterationTag

{

    public abstract void setBodyContent(BodyContent bodycontent);

    public abstract void doInitBody() throws JspException;

    public static final int EVAL_BODY_TAG = 2;

    public static final int EVAL_BODY_BUFFERED = 2;

}

其中:

setBodyContent(BodyContent bodycontent):设置一个BodyContent对象,该对象表示标签体中的内容;该方法由JSP容器进行调用,使得实现BodyTag的类能够操纵标签的内容;该方法先于doInitBody()被调用。

doInitBody():在处理标签时,该方法会在运行标签体以前被调用,用觉得运行标签体作准备。

EVAL_BODY_BUFFERED:标志位,是BodyTag对Tag中定义的SKIP_BODY和EVAL_BODY_INCLUDE的扩展。当BodyTag的doStartTag()执行时,能够返回该标志位用于申请一个新的缓冲区,提供执行标签体时放置BodyContent。

EVAL_BODY_TAG:与EVAL_BODY_BUFFERED具备相同的值,这是之前使用的名字,如今已过时。

该接口的父接口更近了一步,它定义的标签能够对标签体中的内容进行操纵。经过实现这个接口子类能够实现一个能够对标签体中内容进行操纵的标签。

5.TagSupport

javax.servlet.jsp.tagext.TagSupport是一个具体的类,它实现了IterationTag,也就是说它是一个可迭代的标签。但TagSupport只是一个默认的实现,其实现并无实质性的逻辑。TagSupport是为了方便程序员的开发而存在的,程序员在开发一个IterationTag时就能够直接从TagSupport继承而不须要从实现接口开始。该类的声明以下:

package javax.servlet.jsp.tagext;

import java.io.Serializable;

import java.util.Enumeration;

import java.util.Hashtable;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

public class TagSupport implements IterationTag, Serializable {

    private Tag parent;

    private Hashtable values;

    protected String id;

    protected PageContext pageContext;

    public static final Tag findAncestorWithClass(Tag from, Class class);

    public TagSupport();

    public int doStartTag() throws JspException;

    public int doEndTag() throws JspException;

    public int doAfterBody() throws JspException;

    public void release();

    public void setParent(Tag t);

    public Tag getParent();

    public void setId(String id);

    public String getId();

    public void setPageContext(PageContext pageContext);

    public void setValue(String k, Object o);

    public Object getValue(String k);

    public void removeValue(String k);

    public Enumeration getValues();

}

其中:

parent:用于保存该标签的父标签。

values:一个属性表,其中经过键值对能够保存该Tag的属性,键是一个字符串,值是一个对象。

id:该标签id属性的值,或者是null。

pageContext:所在页面的PageContext对象。

findAncestorWithClass(Tag from, Class class):静态方法,该方法经过Tag接口的getParent()方法,从标签from开始一直寻找上级标签,直到寻找到第一个具备class类型的标签返回。

TagSupport():无参数构造方法,该方法体没有提供任何操做。

doStartTag():实现Tag接口的方法;TagSupport中的该方法直接返回SKIP_BODY,即默认实现将忽略标签。

doEndTag():实现Tag接口的方法;TagSupport中的该方法直接返回EVAL_PAGE,即继续对页面剩下的部分进行执行。因此,结合doStartTag()的实现,默认实现中TagSupport对标签不作任何处理,其效果至关于标签不存在。

doAfterBody():实现IterationTag接口的方法;TagSupport中的该方法直接返回SKIP_BODY,即任什么时候候都不重复执行标签体;其目的同前两个方法同样,就是效果等同于标签不存在。

release():实现Tag接口的方法;TagSupport中的该方法对本身的全部状态进行释放,恢复到初始状态,这包括:将parent设置为空,将id设置为空,清空values中的全部属性。

setParent(Tag t):实现Tag接口的方法;为TagSupport设置父标签,该方法只是将t设置给域变量parent。

getParent():实现Tag接口的方法;得到TagSupport的父标签,该方法只是返回域变量parent。

setId(String id):设置TagSupport的id,将参数id设置给域变量id。

getId():得到TagSupport的id,返回域变量id的值。

setPageContext(PageContext pageContext):实现Tag接口的方法;为TagSupport设置PageContext,该方法将参数pageContext设置给域变量pageContext。

setValue(String k, Object o):将键为k值为o的属性添加到values中。

getValue(String k):得到键为k的值。

removeValue(String k):删除键为k的属性。

getValues():得到一个Enumeration对象,其中包含全部属性的键;若是没有属性则返回空。

可见,TagSupport具有了一个IterationTag的概念,但并无提供任何实质性的实现,若是单纯地将一个TagSupport实现的标签添加到JSP页面中,那么这个标签并不会有任何效果,其结果至关于没有添加任何标签。TagSupport的存在是为了方便开发人员,当开发人员须要开发一个IterationTag时,只须要继承TagSupport类而且重写其中的一些标签处理方法(doStartTag()、doEndTag()和doAfterBody());另外TagSupport还提供了一些方便的属性和操做,好比静态方法findAncestorWithClass(),开发人员能够在开发标签时直接调用该方法;TagSupport还为其子类提供了对id、属性和PageContext对象的管理。经过继承TagSupport开发新的标签能够使程序员只关注标签的业务逻辑。

6.BodyTagSupport

与TagSupport相似,javax.servlet.jsp.tagext.BodyTagSupport也是一个具体的类,它继承了TagSupport类,而且还另外实现了BodyTag接口,这使得BodyTagSupport具有了TagSupport的属性和处理能力,并且又增长了BodyTag的特性。该类的声明以下:

package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.JspWriter;

public class BodyTagSupport extends TagSupport implements BodyTag {

    protected BodyContent bodyContent;

    public BodyTagSupport();

    public int doStartTag() throws JspException;

    public int doEndTag() throws JspException;

    public void setBodyContent(BodyContent b);

    public void doInitBody()  throws JspException;

    public int doAfterBody() throws JspException;

    public void release();

    public BodyContent getBodyContent();

    public JspWriter getPreviousOut();

}

其中:

bodyContent:BodyContent的对象,经过setBodyContent()方法设置进来,能够在其余方法中使用,但不能在构造方法中使用,由于在构造方法中setBodyContent()方法还不可能被调用,因此bodyContent对象仍是空。

BodyTagSupport():构造方法,同TagSupport同样,该类的构造方法体也为空。

doStartTag():覆盖TagSupport中的同名方法,不是返回SKIP_BODY,而是返回EVAL_BODY_BUFFERED,由于BodyTagSupport是一个BodyTag,因此返回该标志用于申请一个缓冲区。

doEndTag():覆盖TagSupport中的同名方法,可是这里仍是返回了EVAL_PAGE,其用意与TagSupport也是同样的。

setBodyContent(BodyContent b):实现BodyTag的方法;将参数b设置给域变量bodyContent。

doInitBody():实现BodyTag的方法;方法体中并无提供任何操做,对于该方法而言没有任何操做就是一种恰当的默认实现。

doAfterBody():覆盖TagSupport中的同名方法,可是这里仍是返回了SKIP_BODY,默认实现。

release():覆盖TagSupport中的同名方法,在该方法中除了调用父类中的同名方法用于释放父类中的域,还要将bodyContent置为空。

getBodyContent():得到该标签的BodyContent对象,该方法中返回域变量bodyContent。

getPreviousOut():得到BodyContent所封装的JspWriter对象,经过该对象能够向BodyContent中写入内容。

BodyTagSupport是在TagSupport的基础上又添加了对BodyTag接口的默认实现。BodyTagSupport与TagSupport的区别主要是在处理标签时是否须要与标签体进行交互,若是不须要交互就用TagSupport,若是须要交互就要用BodyTagSupport。或者能够反过来讲,若是开发人员想开发一个须要标签体交互的标签就用BodyTagSupport,不然就用TagSupport。这里所谓的交互就是处理标签时是否要读取标签体的内容或改变标签体的内容。因为BodyTagSupport是TagSupport的子类,因此全部用TagSupport实现的标签也能够用BodyTagSupport来实现,只是将BodyTagSupport实现BodyTag接口的方法保持为默认实现便可。可是,仍是建议读者对于不须要交互的标签使用TagSupport实现,由于这样会避免多余的操做。

7.BodyContent

在实现了BodyTag的类中,包括BodyTagSupport。BodyContent是一个很是重要的结构,在须要和标签内容进行交互的标签中,BodyContent就是BodyTag子类与所处理标签内容进行交互的媒介;BodyTag子类经过BodyContent类的方法对标签的内容进行获取、写入、清空等操做。

BodyContent实质上是一个对JspWriter的封装,它对向JSP页面内容进行写入的Writer封装起来而且向外提供一个JspWriter的接口。在BodyContent的实现中,它保持了一个缓冲,在外部调用其写入方法时写入的内容会被放入BodyContent的缓冲中;同时BodyContent还提供了读取方法,读取方法会返回一个读取缓冲区内容的Reader;清空内容的方法会将缓冲中的内容清空。

一个标签的内容就是一个BodyContent对象,嵌套标签会产生BodyContent对象之间的相互包含;BodyContent的初始内容是标签中原本已有的内容,在对标签进行处理时能够读取、写入和清空这些内容,标签中的最终的内容就是标签处理完后BodyContent的内容。

这里须要注意的是,BodyContent的内容是JSP原始内容的执行结果而非原始内容。例如:

<tag>

<% out.print(“Hello”) %>

</tag>

在处理tag标签时,BodyContent的内容是“Hello”而不是“<% out.print(“Hello”) %>”。

BodyContent类的声明以下:

package javax.servlet.jsp.tagext;

import java.io.*;

import javax.servlet.jsp.JspWriter;

public abstract class BodyContent extends JspWriter {

    private JspWriter enclosingWriter;

    protected BodyContent(JspWriter e);

    public void flush() throws IOException;

    public void clearBody();

    public abstract Reader getReader();

    public abstract String getString();

    public abstract void writeOut(Writer writer) throws IOException;

    public JspWriter getEnclosingWriter();

}

其中:

enclosingWriter:JspWriter的对象,是BodyContent对象封装的JspWriter;

BodyContent(JspWriter e):构造函数,将e传递给封装的enclosingWriter;

flush():实现了JspWriter的flush()方法,在BodyContent中该方法被声明为无效方法,由于对BodyContent调用该方法没有意义,因此在使用BodyContent时不要调用flush()方法;

clearBody():清空BodyContent的内容;

getReader():得到读取BodyContent内容的Reader;

getString():将BodyContent的内容做为字符串返回;

writeOut(Writer writer):将BodyContent的内容写入到指定的writer;

getEnclosingWriter():返回被BodyContent封装的JspWriter,即返回enclosingWriter对象。

BodyContent类是一个抽象类,其中的getReader()、getString()、writeOut()都没有提供具体的实现,它们的实现由软件提供商实现,但方法的做用和意义是不会改变的。做为开发Web应用的开发人员,应该针对BodyContent提供的接口进行编程。

8.标签处理流程

在前面介绍了三种标签接口:Tag、IterationTag和BodyTag,它们分别定义了一些方法,这些方法分别在标签处理过程当中的不一样时间点被调用,并且某些方法是否被调用以及调用的次数还可能取决于前面方法返回的结果。这些方法包括:Tag接口的doStartTag()方法和doEndTag()方法、IterationTag的doAfterBody()、BodyTag的setBodyContent()和doInitBody();返回值包括:EVAL_BODY、SKIP_BODY、EVAL_BODY_AGAIN、EVAL_BODY_BUFFERED。

下面咱们分别针对不一样类型标签的处理过程,详细介绍各个方法被调用的状态转换图。

IterationTag继承Tag,BodyTag又继承IterationTag,因此某个类对这几个接口的实现可能存在以下几种状况:

Tag:实现类只实现了Tag标签。对于这种状况而言所实现的标签是一个不可迭代并且不须要与内容进行交互的标签。因此,它的处理方法只有doStartTag()和doEndTag()。执行的流程图如图9.13所示。

如图9.13所示,Tag子类的在被建立之后,首先会经过setPageContext()、setParent()、setId()等方法将Tag的初始信息传递给Tag;而后在处理到标签开始处时调用doStartTag()方法,根据方法的返回值肯定是否要继续执行标签体内的内容;在标签结束处调用doEndTag()方法,根据方法的返回值肯定是否继续处理页面的剩余部分。

IterationTag:实现类实现了IterationTag,那实现类也自动实现了Tag接口。这说明所实现的标签是一个可迭代但无需与标签体进行交互的标签。该类中的处理方法会增长一个doAfterTag()方法。处理的流程图如图9.14所示:

如图9.14所示,IterationTag的执行流程图是在Tag执行流程图的基础上添加了对doAfterTag()方法的调用和判断,添加了对标签体中内容的屡次执行逻辑。

图9.13  Tag子类执行流程图    

        图9.14  IterationTag子类执行流程图

BodyTag:实现类实现了BodyTag,也就自动实现了Tag和IterationTag。这说明实现的标签是一个可迭代并且须要与标签内容进行交互的标签。这种标签又在IterationTag的基础上增长了setBodyContent()方法和doInitBody()方法,并且在实现类的其余方法中还能够引用BodyContent对象对标签的内容进行操纵。处理的流程图如图9.15所示:

从图9.15能够发现,与IterationTag对Tag的扩展相似,BodyTag也在doStartTag()与doEngTag()之间对Tag进行了扩展。BodyContent是经过setBodyContent()方法被设置到类中的,因此在BodyContent对象只有在setBodyContent()方法调用后才能被使用,从流程图中能够很容易地发现,在doStartTag()被执行时BodyContent尚未被赋值,所以是不能使用的;而在doInitBody()、doAfterBody()和doEndTag()中就能够使用。

图9.15  BodyTag子类执行流程图

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发Tag级别的标签

 

从前面对JSP标签体系的介绍能够发现,在JSP中标签根据其功能分为三个级别:Tag、IterationTag和BodyTag。从本小节开始的如下三个小节将分别举例介绍这三个级别标签的开发。本小节首先介绍Tag级别的标签。

Tag标签是只实现了Tag接口而没有实现IterationTag和BodyTag接口的标签。Tag标签只定义了标签的起始和结束,并分别在标签的起始和结束处调用接口的方法对标签进行处理。下面针对一种特定的标签使用场景开发一个Tag标签。

1.标签使用场景

假设在开发的系统中要添加一种日志功能:要求为每一个JSP页面添加访问日志,在JSP页面被访问时在Tomcat的日志文件中记录一条日志,包括日志内容访问时间以及在访问该JSP页面的客户端主机名。

关于日志功能,咱们在第8.7.3节中已经介绍了如何用ServletContext对象记录日志。完成这个功能能够经过在JSP页面中添加一个代码块,在代码块中获取ServletContext对象进行日志记录;日志功能自己会记录当前时间,客户端主机名能够经过ServletRequest的getRemoteHost()方法得到。开发的test.jsp以下:

test.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<% 

String host = request.getRemoteHost();

config.getServletContext().log("test.jsp: " + host); 

%>

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

<title>Insert title here</title>

</head>

<body>

...

</body>

</html>

在该test.jsp中添加一块Java代码块,得到host后,将JSP文件名和host一同写入日志,日志内容以下:

Jun 23, 2008 4:05:10 PM org.apache.catalina.core.ApplicationContext log

INFO: test.jsp: 127.0.0.1

若是在全部JSP页面中都添加这么一段代码来实现日志功能,那将会很烦琐并且还很容易出错。因此,但愿开发一个通用标签,在JSP页面中只须要将这个标签添加到页面中就能够完成一样的日志功能,使用格式以下:

<mytag:loghost jspfile="test.jsp"/>

2.开发标签

前面已经将标签的功能描述得很是清楚,能够发现该标签不须要迭代处理也不须要与标签内容进行交互,因此只须要实现Tag接口便可。具体的实现以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.Tag;

public class LogTag implements Tag {

private PageContext context;

private String fileName;

public int doEndTag() throws JspException {

String host = context.getRequest().getRemoteHost();

context.getServletContext().log(fileName + ": " + host);

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

return EVAL_BODY_INCLUDE;

}

public Tag getParent() {

return null;

}

public void release() {

}

public void setPageContext(PageContext arg0) {

context = arg0;

}

public void setParent(Tag arg0) {

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

实现Tag接口必须实现它的全部方法,但这里只须要为doStartTag()和doEndTag()方法添加适当的内容便可。另外,因为该标签还有一个jspfile的属性,因此还必须为其提供一个setter方法。

doStartTag():该方法是在遇到标签起始位置时被调用的。在该标签的实现中咱们将实现日志功能的代码放到标签结束位置,因此该方法不须要提供任何实现代码,直接返回便可。因为该标签不支持标签中包含的任何内容,因此此处返回SKIP_BODY和EVAL_BODY_INCLUDE都不影响标签功能的实现。

doEndTag():该实现没有在doStartTag()中添加功能代码,而是选择在doEndTag()中实现标签的功能,因此该方法中就必须实现日志功能。具体的实现代码与在JSP中添加Java代码块实现的代码相似。最后,返回EVAL_PAGE让JSP继续对页面其余内容进行解析,保证添加该标签后不影响页面其余内容的正常处理。

setJspfile():因为该标签中包含了一个jspfile的属性用于指定记录日志的JSP文件的文件名,以便于在日志中包含JSP文件名。JSP的实现规定,标签的实现必须为标签的每一个属性定义一个setter方法,并且setter方法的方法名也应该符合命名规范:set + 属性名(首字母变大写)。例如jspfile属性的setter方法就是setJspfile()。这个方法名是大小写敏感的,因此在实现时要注意方法名中字母的大小写。即便将setJspfile()写成setJspFile()也会致使错误。可是,在实现类中所定义的对应属性的名称没有任何限制,例如本实现中的fileName也是能够的。

在该实现中将标签功能的实现放在了doEndTag()中。其实对于该标签来讲,因为它的标签体中不会包含任何内容,因此将标签功能的实现放在doStartTag()或doEndTag()中都是可行的。将标签功能放在doStartTag()方法中实现,而且让doStartTag()方法返回SKIP_BODY的实现以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.PageContext;

import javax.servlet.jsp.tagext.Tag;

public class LogTag implements Tag {

private PageContext context;

private String fileName;

public int doEndTag() throws JspException {

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

String host = context.getRequest().getRemoteHost();

context.getServletContext().log(fileName + ": " + host);

return SKIP_BODY;

}

public Tag getParent() {

return null;

}

public void release() {

}

public void setPageContext(PageContext arg0) {

context = arg0;

}

public void setParent(Tag arg0) {

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

这个实现所得到的效果与上一个实现相同。

这两个实现都是直接从实现Tag接口开始的,这会使得这其中的许多方法都显得不少余,好比:getParent()、release()、setParent(Tag)方法。为了不这个问题,也能够经过继承TagSupport类来实现标签,虽然这个标签实现了IterationTag接口,但正如前面提到的,TagSupport对IterationTag中定义方法的默认实现并不会对标签产生任何附加的影响。因此若是经过继承TagSupport类来实现一个不可迭代的标签,只须要不对TagSupport的doAfterBody()方法提供覆盖实现便可。这样,开发人员只须要实现本身关心的方法,而不用在实现类中列举多余的方法实现。经过继承TagSupport实现LogTag的代码以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class LogTag extends TagSupport {

private String fileName;

public int doEndTag() throws JspException {

String host = pageContext.getRequest().getRemoteHost();

pageContext.getServletContext().log(fileName + ": " + host);

return EVAL_PAGE;

}

public int doStartTag() throws JspException {

return EVAL_BODY_INCLUDE;

}

public void setJspfile(String jspFile) {

this.fileName = jspFile;

}

}

3.配置和使用标签

完成了标签类的开发并不能直接在JSP页面中使用标签,由于Tomcat服务器并不知道标签类的存在,也并不知道标签的格式,因此必需要对标签进行定义而且将定义标签的标签库的声明添加到web.xml中。

在开发完标签类后,首先要将标签添加到一个标签库中,也能够本身建立一个新的标签库。

标签库一般是一个tld文件,其内容是一个XML文档,例以下面是一个自定义的标签文件的内容:

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com /j2ee/dtds/web-jsptaglibrary_1_1.dtd"> 

<taglib> 

    <tlibversion>1.2</tlibversion> 

    <jspversion>1.1</jspversion> 

    <shortname>MyTag</shortname> 

    <uri>/mytag</uri> 

    <tag> 

        <name>log</name> 

        <tagclass>cn.csai.web.jsp.LogTag</tagclass> 

        <bodycontent>empty</bodycontent> 

        <attribute> 

            <name>jspfile</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

</taglib>

标签订义的XML文档的根元素是taglib,tlibversion是标签库格式的版本,jspversion是JSP标准的版本,shortname是为标签库定义的一个名称,uri是引用该标签库的URI。一个标签库能够定义多个标签,每一个标签使用一个tag元素,tag元素的name子元素是标签的名称;tagclass子元素是实现该标签的类的全路径;bodycontent是指定该标签内容的形式,能够是tagdependent、JSP和empty,empty就表示该标签不该该包含有内容;attribute为该标签订义了该标签可能会包含的属性,name是属性的名称,required说明该属性是不是必须的。这个示例就是LogTag的标签订义。在定义文件中定义的属性要与标签实现类中定义的属性setter方法相一致,这里定义了多少的属性就须要在实现类中定义相同数量的setter方法,并且setter方法的名字要符合命名规范。

将该标签订义文件命名为MyTag.tld,将其放在Web应用的WEB_INF目录中。而且还须要在web.xml中添加对标签订义文件的引用:

<web-app ...>

<jsp-config>

<taglib>

<taglib-uri>/mytag</taglib-uri>

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

</taglib>

</jsp-config>

...

</web-app>

接下来就能够在JSP页面中使用自定义标签了,格式以下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<mytag:log jspfile="test.jsp" />

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

<title>Insert title here</title>

</head>

<body>

...

</body>

</html>

在JSP页面中使用标签,首先要在JSP页面中经过taglib添加标签应用声明,taglib中的uri就对应web.xml中taglib-uri所指定的uri,prefix是开发人员本身定义的一个前缀,这里能够随意定义。

在使用标签时,标签名是taglib中声明的prefix加上标签在标签库中定义的名称,这里就是mytag:log。

对于Tomcat来讲,它是一次性就将一个标签库引入进JSP页面中,而使用标签时一次只会使用一个标签,因此当有多个标签时,能够将多个标签订义到同一个标签库定义文件中,这样就能够经过一次引用而使用标签库中的全部标签。

将Web应用部署到Tomcat中后,访问test.jsp文件,而后查看Tomcat的日志文件就能够发现:在localhost当前日志的最后多了一条日志记录:

Jun 23, 2008 5:03:39 PM org.apache.catalina.core.ApplicationContext log

INFO: test.jsp: 127.0.0.1

4.标签处理的本质

在前面已经讲过,全部的JSP页面最终都会被转换为一个Servlet进行工做,因此只要查看JSP所转化的Servlet就能够明白自定义标签处理的本质。以上面使用LogTag的test.jsp为例,它转化的Servlet以下:

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  static {

    _jspx_dependants = new java.util.ArrayList(1);

    _jspx_dependants.add("/WEB-INF/MyTag.tld");

  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmytag_005flog_ 005fjspfile_005fnobody;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody = org.apache.jasper. runtime.TagHandlerPool.getTagHandlerPool(getServletConfig());

    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()). getExpressionFactory();

    _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext(). getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.release();

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=ISO-8859-1");

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

      null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\n");

      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/ TR/html4/loose.dtd\">\n");

      out.write("<html>\n");

      out.write("<head>\r\n");

      if (_jspx_meth_mytag_005flog_005f0(_jspx_page_context))

        return;

      out.write("\n");

      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

      out.write("<title>Insert title here</title>\n");

      out.write("</head>\n");

      out.write("<body>\n");

      out.write("\n");

      out.write("</body>\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

  private boolean _jspx_meth_mytag_005flog_005f0(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.LogTag _jspx_th_mytag_005flog_005f0 = (cn.csai.web.jsp.LogTag) _005fjspx_ 005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.get(cn.csai.web.jsp.LogTag.class);

    _jspx_th_mytag_005flog_005f0.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005flog_005f0.setParent(null);

    _jspx_th_mytag_005flog_005f0.setJspfile("test.jsp");

    int _jspx_eval_mytag_005flog_005f0 = _jspx_th_mytag_005flog_005f0.doStartTag();

    if (_jspx_th_mytag_005flog_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.reuse(_jspx_th_mytag_005flog_005f0);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody.reuse(_jspx_th_mytag_005flog_005f0);

    return false;

  }

}

查看代码中的黑体部分。首先,Servlet中添加了一个TagHandlerPool类的对象,该对象专门用于处理自定义标签的转换。该对象在_jspInit()中被初始化,在_jspDestory()中被释放。

而对标签处理的核心代码都在_jspService()中,体如今以下这句:

if (_jspx_meth_mytag_005flog_005f0(_jspx_page_context))

        return;

该句调用方法_jspx_meth_mytag_005flog_005f0(PageContext),而且判断返回值,若是返回值为true则返回_jspService()方法,终止对页面剩余部分的处理,不然继续执行代码剩下的部分。这就天然会与doEndTag()方法的两个返回值(SKIP_PAGE和EVAL_PAGE)联系起来。

_jspx_meth_mytag_005flog_005f0(PageContext)方法的定义就在类声明中,下面咱们详细考察一下这个方法。这个方法接受一个PageContext对象做为参数,返回值是一个boolean变量。在方法中,首先以LogTag类为参数得到一个LogTag类的对象:

cn.csai.web.jsp.LogTag _jspx_th_mytag_005flog_005f0 = 

      (cn.csai.web.jsp.LogTag) _005fjspx_005ftagPool_005fmytag_005flog_005fjspfile_005fnobody

      .get(cn.csai.web.jsp.LogTag.class);

而后分别调用LogTag对象的setPageContext()方法和setParent()方法,将PageContext对象和当前标签的父标签赋给LogTag对象。实质上这两个方法是Tag接口的方法。接下来调用LogTag的属性setter方法setJspfile(),将属性参数设置进LogTag。这些都是执行标签处理代码前的准备工做。

准备工做作完了之后就调用LogTag对象的doStartTag()方法和doEndTag()方法,而且判断doEndTag()方法的返回值;若是返回值为SKIP_PAGE则返回true,不然返回false。这偏偏应证了前面的猜想。

因此,对于含有自定义标签的JSP页面来讲,它的处理就是在JSP页面转化而成的Servlet中添加了对标签处理类相关方法调用,而且将调用的返回值用于影响_jspService()方法的处理流程。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发IterationTag级别的标签

 

Tag标签是标签内容不可迭代执行的标签或者不包含标签内容的标签,多数用于实现功能比较单一的标签。而从IterationTag实现的标签会比Tag标签稍微复杂一些,它的标签内容能够迭代执行。下面咱们用一个IterationTag的实例介绍IterationTag的开发。

1.标签使用场景

考虑以下的使用场景:在页面中常常须要让一段内容重复出现屡次,例如重复打印一段文本。直接使用JSP代码块也能够实现这个功能,例以下面的test2.jsp在页面上连续输出三行Hello World:

test2.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<%for(int i=0;i<3;i++) { %>

Hello world!<br>

<%} %>

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

<title>Insert title here</title>

</head>

<body>

</body>

</html>

页面显示如图9.16所示。

图9.16  test2.jsp页面效果

这种实现显然是能够达到目的的,可是因为须要在JSP页面中添加Java代码,就会使页面显得比较凌乱;假如须要重复的内容再复杂一些,就更容易出现错误。下面咱们就介绍如何使用一个自定义标签来实现这个功能,使用方式以下:

<mytag:repeat times="3">

Hello world!<br>

</mytag:repeat>

其中,times表示要重复的次数,标签之间的内容就是要重复的内容。

2.开发标签

显而易见,该标签是须要进行迭代的标签,因此不能使用Tag接口,而使用IterationTag。TagSupport实现了IterationTag,因此这里就能够直接继承TagSupport来实现。实现的标签以下:

package cn.csai.web.jsp;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.TagSupport;

public class RepeatTag extends TagSupport {

private int counter = 0;

private int repeatTimes;

@Override

public int doAfterBody() throws JspException {

counter++;

if (skip())

return SKIP_BODY;

return EVAL_BODY_AGAIN;

}

@Override

public int doEndTag() throws JspException {

return EVAL_PAGE;

}

@Override

public int doStartTag() throws JspException {

if (skip())

return SKIP_BODY;

return EVAL_BODY_INCLUDE;

}

public void setTimes(String rep) {

try {

repeatTimes = Integer.parseInt(rep);

} catch (NumberFormatException e) {

repeatTimes = 0;

}

}

private boolean skip() {

if (counter >= repeatTimes) {

return true;

}

return false;

}

}

给该标签类取名为RepeatTag。该标签有一个参数times,用于说明内容须要重复的次数,因此在标签类中首先必须声明一个setter方法,setTimes()用于将times属性的值传递给域变量repeatTimes。在类中定义另外一个域变量counter用于记录重复执行内容的次数,当counter大于等于repeatTimes时就退出重复执行内容的循环,因此实现了一个skip()方法用于判断是否该退出循环。

在doStartTag()方法中,首先判断是否该退出循环,这是由于若是指定的times小于等于0那就是不执行内容。因此若是在doStartTag()调用skip()时输出为true则返回SKIP_BODY,表示不执行内容;不然返回EVAL_BODY_INCLUDE便表示执行内容。

在doAfterBody()方法中,首先将counter自增1,由于执行到doAfterBody()方法时标签体已经执行了一遍了,而后再判断是否应该退出,若是不该该退出则返回EVAL_BODY_AGAIN再继续执行标签体,直到执行的次数达到为止,此时返回SKIP_BODY退出循环。

在doEndTag()方法中,不须要作其余操做,只要返回EVAL_PAGE保证页面剩余部分被正确解析。

3.配置和使用标签

不管是什么标签,配置都是相似的,都须要放在标签订义文件中。在第9.4.4节中已经定义了MyTag.tld,并且在其中定义了LogTag;如今开发的新的标签只须要在MyTag.tld中再添加RepeatTag的定义就能够了,以下所示:

<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee /dtds/web-jsptaglibrary_1_1.dtd"> 

<taglib> 

    <tlibversion>1.2</tlibversion> 

    <jspversion>1.1</jspversion> 

    <shortname>MyTag</shortname> 

    <uri>/mytag</uri> 

    <tag> 

        <name>log</name> 

        <tagclass>cn.csai.web.jsp.LogTag</tagclass> 

        <bodycontent>empty</bodycontent> 

        <attribute> 

            <name>jspfile</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

    <tag> 

        <name>repeat</name> 

        <tagclass>cn.csai.web.jsp.RepeatTag</tagclass> 

        <bodycontent>JSP</bodycontent> 

        <attribute> 

            <name>times</name> 

            <required>true</required> 

        </attribute> 

    </tag> 

</taglib>

只须要在taglib元素下再添加一个tag元素就能够了,tag元素中的声明含义不变,只是因为该标签是包含标签体内容的,因此bodycontent不能设为empty,而应该设为JSP。

在test2.jsp中使用RepeatTag的格式以下:

test2.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="/mytag" prefix="mytag"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4 /loose.dtd">

<html>

<head>

<mytag:repeat times="3">

Hello world!<br>

</mytag:repeat>

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

<title>Insert title here</title>

</head>

<body>

</body>

</html>

如其中黑体部分所示,其含义就是将“Hello world!<br>”输出3次,页面如图9.17所示:

图9.17  使用RepeatTag标签的test2.jsp页面效果

4.标签处理的本质

为了探究IterationTag标签处理的本质,咱们对test2.jsp所转化的Servlet进行了研究,Servlet以下所示。

package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class test2_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();

  private static java.util.List _jspx_dependants;

  static {

    _jspx_dependants = new java.util.ArrayList(1);

    _jspx_dependants.add("/WEB-INF/MyTag.tld");

  }

  private org.apache.jasper.runtime.TagHandlerPool _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes;

  private javax.el.ExpressionFactory _el_expressionfactory;

  private org.apache.AnnotationProcessor _jsp_annotationprocessor;

  public Object getDependants() {

    return _jspx_dependants;

  }

  public void _jspInit() {

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes = org.apache.jasper.runtime.TagHandlerPool. getTagHandlerPool(getServletConfig());

    _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()). getExpressionFactory();

    _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext(). getAttribute(org.apache.AnnotationProcessor.class.getName());

  }

  public void _jspDestroy() {

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.release();

  }

  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;

    try {

      response.setContentType("text/html; charset=ISO-8859-1");

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

      null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;

      out.write("\r\n");

      out.write("\n");

      out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org /TR/html4/loose.dtd\">\n");

      out.write("<html>\n");

      out.write("<head>\r\n");

      if (_jspx_meth_mytag_005frepeat_005f0(_jspx_page_context))

        return;

      out.write("\n");

      out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n");

      out.write("<title>Insert title here</title>\n");

      out.write("</head>\n");

      out.write("<body>\n");

      out.write("\n");

      out.write("</body>\n");

      out.write("</html>");

    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          try { out.clearBuffer(); } catch (java.io.IOException e) {}

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

  private boolean _jspx_meth_mytag_005frepeat_005f0(PageContext _jspx_page_context)

          throws Throwable {

    PageContext pageContext = _jspx_page_context;

    JspWriter out = _jspx_page_context.getOut();

    cn.csai.web.jsp.RepeatTag _jspx_th_mytag_005frepeat_005f0 = (cn.csai.web.jsp.RepeatTag) _005fjspx_ 005ftagPool_005fmytag_005frepeat_005ftimes.get(cn.csai.web.jsp.RepeatTag.class);

    _jspx_th_mytag_005frepeat_005f0.setPageContext(_jspx_page_context);

    _jspx_th_mytag_005frepeat_005f0.setParent(null);

    _jspx_th_mytag_005frepeat_005f0.setTimes("3");

    int _jspx_eval_mytag_005frepeat_005f0 = _jspx_th_mytag_005frepeat_005f0.doStartTag();

    if (_jspx_eval_mytag_005frepeat_005f0 != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {

      do {

        out.write("\r\n");

        out.write("Hello world!<br>\r\n");

        int evalDoAfterBody = _jspx_th_mytag_005frepeat_005f0.doAfterBody();

        if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)

          break;

      } while (true);

    }

    if (_jspx_th_mytag_005frepeat_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {

      _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.reuse(_jspx_th_mytag_005frepeat_005f0);

      return true;

    }

    _005fjspx_005ftagPool_005fmytag_005frepeat_005ftimes.reuse(_jspx_th_mytag_005frepeat_005f0);

    return false;

  }

}

从代码中能够发现,整体的结构与test.jsp的Servlet相似。只是标签处理函数有了一些变化,添加了一些处理代码。观察方法中的黑体部分,首先仍是调用doStartTag()方法,若是doStartTag()返回值不等于SKIP_BODY则继续执行标签体。但这里处理标签体倒是使用了一个do-while循环;首先输出标签体中的内容而后调用doAfterBody()方法,若是返回值不是EVAL_BODY_AGAIN则跳出循环,不然继续循环输出标签体。

 
 
 
 

第 9 章:JSP基础做者:党海峰孙霞    来源:希赛网    2014年03月17日

 

开发BodyTag级别的标签

 

BodyTag是在IterationTag的基础上又添加了与标签体中内容的交互。

1.标签使用场景

考虑下面的一种使用场景:但愿在页面中显示一段文字,并且这段文字能够根据客户端浏览器的语言设置而动态变化,好比若是客户端浏览器使用的是英文语言设置则返回“Hello,××!”,若是客户端浏览器使用的是中文语言设置则返回“你好,××!”。

浏览器所使用的语言能够在浏览器的选项中进行设置,在IE的菜单Tools(工具)→Internet Options(Internet选项)...选项打开的窗口中的General(常规)页的下部有一个 Languages(语言)...按钮,如图9.18所示。

图9.18  Internet Options窗口

点击Languages...按钮打开Language Preference窗口,如图9.19所示。

图9.19  Language Preference窗口

能够经过Add...添加新的语言,经过Move Up和Move Down移动语言的上下顺序,在最上面的语言是最优选的语言。

另外在服务器端,在Servlet中,能够经过ServletRequest的getLocale()方法得到客户端所使用的语言,如图9.19中显示的两个语言设置分别是中文和英文,所对应的Locale就是Locale.Chinese和Locale.US,两个Locale的缩写代码分别是zh_CN和en_US。程序员能够经过为浏览器设置不一样的Locale,而后测试开发的功能是否正确。

假设咱们要开发一个JSP页面,页面根据客户端设置的不一样Locale分别显示“你好,王先生!”和“Hello, Mr. Wang!”。

根据不一样的Locale显示不一样语言的字符串,这在Java中已经提供了支持。首先,为每一个Locale建立一个资源properties文件,文件名包含Locale的缩写代码,例如MessageBundle_zh_CN.properties和MessageBundle_en_US.properties;这两个文件的文件名都是MessageBundle,但它们分别定义了针对中文和英文的两套资源文件。而后,在每一个资源文件中为每个须要进行多语言支持的字符串定义一个键值对,每一个资源文件中的键名同样,但值分别用资源文件对应的语言进行表达,例如:

表9.2  不一样资源文件中键值对的描述

同时,还能够定义一个默认的properties,即当客户端设置的既不是中文也不是英文时,就用默认的语言值,一般默认的与英文的同样,只是文件名不包含任何Locale的缩写,而直接是MessageBundle.properties。

定义完资源文件,而后实现一个类用于从资源文件中获取值,以下面的Message类:

package cn.csai.web.jsp;

import java.util.Locale;

import java.util.MissingResourceException;

import java.util.ResourceBundle;

public class Messages {

private static final String BUNDLE_NAME = "cn.csai.web.jsp.resource.MessageBundle"; 

private Messages() {

}

public static String getString(String key, Locale locale) {

try {

ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);

return bundle.getString(key);

} catch (MissingResourceException e) {

return "";

}

}

}

该类定义了一个静态方法getString(),该方法接受一个String和一个Locale对象做为参数,表示取Locale属性文件中键为key的值。例如getString(“hello”, Locale.Chinese)就是“你好”,而getString(“mrWang”, Locale.US)就是Mr. Wang。

经过这一套体系就能够完成根据不一样Locale获取不一样语言的文字的功能。下面就关注于如何在JSP页面中添加这个功能。

假如不使用自定义标签,那么只能在页面中添加Java代码以实现这个功能:

test3.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ page import="cn.csai.web.jsp.Messages, java.util.Locale" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose. dtd">

<html>

<head>

<% 

Locale locale = request.getLocale();

String s = Messages.getString("hello", locale) + ", " + Messages.getString("mrWang", locale) + "!";

%>

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

<title>Insert title here</title>

</head>

<body>

<%= s %>

</body>

</html>

下面咱们将介绍如何使用自定义标签的方式实现这个功能,以使这个功能可以被通用。

2.开发标签

根据该功能要求,须要开发一个能够根据客户端浏览器Locale设置输出多种语言的标签。设计标签的格式以下:

<mytag:locale>hello</mytag:locale>

标签的内容包含的是所须要输出消息的键。

因为该标签须要输出标签内容,因此首先能够确定该标签必须是一个BodyTag标签,为了简单起见,继承BodyTagSupport来实现LocaleTag。标签的实现以下:

package cn.csai.web.jsp;

import java.io.IOException;

import java.util.Locale;

import javax.servlet.jsp.JspException;

import javax.servlet.jsp.tagext.BodyTagSupport;

public class LocaleTag extends BodyTagSupport {

@Override

public int doStartTag() throws JspException {

return EVAL_BODY_BUFFERED;

}

@Override

public int doEndTag() throws JspException {

String key = bodyContent.getString().trim();

Locale locale = pageContext.getRequest().getLocale();

String message = Messages.getString(key, locale);

try {

bodyContent.getEnclosingWriter().print(message);

} catch (IOException e) {

}