- Java Web程序設計
- 張磊 丁香乾編著
- 250字
- 2018-12-29 18:50:43
理論篇
第1章 Servlet基礎
本章目標
■ 了解動態網站開發的相關技術
■ 理解Servlet的運行原理及生命周期
■ 掌握Servlet的編寫及部署
■ 掌握Servlet對表單數據的處理
■ 掌握Servlet對HTTP請求報頭的處理
學習導航

任務描述
【描述1.D.1】
使用Servlet輸出“Hello World”頁面。
【描述1.D.2】
使用Servlet處理表單數據,當用戶提交的數據正確時(用戶名haier,密碼soft),輸出“登錄成功!”,否則提示“登錄失敗!”。
【描述1.D.3】
使用request對象讀取報頭信息,并打印在頁面中。
【描述1.D.4】
通過設置響應報頭,實現動態時鐘。
【描述1.D.5】
使用請求重定向和轉發兩種方式,使用戶自動訪問重定向后的頁面,區分轉發和重定向的區別。
1.1 動態網站技術概述
在《Web編程基礎》課程中已經學習使用HTML、CSS、JavaScript等相關技術來建設靜態網站,其中,靜態網頁文件的擴展名為“.htm”或“.html”,這些頁面不能與服務器進行數據交互。隨著站點內容和功能需求的不斷復雜化,單一的靜態網站技術往往不能滿足應用的要求。例如,靜態網站無法完成將數據傳輸到服務器上進行處理并存儲到數據庫中,此時就需要動態網站技術。
1.1.1 動態網站技術
動態網站并不是指具有動畫功能的網站,而是指基于數據庫架構的網站,一般由大量的動態網頁(如JSP)、后臺處理程序(如Servlet)和用于存儲內容的數據庫組成。所謂“動態網頁”,本質上與網頁上的各種動畫、滾動字幕等視覺上的“動態效果”無關。動態網頁可以是純文字內容,也可以包含各種動畫內容,這些只是網頁具體內容的表現形式。無論網頁是否具有動態效果,采用動態網站技術生成的網頁都稱為動態網頁。
動態網站一般具有以下幾個特點。
■ 交互性:網頁會根據用戶的要求和選擇而動態改變和響應。例如,用戶在網頁中填寫表單信息并提交,服務器可以對數據進行處理并保存到數據庫中,然后跳轉到相應頁面。因此,動態網站可以實現用戶注冊、信息發布、訂單管理等功能。
■ 自動更新:無須手動更新HTML文檔,便會自動生成新的頁面,大大減少了工作量。例如,在論壇中發布信息時,后臺服務器可以產生新的網頁。
■ 隨機性:在不同的時間、不同的用戶訪問同一網頁時可能產生不同的頁面。
注意 動態網站一般采用動靜結合的原則:網站中內容頻繁更新的,可采用動態網頁技術;網站中內容不需要更新的,則可采用靜態網頁進行顯示。通常一個網站既包含動態網頁也包含靜態網頁。
動態網站技術有早期的CGI技術,全名Common Gateway Interface(公用網關接口)。CGI提供了一種機制,可以實現客戶和服務器之間真正的雙向交互。這種技術為諸如在線用戶支持和電子商務等新思想的實現鋪平了道路,同時CGI技術因可以使用不同的語言編寫適合的CGI程序,如Visual Basic、Delphi和C/C++等,并且功能強大,被早期的很多網站采用。但由于編程困難、效率低下、修改復雜,所以逐漸被新技術所取代。目前被廣泛應用的動態網站技術主要有以下三種。
■ PHP(Hypertext Preprocessor):是超文本預處理器,其語法大量借鑒了C、Java、Perl等語言,只需要很少的編程知識就能使用PHP建立一個真正交互的Web站點。由于PHP開放源代碼,并且是免費的,所以非常流行,是當今Internet上最為火熱的腳本語言之一。
■ ASP(Active Server Pages):是一種類似HTML、Script與CGI結合體的技術,它沒有提供自己專門的編程語言,允許用戶使用許多已有的腳本語言編寫ASP應用程序。但ASP技術局限于微軟的操作系統平臺之上,主要工作環境為微軟的IIS應用程序結構,而且ASP技術不能很容易地實現在跨平臺Web服務器上工作,因此一般只適合一些中小型站點。但目前由ASP升級演變而來的ASP.NET支持大型網站的開發。
■ JSP(Java Server Pages):是基于Java Servlet以及整個Java體系的Web開發技術。JSP是由SUN公司于1999年6月推出的新技術,它與ASP有一定的相似之處,但JSP能在大部分的服務器上運行,而且其應用程序易于維護和管理,安全性能方面也被認為是這三種基本動態網站技術中最好的。
1.1.2 B/S架構
在動態網站技術中,一般使用瀏覽器作為客戶端,當客戶在瀏覽器中發出請求時,Web服務器得到請求后查找資源,然后向客戶返回一個結果,這就是B/S(Browser/Server)架構,如圖1-1所示。

圖1-1 B/S模型
在B/S架構中,用戶的請求與Web服務器響應需要通過Internet網絡從一臺計算機發送到另一臺計算機,不同計算機之間是使用HTTP(HyperText Transfer Protocol)協議進行通信的。HTTP是超文本傳輸協議,包含命令和傳輸信息,不僅用于Web訪問,也可以用于其他互聯網/內聯網應用系統之間的通信,從而實現各種資源信息的超媒體訪問集成。
1.2 Servlet簡介
Servlet是JavaEE架構中的關鍵組成部分。JavaEE是基于分布式和多層結構的企業級應用開發規范和標準。目前,在企業應用開發中不僅會使用傳統的JavaEE組件(例如JDBC、Servlet、EJB等),還會使用一些輕量級的框架結構(例如Struts、Hibernate和Spring),以提高企業開發效率。在Java企業級開發應用中會使用到的技術如圖1-2所示。Servlet技術是Sun公司提供的一種實現動態網頁的解決方案,它是基于Java編程語言的Web服務器端編程技術,主要用于在Web服務器端獲得客戶端的訪問請求信息并動態生成對客戶端的響應信息。此外,Servlet技術也是JSP技術的基礎。

圖1-2 JavaEE技術組成
Servlet是Web服務器端的Java應用程序,它支持用戶交互式地瀏覽和修改數據,生成動態的Web頁面。比如,當瀏覽器發送一個請求到服務器后,服務器會把請求送往一個特定的Servlet,這樣Servlet就能處理請求并構造一個合適的響應(通常以HTML網頁形式)返回給客戶,如圖1-3所示。

圖1-3 Servlet的作用
Servlet與普通Java程序相比,只是輸入信息的來源和輸出結果的目標不一樣,例如,對于Java程序而言,用戶一般通過GUI窗口輸入信息,并在GUI窗口上獲得輸出結果,而對于Servlet程序而言,用戶一般通過瀏覽器輸入并獲取響應結果。通常普通Java程序所能完成的大多數任務,Servlet程序都可以完成。Servlet程序具有以下特點:
■ 高效
在傳統CGI中,如果有N個并發的對同一CGI程序的請求,則該CGI程序的代碼在內存中重復裝載了N次;而對于Servlet,處理請求的是N個線程,只需要一份Servlet類代碼。在性能優化方面,Servlet也比CGI有著更多的選擇,比如緩沖以前的計算結果、保持數據庫連接的活動等。
■ 方便
Servlet提供了大量的實用工具例程,例如自動地解析和解碼HTML表單數據、讀取和設置HTTP頭、處理Cookie、跟蹤會話狀態等。
■ 功能強大
在Servlet中,許多使用傳統CGI程序很難完成的任務都可以輕松地完成。例如,Servlet能夠直接和Web服務器交互,而普通的CGI程序不能。Servlet還能夠在各個程序之間共享數據,很容易地實現數據庫連接池之類的功能。
■ 良好的可移植性
Servlet是用Java語言編寫的,所以具備Java的可移植性特點。此外,Servlet API具有完善的標準,支持Servlet規范的容器都可以運行Servlet程序,例如Tomcat、Resin等。
1.3 第一個Servlet
編寫Servlet需要遵循其規范:
■ 創建Servlet時,需要繼承HttpServlet類,同時需要導入Servlet API的兩個包:javax.servlet和javax.servlet.http。javax.servlet包提供了控制Servlet生命周期所必需的Servlet接口,是編寫Servlet時必須要實現的;javax.servlet.http包提供了從Servlet接口派生出的專門用于處理HTTP請求的抽象類和一般的工具類。
■ 根據數據的發送方式,覆蓋doGet()、doPost()方法之一或全部。doGet()和doPost()方法都有兩個參數,分別為HttpServletRequest和HttpServletResponse類型。這兩個參數分別用于表示客戶端的請求和服務器端的響應。通過HttpServletRequest,可以從客戶端獲得發送過來的信息;通過HttpServletResponse,可以讓服務器端對客戶端做出響應,最常用的就是向客戶端發送信息。關于這兩個參數,將在后續內容中詳細講解。
注意 如果在瀏覽器中直接輸入地址來訪問Servlet資源,屬于使用GET方式訪問。
下述代碼用于實現任務描述1.D.1,使用Servlet輸出“Hello World”頁面。
【描述1.D.1】HelloServlet.java
// 創建一個Servlet類,繼承HttpServlet public class HelloServlet extends HttpServlet { // 重寫doGet() public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 設置響應到客戶端的文本類型為HTML response.setContentType("text/html"); // 獲取輸出流 PrintWriter out = response.getWriter(); out.println(" Hello World"); } }
上述代碼會向客戶端瀏覽器中打印“Hello World”信息。通過response對象的getWriter()方法可以獲取向客戶端輸出信息的輸出流:
PrintWriter out = response.getWriter();
調用輸出流的println()方法可以在客戶端瀏覽器中打印消息。例如:
out.println(" Hello World");
下面在Web應用的部署文件web.xml中注冊此Servlet信息。
【描述1.D.1】在web.xml中配置Servlet
<servlet> <display-name>Hello</display-name> <!-- Servlet的引用名 --> <servlet-name>Hello</servlet-name> <!-- 所配置的Servlet類的完整類路徑 --> <servlet-class>com.haiersoft.ch01.HelloServlet</servlet-class> </servlet> <servlet-mapping> <!-- 前面配置的Servlet引用名--> <servlet-name>Hello</servlet-name> <!-- 訪問當前Servlet的URL --> <url-pattern>/hello</url-pattern> </servlet-mapping>
在上述配置信息中,需要注意以下幾個方面:
■ Servlet別名,即<servlet-name>和</servlet-name>之間的命名可以隨意命名,但要遵循命名規范。
■ <servlet>和<servlet-mapping>元素可以配對出現,通過Servlet別名進行匹配。<servlet>元素也可以單獨出現,通常用于初始化操作。
■ URL引用,即<url-pattern>和</url-pattern>之間的命名通常以“/”開頭。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/hello,運行結果如圖1-4所示。

圖1-4 運行結果
注意 關于Web應用的開發過程及配置,可參考實踐1.G.2指導部分。
1.4 Servlet的生命周期
Servlet是運行在服務器上的,其生命周期由Servlet容器負責。Servlet生命周期是指Servlet實例從創建到響應客戶請求直至銷毀的過程。Servlet API中定義了關于Servlet生命周期的3個方法。
■ init():用于Servlet初始化。當容器創建Servlet實例后,會自動調用此方法。
■ service():用于服務處理。當客戶端發出請求,容器會自動調用此方法進行處理,并將處理結果響應到客戶端。service()方法有兩個參數,分別接收ServletRequest接口和ServletResponse接口的對象來處理請求和響應。
■ destroy():用于銷毀Servlet。當容器銷毀Servlet實例時自動調用此方法,釋放Servlet實例,清除當前Servlet所持有的資源。
Servlet生命周期概括為以下幾個階段。
01 裝載Servlet:這項操作一般是動態執行的,有些服務器提供了相應的管理功能,可以在啟動的時候就裝載Servlet。
02 創建一個Servlet實例:容器創建Servlet的一個實例對象。
03 初始化:容器調用init()方法對Servlet實例進行初始化。
04 服務:當容器接收到對此Servlet的請求時,將調用service()方法響應客戶的請求。
05 銷毀:容器調用destroy()方法銷毀Servlet實例。
在Servlet生命周期的這幾個階段中,初始化init()方法僅執行一次,是在服務器裝載Servlet時執行的,以后無論有多少客戶訪問此Servlet,都不會重復執行init()。即此Servlet在Servlet容器中只有單一實例;當多個用戶訪問此Servlet時,會分為多個線程訪問此Servlet實例對象的service()方法。在service()方法內,容器會對客戶端的請求方式進行判斷,如果是Get方式提交,則調用doGet()進行處理;如果是Post方式提交,則調用doPost()進行處理。圖1-5說明了Servlet生命周期的不同階段。
下面代碼演示了Servlet的生命周期。

圖1-5 Servlet的生命周期
【代碼1-1】ServletLife.java
public class ServletLife extends HttpServlet { /** * 構造方法 */ public ServletLife() { super(); } /** * 初始化方法 */ public void init(ServletConfig config) throws ServletException { System.out.println("初始化時,init()方法被調用!"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("處理請求時,doGet()方法被調用。"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("處理請求時,doPost()方法被調用。"); } /** * 用于釋放資源 */ public void destroy() { super.destroy(); System.out.println("釋放系統資源時,destroy()方法被調用!"); } }
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/ServletLife,觀察控制臺輸出信息,如圖1-6所示。

圖1-6 Servlet生命周期
打開多個IE窗口,訪問此Servlet,觀察控制臺輸出,會發現init()方法只運行一次,而service()方法會對每次請求都做出響應。
1.5 Servlet數據處理
Servlet數據處理主要包括讀取表單數據、HTTP請求報頭的處理和HTTP響應報頭的設置。
1.5.1 讀取表單數據
當訪問Internet網站時,在瀏覽器地址欄中會經常看到如下所述的字符串:
http://host/path?usr=tom&dest=ok
該字符串問號后面的部分為表單數據(Form Data)或查詢數據(Query Data),這些數據以“name=value”形式通過URL傳送,多個數據使用“&”分開,這種形式也稱為“查詢字符串”。查詢字符串緊跟在URL中的“?”后面,所有“名/值”對會被傳遞到服務器,這是服務器獲取客戶端信息所采用的最常見的方式。
表單數據可以通過GET請求方式提交給服務器,此種方式將數據跟在問號后附加到URL的結尾(查詢字符串形式);也可以采用POST請求方式提交給服務器,此種方式將在地址欄看不到表單數據信息,可用于大量數據的傳輸,并且比GET方式更安全。
在學習處理Form表單數據前,先來回顧《Web編程基礎》中學過的關于表單的基本知識。
(1)使用Form標簽創建HTML表單。
使用action屬性指定對表單進行處理的Servlet或JSP頁面的地址,可以使用絕對或相對URL。例如:
<form action="...">...</form>
如果省略action屬性,那么數據將提交給當前頁面對應的URL。
(2)使用輸入元素收集用戶數據。
將這些元素放在Form標簽內,并為每個輸入元素賦予一個name。文本字段是最常用的輸入元素,其創建方式如下:
<input type="text" name="...">
(3)在接近表單的尾部放置提交按鈕。
例如:
<input type="submit"/>
單擊提交按鈕時,瀏覽器會將數據提交給表單action對應的服務器端程序。
1. Form表單數據
通過HttpServletRequest對象可以讀取Form標簽中的表單數據。HttpServletRequest接口在javax.servlet.http包中定義,它擴展了ServletRequest,并定義了描述一個HTTP請求的方法。當客戶端請求Servlet時,一個HttpServletRequest類型的對象會被傳遞到Servlet的service()方法,進而傳遞到doGet()或doPost()方法中去。此對象中封裝了客戶端的請求擴展信息,包括HTTP方法(即GET或POST)、Cookie、身份驗證和表單數據等信息。
表1-1列出了HttpServletRequest接口中用于讀取表單數據的方法。
表1-1 HttpServletRequest接口中讀取表單數據的方法

默認情況下,request.getParameter()使用服務器的當前字符集解釋輸入。要改變這種默認行為,需要使用setCharacterEncoding(String env)方法來設置字符集,例如:
request.setCharacterEncoding("GBK");
下述內容用于實現任務描述1.D.2,使用Servlet處理表單數據,當用戶提交的數據正確時(用戶名haier,密碼soft),輸出“登錄成功!”,否則提示“登錄失敗!”。
(1)首先編寫靜態頁面,用于接收用戶信息。
【描述1.D.2】index.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gbk"> <title>登錄</title> <script language="javascript" type=""> function LoginSubmit(){ var user=document.Login.loginName.value; var pass=document.Login.password.value; if(user==null||user==""){ alert("請填寫用戶名"); } else if(pass==null||pass==""){ alert("請填寫密碼"); } else document.Login.submit(); } </script> </head> <body> <form method="POST" name="Login" action="LoginServlet"> <p align="left"> 用戶名:<input type="text" name="loginName" size="20"></p> <p align="left"> 密 碼:<input type="password" name="password" size="20"></p> <p align="left"> <input type="button" value="提交" name="B1" onclick="LoginSubmit()"> <input type="reset" value="重置" name="B2"></p> </form> </body> </html>
上述HTML代碼中,使用JavaScript對用戶表單進行初始驗證,驗證成功后才提交給LoginSevlet進行處理。
(2)編寫Servlet處理用戶提交表單數據。
【描述1.D.2】LoginServlet.java
public class LoginServlet extends HttpServlet { public LoginServlet() { super(); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 設置請求的編碼字符為GBK(中文編碼) request.setCharacterEncoding("GBK"); // 設置響應的文本類型為html,編碼字符為GBK response.setContentType("text/html;charset=GBK"); // 獲取輸出流 PrintWriter out = response.getWriter(); // 獲取表單數據 String pass = request.getParameter("password"); String user = request.getParameter("loginName"); if ("haier".equals(user) && "soft".equals(pass)) { out.println("登錄成功!"); } else { out.println("登錄失敗!"); } } }
上述代碼中,在doGet()方法中調用了doPost()方法,這樣不管用戶以什么方式提交,處理過程都一樣。因為頁面中使用了中文,為了防止出現中文亂碼問題,所以需要設置請求和響應的編碼字符集,使之能夠支持中文,如下所示:
request.setCharacterEncoding("GBK"); response.setContentType("text/html;charset=GBK");
獲取表單中的數據時,使用getParameter()方法通過參數名獲得參數值,例如:
String pass = request.getParameter("password");
上面語句通過參數名“password”來獲取該參數的值。
注意 如果index.html中表單的提交方式為GET方式,則在瀏覽器地址欄中會出現查詢字符串形式的表單數據(如password=soft&user=haier),但在LoginServlet中獲取參數值的方式完全相同。
(3)在web.xml中注冊該Servlet。
【描述1.D.2】web.xml
<servlet> <display-name>LoginServlet</display-name> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.haiersoft.ch01.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/LoginServlet</url-pattern> </servlet-mapping>
上述代碼中,注冊了一個名為“LoginServlet”的Servlet,當請求的相對URL為“/LoginServlet”時,Servlet容器會將請求交給該Servlet進行處理。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/index.html,運行結果如圖1-7所示。

圖1-7 index.html頁面
在“用戶名”文本欄中輸入“haier”,在“密碼”文本框中輸入“soft”。然后單擊“提交”按鈕,顯示結果如圖1-8所示。
當輸入錯誤的用戶名或密碼時,則顯示“登錄失敗!”,如圖1-9所示。

圖1-8 LoginServlet驗證成功

圖1-9 LoginServlet驗證失敗
Form表單數據中除了普通的表單項之外,在實際開發中會廣泛應用到隱藏域。隱藏域是隱藏的HTML表單變量,可以用來存儲狀態信息,操作起來與一般的HTML輸入域(比如文本輸入域、復選框和單選按鈕)類似,同樣會被提交到服務器。隱藏域與普通的HTML輸入域之間的不同之處就在于客戶端不能看到或修改隱藏域的值。
隱藏域可以用來在客戶端和服務器之間透明地傳輸狀態信息,示例代碼如下。
【代 碼1-2】hidden.html
<html> <head> <title>隱藏域</title> </head> <body bgcolor="blue"> <form method="post" action="nameservlet"> <p> 請輸入用戶名:<br> <input type="text" name="uname"><br> <input type="hidden" name="bcolor" value="blue"><br> <input type="submit" value="submit"> </form> </body> </html>
上述代碼中使用隱藏域將用戶所喜歡的背景色傳遞給服務器。在服務器端獲取隱藏域的數據與表單其他元素一樣,都是使用getParameter()方法通過參數名獲取其數據值。
2. 查詢字符串
查詢字符串是表單數據的另一種情況,它們實質上是相同的。同樣,服務器端的Servlet也是通過HttpServletRequest對象的getParameter()方法或者getParameterValues()方法讀取URL中查詢字符串的信息,然后根據信息可以進行查詢,再把查詢的結果返回。
下述代碼演示查詢字符串的應用。
【代碼1-3】querystr.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GBK"> <title> 查詢字符串</title> </head> <body> <a href="TestURL?id=2010">下一頁</a> </body> </html>
上述代碼中,在超鏈接的URL中使用查詢字符串,在“?”后添加了“id=2010”,該語句傳遞了一個參數id,其值為2010。
【代碼1-4】TestURL
public class TestURL extends HttpServlet { public TestURL() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=GBK"); PrintWriter out = response.getWriter(); String id = request.getParameter("id"); out.println("URL參數值是:" + id); } }
在上述代碼中,使用request對象的getParameter()方法獲取URL中的參數值,并輸出。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/querystr.html,顯示結果如圖1-10所示。
單擊“下一頁”超鏈接,顯示結果如圖1-11所示。

圖1-10 index.html

圖1-11 TestURL結果
1.5.2 處理HTTP請求報頭
客戶端瀏覽器向服務器發送請求的時候,除了用戶輸入的表單數據或者查詢數據之外,通常還會在GET/POST請求行后面加上一些附加的信息;而在服務器向客戶端的請求做出響應的時候,也會自動向客戶端發送一些附加的信息。這些附加信息被稱為HTTP報頭,信息附加在請求信息后面稱為HTTP請求報頭,而附加在響應信息后面則稱為HTTP響應報頭。在Servlet中可以獲取或設置這些報頭的信息。
報頭信息的讀取比較簡單:只需將報頭的名稱作為參數,調用HttpServletRequest的getHeader方法;如果當前的請求中提供了對應的報頭信息,則返回一個String,否則返回null。
另外,這些報頭的參數名稱不區分大小寫,也就是說,也可以通過getHeader("user-agent")來獲得User-Agent報頭。常用的HTTP請求報頭如表1-2所示。
表1-2 常用HTTP請求報頭

盡管getHeader()方法是讀取輸入報頭的通用方式,但由于幾種報頭的應用很普遍,因此HttpServletRequest為它們提供了專門的訪問方法,如表1-3所示。
表1-3 HttpServletRequest獲取報頭信息的方法

下述代碼用于實現任務描述1.D.3,演示報頭信息的讀取方式。
【描述1.D.3】HttpHeadServlet.java
public class HttpHeadServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=gbk"); PrintWriter out = response.getWriter(); StringBuffer buffer = new StringBuffer(); buffer.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">"); buffer.append("<html>"); buffer.append("<head><title>"); String title = "請求表頭信息"; buffer.append(title); buffer.append("</title></head>"); buffer.append("<body>"); buffer.append("<h1 align='center'>" + title + "</h1>"); buffer.append("<b>Request Method: </b>"); buffer.append(request.getMethod() + "<br/>"); buffer.append("<b>Request URL: </b>"); buffer.append(request.getRequestURI() + "<br/>"); buffer.append("<b>Request Protocol: </b>"); buffer.append(request.getProtocol() + "<br/>"); buffer.append("<b>Request Local: </b>"); buffer.append(request.getLocale() + "<br/><br/>"); buffer.append("<table border='1' align='center'>"); buffer.append("<tr bgcolor='#FFAD00'>"); buffer.append("<th>Header Name</th><th>Header Value</th>"); buffer.append("</tr>"); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = (String) headerNames.nextElement(); buffer.append("<tr>"); buffer.append("<td>" + headerName + "</td>"); buffer.append("<td>" + request.getHeader(headerName) + "</td>"); buffer.append("</tr>"); } buffer.append("</body>"); buffer.append("</html>"); out.println(buffer.toString()); } }
上述代碼中,通過調用request對象中的getMethod()方法來獲取用戶請求方式;調用getRequestURI()方法來獲取用戶請求路徑;調用getHeaderNames()方法返回所有請求報頭名稱的集合,遍歷此集合并使用getHeader()提取報頭信息顯示。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/HttpHeadServlet,運行結果如圖1-12所示。

圖1-12 請求報頭信息
1.5.3 設置HTTP響應報頭
在Servlet中,可以通過HttpServletResponse的setHeader()方法來設置HTTP響應報頭,它接收兩個參數,用于指定響應報頭的名稱和對應的值,語法格式如下:
setHeader(String headerName,String headerValue)
常用的HTTP響應報頭如表1-4所示。
表1-4 常用的HTTP響應報頭

注意 一些舊版本的瀏覽器只支持HTTP 1.0的報頭,所以,為了保證程序具有良好的兼容性,應該慎重地使用這些報頭,或者使用HttpServletRequest的getRequestProtocol()方法獲得HTTP的版本后再做選擇。
除了setHeader()方法外,還有兩個方法用于設置日期或者整型數據格式報頭:
setDateHeader(String headerName, long ms)
和
setIntHeader(String headerName, int headerValue)
此外,對于一些常用的報頭,在API中也提供了更方便的方法來設置它們,如表1-5所示。
表1-5 HttpServletResponse響應方法

下述代碼用于實現任務描述1.D.4,通過設置響應報頭,實現動態時鐘。
【描述1.D.4】DateServlet.java
public class DateServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲得一個向客戶發送數據的輸出流 response.setContentType("text/html; charset=GBK");// 設置響應的MIME類型 PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<body>"); response.setHeader("Refresh", "1"); // 設置Refresh的值 out.println("現在時間是:"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); out.println("<br/>" + sdf.format(new Date())); out.println("</body>"); out.println("</html>"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }
上述代碼中,通過設置響應報頭,使得客戶端每隔一秒訪問一次當前Servlet,從而在客戶端能夠動態地觀察時鐘的變化。實現每隔一秒動態刷新的功能代碼如下:
response.setHeader("Refresh", "1");
其中,Refresh為響應頭部信息;1是時間間隔值,以秒為單位。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/DateServlet,運行結果如圖1-13所示。

圖1-13 動態時鐘
注意 在任務描述1.D.4中,通過不斷地刷新當前頁面來訪問DateServlet,從而實現了動態時鐘,在本書第8章通過AJAX技術可以實現無刷新時鐘,讀者可以通過觀察顯示效果對這兩種方式進行比較。
1.6 重定向和請求轉發
重定向和請求轉發是Servlet處理完數據后進行頁面跳轉的兩種主要方式。
1.6.1 重定向
重定向是指頁面重新定位到某個新地址,之前的Request失效,進入一個新的Request,且跳轉后瀏覽器地址欄內容將變為新的指定地址。重定向是通過HttpServletResponse對象的sendRedirect()方法來實現的,該方法用于生成302響應碼和Location響應頭,從而通知客戶端去重新訪問Location響應頭中指定的URL,其語法格式如下:
pubilc void sendRedirect(java.lang.String location)throws java.io.IOException
其中:
■ location參數指定了重定向的URL,它可以是相對路徑也可以是絕對路徑。
使用sendRedirect()方法不僅可以重定向到當前應用程序中的其他資源,還可以重定向到其他應用程序中的資源,例如:
response.sendRedirect("/ch01/index.html");
上面語句重定向到當前站點(ch01)的根目錄下的index.html界面。
下述代碼用于實現任務描述1.D.5,使用請求重定向方式使用戶自動訪問重定向后的頁面。
【描述1.D.5】RedirectServlet.java
public class RedirectServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset="GBK"); PrintWriter out = response.getWriter(); out.println("重定向前"); response.sendRedirect(request.getContextPath() + "/myservlet"); out.println("重定向后"); } }
在web.xml配置文件中配置RedirectServlet的<url-pattern>為“/redirect”。
其中,myservlet對應的Servlet代碼如下所示。
【描述1.D.5】MyServlet.java
public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 設置響應到客戶端的文本類型為HTML response.setContentType("text/html; charset=GBK"); // 獲取輸出流 PrintWriter out = response.getWriter(); out.println("重定向和請求轉發"); } }
在web.xml配置文件中配置MyServlet的<url-pattern>為“/myservlet”。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/redirect,顯示出了MyServlet輸出網頁中的內容,這時瀏覽器地址欄中的地址變成了MyServlet的URL“http://localhost:8080/ch01/myservlet”,結果如圖1-14所示。

圖1-14 重定向地址欄變化
1.6.2 請求轉發
請求轉發是指將請求再轉發到另一頁面,此過程依然在Request范圍內,轉發后瀏覽器地址欄內容不變。請求轉發使用RequestDispatcher接口中的forward()方法來實現,該方法可以把請求轉發到另外一個資源,并讓該資源對瀏覽器的請求進行響應。
RequestDispatcher接口有以下兩個方法。
■ forward()方法:請求轉發,可以從當前Servlet跳轉到其他Servlet。
■ include()方法:引入其他Servlet。
RequestDispatcher是一個接口,通過使用HttpRequest對象的getRequestDispalcher()方法可以獲得該接口的實例對象,例如:
RequestDispatcher rd = request.getRequestDispatcher(path); rd.forward(request,response);
下述代碼用于實現任務描述1.D.5,使用請求轉發方式使用戶自動訪問請求轉發后的頁面。
【描述1.D.5】ForwardServlet.java
//請求轉發 public class ForwardServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=GBK"); PrintWriter out = response.getWriter(); out.println("請求轉發前"); RequestDispatcher rd = request.getRequestDispatcher("/myservlet"); rd.forward(request, response); out.println("請求轉發后"); } }
在IE中訪問http://localhost:8080/ch01/forward,瀏覽器中顯示出了MyServlet輸出網頁中的內容,這時瀏覽器地址欄中的地址不會發生改變,結果如圖1-15所示。

圖1-15 請求轉發地址欄變化
通過上述ForwardServlet和RedirectServlet的運行結果可以看出,轉發和重定向兩種方式在調用后地址欄中的URL是不同的,前者的地址欄不變,后者地址欄中的URL變成目標URL。
此外,轉發和重定向最主要的區別是:轉發前后共享同一個request對象,而重定向前后不在一個請求中。
為了驗證請求轉發和重定向的區別,在示例中會用到HttpServletRequest的存取和讀取屬性值的兩個方法。
■ getAttribute(String name):取得name的屬性值,如果屬性不存在則返回null。
■ setAttribute(String name,Object value):將value對象以name名稱綁定到request對象中。
注意 除HttpServletRequest接口外,HttpSession和ServletContext接口也擁有getAttribute()和setAttribute()方法,分別用來讀取和設置這兩類對象中的屬性值。
下述內容用于實現任務描述1.D.5,通過請求參數的傳遞來驗證forward()方法和sendRedirect()方法在request對象共享上的區別。
(1)改寫RedirectServlet,在sendRedirect()方法中加上查詢字符串。
【描述1.D.5】RedirectServlet.java
public class RedirectServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=GBK"); PrintWriter out = response.getWriter(); request.setAttribute("test","helloworld"); out.println("重定向前"); response.sendRedirect(request.getContextPath() + "/myservlet "); out.println("重定向后"); } }
上述代碼中,調用了setAttribute()方法把test屬性值helloworld存儲到request對象中。
(2)改寫MyServlet,獲取request對象中的test屬性值。
【描述1.D.5】MyServlet.java
public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 設置響應到客戶端的文本類型為HTML response.setContentType("text/html; charset=GBK"); String test =(String)request.getAttribute("test"); // 獲取輸出流 PrintWriter out = response.getWriter(); out.println("重定向和請求轉發"); out.println(test); } }
上述代碼中,從request對象中獲取test屬性值。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/redirect,運行結果如下:
重定向和請求轉發null
由此可知,在MyServlet中的request對象中并沒有獲得RedirectServlet中request對象設置的值。
(3)改寫ForwardServlet,獲取request對象中的test屬性值。
【描述1.D.5】ForwardServlet.java
// 請求轉發 public class ForwardServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=GBK"); request.setAttribute("test","helloworld"); PrintWriter out = response.getWriter(); out.println("請求轉發前"); RequestDispatcher rd = request.getRequestDispatcher("/myservlet"); rd.forward(request, response); out.println("請求轉發后"); } }
上述代碼中,從request對象中獲取test屬性值。
啟動Tomcat,在IE中訪問http://localhost:8080/ch01/forward,運行結果如下:
重定向和請求轉發helloworld
由此可知,在MyServlet中的request對象中獲得了ForwardServlet的request對象設置的值。
通過對上述示例的運行結果進行比較,forward()和sendRedirect()兩者的區別總結如下:
■ forward()只能將請求轉發給同一個Web應用中的組件,而sendRedirect()方法不僅可以重定向到當前應用程序中的其他資源,還可以重定向到其他站點中的資源。如果傳給sendRedirect()方法的相對URL以“/”開頭,它是相對于整個Web站點的根目錄;如果創建RequestDispatcher對象時指定的相對URL以“/”開頭,它是相對于當前Web應用程序的根目錄。
■ sendRedirect()方法重定向的訪問過程結束后,瀏覽器地址欄中顯示的URL會發生改變,由初始的URL地址變成重定向的目標URL;而調用forward()方法的請求轉發過程結束后,瀏覽器地址欄保持初始的URL地址不變。
■ forward()方法的調用者與被調用者之間共享相同的request對象和response對象,它們屬于同一個請求和響應過程;而sendRedirect()方法調用者和被調用者使用各自的request對象和response對象,它們屬于兩個獨立的請求和響應過程。
1.7 小結
通過本章的學習,學生應該能夠學會:
■ 動態網站開發技術有Servlet、JSP、PHP、ASP、ASP.NET和CGI等。
■ Servlet是運行在服務器端的Java程序,內嵌HTML。
■ Servlet生命周期的三個方法分別是init()、service()和destroy()。
■ Servlet處理GET和POST請求時分別使用doGet()和doPost()方法進行處理。
■ HttpServletRequest的getParameter(“參數名稱”)獲取表單、URL參數值。
■ HttpServletResponse的getWriter()獲取向客戶端發送信息的輸出流。
■ HttpServletRequest的getHeader(“報頭名稱”)獲取相關報頭信息。
■ 請求轉發和重定向都可以使瀏覽器獲得另外一個URL所指向的資源。
■ 請求轉發通常由RequestDispatcher接口的forward()方法實現,轉發前后共享同一個請求對象。
■ 重定向由HttpServletResponse接口的sendRedirect()方法實現,重定向不共享同一個請求對象。
練習
1. 下列選項中屬于動態網站技術的是______。(多選)
A. PHP
B. ASP
C. JavaScript
D. JSP
2. 下列關于Servlet的說法正確的是______。(多選)
A. Servlet是一種動態網站技術
B. Servlet運行在服務器端
C. Servlet針對每個請求使用1個進程來處理
D. Servlet與普通的Java類一樣,可以直接運行,不需要環境支持
3. 下列關于Servlet的編寫方式正確的是______。(多選)
A. 必須是HttpServlet的子類
B. 通常需要覆蓋doGet()和doPost()方法或其中之一
C. 通常需要覆蓋service()方法
D. 通常需要在web.xml文件中聲明<servlet>和<servlet-mapping>兩個元素
4. 下列關于Servlet生命周期的說法正確的是______。(多選)
A. 構造方法只會調用一次,在容器啟動時調用
B. init()方法只會調用一次,在第一次請求此Servlet時調用
C. service()方法在每次請求此Servlet時都會被調用
D. destroy()方法在每次請求完畢時會被調用
5. 下列方式中可以執行TestServlet(路徑為 /test)的doPost()方法的是______。(多選)
A. 在IE中直接訪問http://localhost:8080/網站名/test
B. <form action="/網站名/test"> 提交此表單
C. <form action="/網站名/test" method="post"> 提交此表單
D. <form id="form1">,在JavaScript中執行下述代碼:
document.getElementById("form1").action="/網站名/test"; document.getElementById("form1").method="post"; document.getElementById("form1").submit();
6. 針對下述JSP頁面,在Servlet中需要得到用戶選擇的愛好的數量,最合適的代碼是______。
<input type="checkbox" name="aihao" value="1"/>游戲<br/> <input type="checkbox" name="aihao" value="2"/>運動<br/> <input type="checkbox" name="aihao" value="3"/>棋牌<br/> <input type="checkbox" name="aihao" value="4"/>美食<br/>
A. request.getParameter("aihao").length
B. request.getParameter("aihao").size()
C. request.getParameterValues("aihao").length
D. request.getParameterValues("aihao").size()
7. 用戶使用POST方式提交的數據中存在漢字(使用GBK字符集),在Servlet中需要使用下述______語句處理。
A. request.setCharacterEncoding("GBK");
B. request.setContentType("text/html;charset=GBK");
C. response.setCharacterEncoding("GBK");
D. response.setContentType("text/html;charset=GBK");
8. 簡述Servlet的生命周期。Servlet在第一次和第二次被訪問時,其生命周期方法的執行有何區別?
9. 簡述轉發和重定向兩種頁面跳轉方式的區別,在Servlet中分別使用什么方法實現?
- Architecting the Industrial Internet
- 算法大爆炸:面試通關步步為營
- RTC程序設計:實時音視頻權威指南
- Android NDK Beginner’s Guide
- Java Web應用開發技術與案例教程(第2版)
- 微信公眾平臺開發:從零基礎到ThinkPHP5高性能框架實踐
- Learning Network Forensics
- SAP BusinessObjects Dashboards 4.1 Cookbook
- Serverless computing in Azure with .NET
- NGINX Cookbook
- 區塊鏈技術與應用
- Processing創意編程指南
- Extending Unity with Editor Scripting
- Visual C++開發寶典
- Natural Language Processing with Python Cookbook