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

}

return SKIP_BODY;

}

}

標籤不含任何屬性,因此也不須要實現任何setter方法;在doStartTag()�

相關文章
相關標籤/搜索