JavaWeb基礎總結:Servlet專題
最近工作中有部分整改老介面的任務,大部分與Spring的攔截器,Tomcat相關,改到一些底層的程式碼發現,對基礎J2EE的知識有些遺忘,需要頻繁查閱,索性從頭系統的整理一下Servlet和Filter的知識。
Servlet是什麼
Servlet(Server Applet)是 Java Servlet 的簡稱,是使用 Java 語言編寫的執行在伺服器端的程式。具有獨立於平臺和協議的特性,主要功能在於互動式地瀏覽和生成資料,生成動態Web內容。通常來說,Servlet 是指所有實現了 Servlet 介面的類。Servlet的主要工作流程如下圖:
Servlet 的請求首先會被 HTTP 伺服器(如 Apache)接收,HTTP 伺服器只負責靜態 HTML 頁面的解析,而 Servlet 的請求會轉交給 Servlet 容器,Servlet 容器會根據 web.xml 檔案中的對映關係,呼叫相應的 Servlet,Servlet 再將處理的結果返回給 Servlet 容器,並通過 HTTP 伺服器將響應傳輸給客戶端。
Servlet 主要用於處理客戶端傳來的 HTTP 請求,並返回一個響應,它能夠處理的請求有 doGet() 和 doPost() 等。
Servlet 由 Servlet 容器提供,Servlet 容器是指提供了 Servlet 功能的伺服器(如 Tomcat)。
Servlet 容器會將 Servlet 動態載入到伺服器上,然後通過 HTTP 請求和 HTTP 應與客戶端進行互動。
Servlet的相關類及介面
Sun 公司提供了一系列的介面和類用於 Servlet 技術的開發,其中最重要的介面是 javax.servlet.Servlet。在 Servlet 介面中定義了 5 個抽象方法,具體如下表:
方法宣告 | 功能描述 |
---|---|
void init(ServletConfig config) | 容器在建立好 Servlet 物件後,就會呼叫此方法。該方法接收一個 ServletConfig 型別的引數,Servlet 容器通過該引數向 Servlet 傳遞初始化配置資訊 |
ServletConfig getSendetConfig() | 用於獲取 Servlet 物件的配置資訊,返回 Servlet 的 ServletConfig 物件 |
String getServletInfo() | 返回一個字串,其中包含關於 Servlet 的資訊,如作者、版本和版權等資訊 |
voidservice (ServletRequest request,ServletResponse response) |
負責響應使用者的請求,當容器接收到客戶端訪問 Servlet 物件的請求時,就會呼叫此方法。 容器會構造一個表示客戶端請求資訊的 ServletRequest 物件和一個用於響應客戶端的 ServletResponse 物件作為引數傳遞給 service() 方法。 在 service() 方法中,可以通過 ServletRequest 物件得到客戶端的相關資訊和請求資訊,在對請求進行處理後,呼叫 ServletResponse 物件的方法設定響應資訊 |
void destroy() | 負責釋放 Servlet 物件佔用的資源。當伺服器關閉或者 Servlet 物件被移除時,Servlet 物件會被銷燬,容器會呼叫此方法 |
Servlet 介面中的五個方法,其中 init()、service() 和 destroy() 方法可以表現 Servlet 的生命週期,它們會在某個特定的時刻被呼叫。
針對 Servlet 的介面,Sun 公司提供了兩個預設的介面實現類:GenericServlet 和 HttpServlet。其中,GenericServlet 是一個抽象類,該類為 Servlet 介面提供了部分實現,它並沒有實現 HTTP 請求處理。
HttpServlet 是 GenericServlet 的子類,它繼承了 GenericServlet 的所有方法,並且為 HTTP 請求中的 GET 和 POST 等型別提供了具體的操作方法。通常情況下,編寫的 Servlet 類都繼承自 HttpServlet,在開發中使用最多的也是 HttpServlet 物件。
方法宣告 | 功能描述 |
---|---|
protected void doGet (HttpServletRequest req, HttpServletResponse resp) | 用於處理 GET 型別的 HTTP 請求的方法 |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) | 用於處理 POST 型別的 HTTP 請求的方法 |
Servlet的生命週期
在java程式中,任何物件都有其生命週期,Servlet也不例外;
圖 中描述了 Servlet 的生命週期。Servlet容器載入之後,按照功能的不同,大致可以將 Servlet 的生命週期分為三個階段,分別是初始化階段、執行階段和銷燬階段。
初始化階段
當客戶端向 Servlet 容器發出 HTTP 請求要求訪問 Servlet 時,Servlet 容器首先會解析請求,檢查記憶體中是否已經有了該 Servlet 物件,如果有,則直接使用該 Servlet 物件,如果沒有,則建立 Servlet 例項物件,然後通過呼叫 init() 方法實現 Servlet 的初始化工作。需要注意的是,在 Servlet 的整個生命週期內,它的 init() 方法只能被呼叫一次。
執行階段
這是 Servlet 生命週期中最重要的階段,在這個階段中,Servlet 容器會為這個請求建立代表 HTTP 請求的 ServletRequest 物件和代表 HTTP 響應的 ServletResponse 物件,然後將它們作為引數傳遞給 Servlet 的 service() 方法。
service() 方法從 ServletRequest 物件中獲得客戶請求資訊並處理該請求,通過 ServletResponse 物件生成響應結果。
在 Servlet 的整個生命週期內,對於 Servlet 的每一次訪問請求,Servlet 容器都會呼叫一次 Servlet 的 service() 方法,並且建立新的 ServletRequest 和 ServletResponse 物件,也就是說,service() 方法在 Servlet 的整個生命週期中會被呼叫多次。
銷燬階段
當伺服器關閉或 Web 應用被移除出容器時,Servlet 隨著 Web 應用的關閉而銷燬。在銷燬 Servlet 之前,Servlet 容器會呼叫 Servlet 的 destroy() 方法,以便讓 Servlet 物件釋放它所佔用的資源。在 Servlet 的整個生命週期中,destroy() 方法也只能被呼叫一次。
在這裡,Servlet 物件一旦建立就會駐留在記憶體中等待客戶端的訪問,直到伺服器關閉或 Web 應用被移除出容器時,Servlet 物件才會銷燬。
Servlet作用位置及主要工作流程
Servlet 的每次請求,Web 伺服器在呼叫 service() 方法之前,都會建立 HttpServletRequest 和 HttpServletResponse 物件。其中,HttpServletRequest 物件用於封裝 HTTP 請求訊息,簡稱 request 物件。HttpServletResponse 物件用於封裝 HTTP 響應訊息,簡稱 response 物件(後面會詳細介紹該部分)。詳細流程見下圖:
根據圖中可見,當瀏覽器中發生了請求事件,
- 會向 Web 伺服器傳送了一個 HTTP 請求;
- Web 伺服器根據收到的請求,會先建立一個 HttpServletRequest 和 HttpServletResponse 物件,然後再呼叫相應的 Servlet 程式。
- 在 Servlet 程式執行時,它首先會從 HttpServletRequest 物件中讀取資料資訊,然後通過 service() 方法處理請求訊息,並將處理後的響應資料寫入到 HttpServletResponse 物件中。最後,Web 伺服器會從 HttpServletResponse 物件中讀取到響應資料,併發送給瀏覽器。
注:在 Web 伺服器執行階段,每個 Servlet 都只會建立一個例項物件,針對每次 HTTP 請求,Web 伺服器都會呼叫所請求 Servlet 例項的 service(HttpServletRequest request,HttpServletResponse response)方法,並重新建立一個 request 物件和一個 response 物件。
HttpServletRequest基礎
關於Request物件
前面講了很多關於request物件的資訊,下面就來詳細說一下request物件。在網路中,我們的資料都是基於協議傳輸的,在web應用中,流通最多的就是http協議,
一個瀏覽器請求web伺服器,其實本質是向web伺服器傳送一個http請求,而web伺服器為了能更好的在web應用中使用這個http,就封裝了一個HttpServletRequest的類,併為此建立了一系列的方法及方案來建立request物件;
HttpServletRequest 介面繼承自 ServletRequest 介面,其主要作用是封裝 HTTP 請求訊息。由於 HTTP 請求訊息分為請求行、請求訊息頭和請求訊息體三部分。因此,在 HttpServletRequest 介面中定義了獲取請求行、請求頭和請求訊息體的相關方法。
當訪問 Servlet 時,所有請求訊息將被封裝到 HttpServletRequest 物件中,請求訊息的請求行中包含請求方法、請求資源名、請求路徑等資訊,為了獲取這些資訊,HttpServletRequest 介面定義了一系列方法,如下表所示:
方法宣告 | 功能描述 |
---|---|
String getMethod() | 該方法用於獲取 HTTP 請求訊息中的請求方式(如 GET、POST 等) |
String getRequestURI() | 該方法用於獲取請求行中的資源名稱部分即位於 URL 的主機和端門之後、引數部分之前的部分 |
String getQueryString() | 該方法用於獲取請求行中的引數部分,也就是資源路徑後問號(?)以後的所有內容 |
String getContextPath() | 該方法用於獲取請求 URL 中屬於 Web 應用程式的路徑,這個路徑以 / 開頭,表示相對於整個 Web 站點的根目錄,路徑結尾不含 /。如果請求 URL 屬於 Web 站點的根目錄,那麼返回結果為空字串("") |
String getServletPath() | 該方法用於獲取 Servlet 的名稱或 Servlet 所對映的路徑 |
String getRemoteAddr() | 該方法用於獲取請求客戶端的 IP 地址,其格式類似於 192.168.0.3 |
String getRemoteHost() | 該方法用於獲取請求客戶端的完整主機名,其格式類似於 pcl.mengma.com。需要注意的是,如果無法解析出客戶機的完整主機名,那麼該方法將會返回客戶端的 IP 地址 |
int getRemotePort() | 該方法用於獲取請求客戶端網路連線的埠號 |
String getLocaIAddr() | 該方法用於獲取 Web 伺服器上接收當前請求網路連線的 IP 地址 |
String getLocalName() |
該方法用於獲取 Web 伺服器上接收當前網路連線 IP 所對應的主機名 |
int getLocalPort() | 該方法用於獲取 Web 伺服器上接收當前網路連線的埠號 |
String getServerName() | 該方法用於獲取當前請求所指向的主機名,即 HTTP 請求訊息中 Host 頭欄位所對應的主機名部分 |
int gctServcrPort() | 該方法用於獲取當前請求所連線的伺服器埠號,即 HTTP 請求訊息中 Host 頭欄位所對應的埠號部分 |
StringBuffcr getRequestURL() | 該方法用於獲取客戶端發出請求時的完整 URL,包括協議、伺服器名、埠號、 資源路徑等資訊,但不包括後面的査詢引數部分。注意,getRequcstURL() 方法返冋的結果是 StringBuffer 型別,而不是 String 型別,這樣更便於對結果進行修改 |
方法宣告 | 功能描述 |
---|---|
String getHeader(String name) | 該方法用於獲取一個指定頭欄位的值,如果請求訊息中沒有包含指定的頭欄位,則 getHeader() 方法返回 null;如果請求訊息中包含多個指定名稱的頭欄位,則 getHeader() 方法返回其中第一個頭欄位的值 |
Enumeration getHeaders(String name) |
該方法返回一個 Enumeration 集合物件,該集合物件由請求訊息中出現的某個指定名稱的所有頭欄位值組成。在多數情況下,一個頭欄位名在請求訊息中只出現一次,但有時可能會出現多次 |
Enumeration getHeaderNames() | 該方法用於獲取一個包含所有請求頭欄位的 Enumeration 物件 |
int getIntHeader(String name) | 該方法用於獲取指定名稱的頭欄位,並且將其值轉為 int 型別。需要注意的是,如果指定名稱的頭欄位不存在,則返回值為 -1;如果獲取到的頭欄位的值不能轉為 int 型別,則將發生 NumberFormatException 異常 |
long getDateHeader(String name) | 該方法用於獲取指定頭欄位的值,並將其按 GMT 時間格式轉換為一個代表日期/時間的長整數,該長整數是自 1970 年 1 月 1 日 0 時 0 分 0 秒算起的以毫秒為單位的時間值 |
String getContentType() | 該方法用於獲取 Content-Type 頭欄位的值,結果為 String 型別 |
int getContentLength() | 該方法用於獲取 Content-Length 頭欄位的值,結果為 int 型別 |
String getCharacterEncoding() | 該方法用於返回請求訊息的實體部分的字符集編碼,通常是從 Content-Type 頭欄位中進行提取,結果為 String 型別 |
請求轉發
當一個 Web 資源收到客戶端的請求後,如果希望伺服器通知另外一個資源處理請求,可以通過RequestDispatcher 物件來解決, ServletRequest 介面中定義了一個獲取 RequestDispatcher 物件的方法。
- RequestDispatcher getRequestDispatcher (String path)
返回封裝了某條路徑所指定資源的 RequestDispatcher 物件。其中,引數 path 必須以“/”開頭,用於表示當前 Web 應用的根目錄。需要注意的是,WEB-INF 目錄中的內容對 RequestDispatcher 物件也是可見的。因此,傳遞給 getRequestDispatcher(String path) 方法的資源可以是 WEB-INF 目錄中的檔案
RequestDispatcher 介面定義了兩個相關方法。
方法宣告 | 功能描述 |
---|---|
forward(ServletRequest request,ServletResponse response) | 該方法用於將請求從一個 Servlet 傳遞給另一個 Web 資源。在 Servlet 中,可以對請求做一個初步處理,然後通過呼叫這個方法,將請求傳遞給其他資源進行響應。需要注意的是,該方法必須在響應提交給客戶端之前被呼叫,否則將丟擲 IllegalStateException 異常 |
include(ServletRequest request,ServletResponse response) |
該方法用於將其他的資源作為當前響應內容包含進來 |
HttpServletResponse基礎
關於Response物件
HttpServletnse 介面繼承自 ServletResponse 介面,主要用於封裝 HTTP 響應訊息。由於 HTTP 響應訊息分為狀態行、響應訊息頭、訊息體三部分。因此,在 HttpServletResponse 介面中定義了向客戶端傳送響應狀態碼、響應訊息頭、響應訊息體的方法。
當 Servlet 向客戶端回送響應訊息時,需要在響應訊息中設定狀態碼。因此,HttpServletResponse 介面定義了兩個傳送狀態碼的方法。
1)setStatus(int status)方法
該方法用於設定 HTTP 響應訊息的狀態碼,並生成響應狀態行。由於響應狀態行中的狀態描述資訊直接與狀態碼相關,而 HTTP 版本由伺服器確定,因此,只要通過 setStatus(int status)方法設定了狀態碼,即可實現狀態行的傳送。需要注意的是,在正常情況下,Web 伺服器會預設產生一個狀態碼為 200 的狀態行。
2)sendError(int sc)方法
Servlet 向客戶端傳送的響應訊息中包含響應頭欄位,由於 HTTP 協議的響應頭欄位有很多種,因此,HttpServletResponse 介面定義了一系列設定 HTTP 響應頭欄位的方法。
方法宣告 | 功能描述 |
---|---|
void addHeader(String name,String value) | 這兩個方法都是用於設定 HTTP 協議的響應頭欄位。其中,引數 name 用於指定響應頭欄位的名稱,引數 value 用於指定響 應頭欄位的值。不同的是,addHeader() 方法可以增加同名的響應頭欄位,而 setHeader() 方法則會覆蓋同名的頭欄位 |
void setHeader (String name,String value) | |
void addIntHeader(String name,int value) | 這兩個方法專門用於設定包含整數值的響應頭,避免了使用 addHeader() 與 setHeader() 方法時需要將 int 型別的設定值轉換為 String 型別的麻煩 |
void setIntHeader(String name, int value) |
|
void setContentType(String type) | 該方法用於設定 Servlet 輸出內容的 MIME 型別,對於 HTTP 協議來說,就是設定 Content-Type 響應頭欄位的值。例如,如果傳送到客戶端的內容是 jpeg 格式的影象資料,就需要將響應頭欄位的型別設定為 image/jpeg。需要注意的是,如果響應的內容為文字,setContentType() 方法還可以設定字元編碼,如 text/html;charset = UTF-8 |
void setLocale (Locale loc) | 該方法用於設定響應訊息的本地化資訊。對 HTTP 來說,就是設定 Content-Language 響應頭欄位和 Content-Type 頭欄位中的字符集編碼部分。需要注意的是,如果 HTTP 訊息沒有設定 Content-Type 頭欄位,則 setLocale() 方法設定的字符集編碼不會出現在 HTTP 訊息的響應頭中,如果呼叫 setCharacterEncoding() 或 setContentType() 方法指定了響應內 容的字符集編碼,則 setLocale() 方法將不再具有指定字符集編碼的功能 |
void setCharacterEncoding(String charset) | 該方法用於設定輸出內容使用的字元編碼,對 HTTP 協議來說,就是設定 Content-Type 頭欄位中的字符集編碼部分。如果沒有設定 Content-Type 頭欄位,則 setCharacterEncoding 方法設 置的字符集編碼不會出現在 HTTP 訊息的響應頭中。setCharacterEncoding() 方法比 setContentType() 和 setLocale() 方法的優先權高,它的設定結果將覆蓋 setContentType() 和 setLocale() 方法所設定的字元碼錶 |
於在 HTTP 響應訊息中,大量的資料都是通過響應訊息體傳遞的,因此,ServletResponse 遵循以 I/O 流傳遞大量資料的設計理念。在傳送響應訊息體時,定義了兩個與輸出流相關的方法。
1)getOutputStream() 方法
該方法所獲取的位元組輸出流物件為 ServletOutputStream 型別。由於 ServletOutputStream是OutputStream 的子類,它可以直接輸出位元組陣列中的二進位制資料。因此,要想輸出二進位制格式的響應正文,就需要使用 getOutputStream() 方法。
2)getWriter() 方法
該方法所獲取的字元輸出流物件為 PrintWriter 型別。由於 PrintWriter 型別的物件可以直接輸出字元文字內容,因此,要想輸出內容全部為字元文字的網頁文件,則需要使用 getWriter() 方法。
注意:雖然 response 物件的 getOutputStream() 和 getWriter() 方法都可以傳送響應訊息體,但是,它們之間互相排斥,不可同時使用,否則會發生 IllegalStateException 異常。
請求重定向
在某些情況下,針對客戶端的請求,一個 Servlet 類可能無法完成全部工作。這時,可以使用請求重定向完成這一工作。
請求重定向指 Web 伺服器接收到客戶端的請求後,可能由於某些條件的限制,不能訪問當前請求 URL 所指向的 Web 資源,而是指定了一個新的資源路徑,讓客戶端重新發送請求。
為了實現請求重定向,HttpServletResponse 介面定義了一個 sendRedirect() 方法,該方法用於生成 302 響應碼和 Location 響應頭,從而通知客戶端重新訪問 Location 響應頭中指定的 URL
sendRedirect() 方法的工作原理如圖
當客戶端訪問 Servlet1 時,由於在 Servlet1 中呼叫了 sendRedirect() 方法將請求重定向到 Servlet2,因此,瀏覽器收到 Servlet1 的響應訊息後,立刻向 Servlet2 傳送請求,Servlet2 對請求處理完畢後,再將響應訊息回送給客戶端瀏覽器並顯