Java Web 掃盲行動

你在我家門口發表於2019-04-26

前言

這次分享講一下 Java Web 相關的基礎知識,主要就是 servlet 部分的知識。涉及到的知識點比較的多,如果同學們來不及看,可以先收藏起來,有空的時候再慢慢看哦!下面我們步入正題。

1. HTTP 協議

  • 協議 就是一套約定好的規則,只要我們遵循其中的規則就能很好的進行溝通與協作。HTTP 協議也一樣,HTTP 協議嚴格規定了 HTTP 請求和 HTTP 響應的資料格式,只要 HTTP 伺服器與客戶程式之間的交換資料都遵守 HTTP 協議,雙方就能看得懂對方傳送的資料,從而順利交流。
  • HTTP 協議位於 應用層,建立在 TCP/IP 協議的基礎上。HTTP 協議使用可靠的 TCP 連線,預設埠 80 埠。

1.1 HTTP 響應格式

HTTP 響應也由3部分構成:

  1. HTTP 協議的版本、狀態碼和描述。
  2. 響應頭(Response Header)。
  3. 響應正文(Response Content)。

以下是一些常見的狀態碼:

  • 200表示伺服器已經成功地處理了客戶端發出的請求
  • 400:錯誤的請求,客戶傳送的HTTP請求不正確。
  • 404:檔案不存在。
  • 405:伺服器不支援客戶的請求方式。
  • 500:伺服器內部錯誤。

1.2 正文部分的 MIME 型別

HTTP 請求及響應的正文部分可以是任意格式的資料,如何保證接收方能看得懂傳送方傳送的 正文資料 呢?HTTP協議採用 MIME 協議來規範正文的資料格式。

遵循 MIME 協議的資料型別統稱為 MIME 型別。在 HTTP 請求頭和 HTTP 響應頭中都有一個 Content-Type 項,用來指定 請求正文部分響應正文 部分的 MIME 型別。下標列出了常見的 MIME 型別與副檔名之間的對應關係。

副檔名 MIME型別
未知的資料型別或不可識別的副檔名 content/unknown
.bin、.exe、.o、.a、.z application/octet-stream
.pdf application/pdf
.zip application/zip
.tar application/x-tar
.gif image/gif
.jpg、.jpeg image/jpeg
.htm ,html text/html
.text .c .h .txt .java text/plain
.mpg .mpeg video/mpeg
.xml application/xml

2. Web 伺服器

什麼是Web伺服器呢?Web伺服器是由專門的伺服器開發商建立 ,用來發布和執行Web應用的。Web 伺服器如何能動態執行由第三方建立的Web應用中的程式程式碼呢?所以我們需要一個 中介方 制定 Web應用Web 伺服器 進行協作的標準介面,Servlet 就是其中最主要的一個介面。中介方規定:

  • Web 伺服器可以訪問任意一個 Web 應用中實現 Servlet 介面的類。
  • Web 應用中用於被 Web 伺服器動態呼叫的程式程式碼位於 Servlet 介面的實現類中。

3B49D892-27A6-4C31-AA06-066C45CF2BA3

Servlet 規範把能夠釋出和執行 JavaWeb 應用的 Web伺服器 稱為 Servlet 容器,它的最主要的特徵是動態執行 JavaWeb 實現類中的程式程式碼。常見的 Servlet 容器有 Tomcat、 Jetty、WebLogic、WebSphere、JBoss 等。

2.1 Tomcat 伺服器

Tomca 作為 Servlet 容器的基本功能 如下圖所示,Tomcat 作為執行 Servlet 的容器,其基本功能是 負責接收和解析來自客戶的請求,同時把客戶的請求 傳送給相應的 Servlet,並把 Servlet 的響應結果 返回給客戶。

B5915359-62D4-4A78-AC89-C6DEBA70980D

Servlet 容器響應客戶請求訪問特定 Servlet 的流程如下:

  1. 客戶發出要求訪問特定 Servlet 的請求。
  2. Servlet 容器接收到客戶請求,對其解析。
  3. Servlet 容器建立一個 ServletRequest 物件,在 ServletRequest 物件中包含了客戶請求資訊及其他關於客戶的資訊,如請求頭、請求正文,以及客戶機的IP地址等。
  4. Servlet 容器建立一個 ServletResponse 物件。
  5. Servlet 容器呼叫客戶所請求的 Servlet 的 service() 服務方法,並且把 ServletRequest物件和 ServletResponse 物件作為引數傳給該服務方法。
  6. Servlet 從 ServletRequest 物件中可獲取客戶的請求資訊。
  7. Servlet 利用 ServletResponse 物件來生成響應結果。
  8. Servlet 容器把 Servlet 生成的響應結果傳送給客戶。
    C566E1C3-8F21-48F2-95AC-5E811A2AA3DF

3. JavaWeb 應用

為了讓 Servlet 容器能順利地找到 JavaWeb 應用中的各個元件,Servlet 規範規定,JavaWeb 應用必須採用固定的目錄結構。Servlet 規範還規定,JavaWeb 應用的配置資訊存放在 WEB-INF/web.xml 檔案中,Servlet 容器從該檔案中讀取配置資訊。

3.1 JavaWeb 應用的目錄結構

假定開發一個名為 helloapp 的 JavaWeb 應用,首先,應該建立這個Web應用的目錄結構,如下表所示:

目錄 描述
/helloapp Web應用的根目錄。
/helloapp/WEB-INF 存放Web應用的配置檔案web.xml
/helloapp/WEB-INF/classes 存放各種.class檔案,Servlet 類的 .class 檔案也放於此目錄下
/helloapp/WEB-INF/lib 存放Web應用所需的各種JAR檔案。例如,JDBC驅動程式的JAR檔案。

可以看出 Servlet 容器並不關心你的原始碼放在哪裡,它只關心 .class 檔案,因為載入類只需要用到 .class 檔案。在 WEB-INF 目錄的 classes及 lib 子目錄下,都可以存放 Java 類檔案。在執行時,Servlet 容器的類載入器先載入 classes 目錄下的類,再載入 lib 目錄下的 JAR 檔案(Java類庫的打包檔案)中的類。因此,如果兩個目錄下存在同名的類,classes 目錄下的類具有優先權。

4. Servlet 技術

大佬終於出場了,大家掌聲歡迎!本節主要介紹的就是 Servlet 中經常需要使用到的 "神器“。

4.1 Servlet 常用物件

  • 請求物件(ServletRequest 和 HttpServletRequest):Servlet 從該物件中獲取來自客戶端的請求資訊。
  • 響應物件(ServletResponse 和 HttpServletResponse):Servlet 通過該物件來生成響應結果。
  • Servlet配置物件(ServletConfig):當容器初始化一個 Servlet 物件時,會向 Servlet 提供一個 ServletConfig 物件,Servlet 通過該物件來獲取初始化引數資訊及 ServletContext 物件。
  • Servlet上下文物件(ServletContext):Servlet 通過該物件來訪問容器為當前 Web 應用提供的各種資源。

4.2 Servlet API

Servlet API 主要由兩個 Java 包組成:javax.servletjavax.servlet.http

  • javax.servlet :包中定義了 Servlet 介面及相關的通用介面和類;
  • javax.servlet.http 包中主要定義了與 HTTP 協議相關的 HTTPServlet 類、HTTPServletRequest 介面和 HTTPServletResponse 介面。

4.3 Servlet 介面

Servlet API 的核心是 javax.servlet.Servlet 介面,所有的 Servlet 類都必須實現這一介面。在 Servlet 介面中定義了5個方法,其中3個方法都由 Servlet 容器來呼叫,容器會在 Servlet的生命週期的不同階段呼叫特定的方法。

  • init(ServletConfig config):負責初始化 Servlet 物件。容器在建立好 Servlet 物件後,就會呼叫該方法。
  • service(ServletRequest req, ServletResponse res):負責響應客戶的請求,為客戶提供相應服務。當容器接收到客戶端要求訪問特定 Servlet 物件的請求時,就會呼叫該 Servlet 物件的 service() 方法。
  • destroy():負責釋放Servlet物件佔用的資源。當 Servlet 物件結束生命週期時,容器會呼叫此方法。

Servlet 介面還定義了以下兩個返回 Servlet 的相關資訊的方法。JavaWeb 應用中的程式程式碼可以訪問 Servlet 的這兩個方法,從而獲得 Servlet 的配置資訊及其他相關資訊。

  • getServletConfig():返回一個 ServletConfig 物件,在該物件中包含了 Servlet 的初始化引數資訊。
  • getServletInfo():返回一個字串,在該字串中包含了Servlet的建立者、版本和版權等資訊。

在 Servlet API 中,java.servlet.GenericServlet 抽象類實現了 Servlet 介面,而 javax.servlet.http.HttpServlet 抽象類是 GenericServlet 類的子類。當使用者開發自己的 Servlet 類時,可以選擇擴充套件 GenericServlet 類或者 HTTPServlet 類。

4.4 GenericServlet 抽象類

GenericServlet 抽象類為 Servlet 介面提供了通用實現,它與任何 網路應用層協議無關。GenericServlet 類除了實現 Servlet 介面,還實現了 ServletConfig 介面和 Serializable 介面。

從GenericServlet 類的原始碼可以看出,GenericServlet 類實現了 Servlet 介面中的 init(ServletConfig config) 初始化方法。GenericServlet 類有一個 ServletConfig 型別的私有例項變數config,當 Sevlet容器 呼叫 GenericServletinit(ServletConfig config) 方法時,該方法使得私有例項變數 config 引用由 容器傳入 的 ServletConfig 物件,即使得 GenericServlet 物件與一個 ServletConfig 物件關聯。

public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
複製程式碼

GenericServlet類還自定義了一個不帶引數的 init() 方法,init(ServletConfig config) 方法會呼叫此方法。對於GenericServlet類的子類,如果希望覆蓋父類的初始化行為,有以下兩種辦法:

  1. 覆蓋父類的不帶引數的init()方法:
public void init(){ 
// 子類具體的初始化行為 
} 
複製程式碼
  1. 覆蓋父類的帶引數的 init(ServletConfig config) 方法。如果希望當前 Servlet 物件與 ServletConfig 物件關聯,應該現在該方法中呼叫 super.init(config) 方法:
public void init(ServletConfig config){ 
  // 呼叫父類的init(config)方法 
  super.init(config);
  // 子類具體的初始化行為 
} 
複製程式碼

GenericServlet 類沒有實現 Servlet 介面中的 service() 方法。service() 方法是 GenericServlet 類中唯一的抽象方法,GenericServlet 類的具體子類必須實現該方法,從而為特定的客戶請求提供具體的服務。

此外,GenericServlet 類實現了 ServletConfig 介面中的所有方法。因此,GenericServlet 類的子類可以直接呼叫在 ServletConfig 介面中定義的 getServletContext()、getInitParameter() 和 getInitParameterNames() 等方法。

GenericServlet 類實現了 Servlet 介面和 ServletConfig 介面。GenericServlet 類的主要身份是 Servlet,此外,它還運用 裝飾設計模式,為自己附加了 ServletConfig 裝飾身份。在具體實現中,GenericServlet 類包裝了一個 ServletConfig 介面的例項,通過該例項來實現 ServletConfig 介面中的方法。

4.5 HttpServlet 抽象類

HttpServlet 類是 GenericServlet 類的子類。HttpServlet 類為 Servlet 介面提供了與 HTTP協議 相關的通用實現,也就是說,HttpServlet 物件適合執行在客戶端採用 HTTP 協議通訊的 Servlet 容器或者 Web 伺服器中。在開發 JavaWeb 應用時,自定義的 Servlet 類一般都擴充套件 HttpServlet 類。

HTTP 協議把客戶請求分為 GET、POST、PUT 和 DELETE 等多種方式。HttpServlet 類針對每一種請求方式都提供了相應的服務方法,如doGet()、doPost()、doPut和doDelete()等方法。

從 HttpServlet 的原始碼可以看出,HttpServlet 類實現了 Servlet 介面中的 service(ServletRequest req,ServletResponse res) 方法,該方法實際上呼叫的是它的過載方法:

service(HttpServletRequest req, HttpServletResponse resp)

在以上過載 service() 方法中,首先呼叫 HttpServletRequest 型別的 req 引數的 getMethod() 方法,從而獲得客戶端的請求方式,然後依據該請求方式來呼叫匹配的服務方法。如果為 GET 方法,則呼叫 doGet() 方法;如果為 POST 方式,則呼叫 doPost() 方法,依次類推。

4.6 ServletRequest介面

在 Servlet 介面的 service(ServletRequest req, ServletResponse res) 方法中有一個 ServletRequest 型別的引數。ServletRequest 類表示來自客戶端的請求。當 Servlet 容器接收到客戶端要求訪問特定 Servlet 的請求時,容器先解析客戶端的原始請求資料,把它包裝成一個ServletRequest 物件 。當容器呼叫 Servlet 物件的 service() 方法時,就可以把 ServletRequest 物件作為引數傳給 service() 方法。

ServletRequest 介面提供了一系列用於讀取客戶端的請求資料的方法:

  • getContentLength():返回請求正文的長度。如果請求正文的長度未知,則返回-1。

  • getContentType():獲得請求正文的MIME型別。如果請求正文的型別未知,則返回null。

  • getInputStream():返回用於讀取請求正文的輸入流。

  • getParameter(String name):根據給定的請求引數名,返回來自客戶端請求中的匹配的請求引數值。

  • getReader():返回用於讀取字串形式的請求正文的BufferedReader物件。等等

此外,在 ServletRequest 介面中還定義了一組用於在請求範圍記憶體取共享資料的方法:

  • getAttribute(String name,Object object):在請求範圍內儲存一個屬性,引數 name 表示屬性名,引數 object 表示屬性值。

  • getAttribute(String name):根據 name 引數給定的屬性名,返回請求範圍內的匹配的屬性值。

  • removeAttribute(String name):從請求範圍內刪除一個屬性。

4.7 HttpServletRequest 介面

HttpServletRequest 介面是 ServletRequest 介面的子介面。HttpServlet 類的過載 service() 方法及 doGet() 和doPost() 等方法都有一個 HttpServletRequest 型別的引數。

HttpServletRequest 介面提供了用於讀取HTTP請求中的相關資訊的方法:

  • getContextPath():返回客戶端所請求訪問的Web應用的URL入口。例如,客戶端訪問的URL為 http://localhost:8080/helloapp/info,那麼該方法返回 ”/helloapp"

  • getCookies():返回HTTP請求中的所有 Cookie。

  • getHeader(String name):返回 HTTP 請求頭部的特定項。

  • getHeaderNames():返回一個 Enumeration 物件,它包含了 HTTP 請求頭部的所有專案名。

  • getMethod():返回 HTTP 請求方式。

  • getRequestURI():返回HTTP請求的頭部的第一行中的 URI。

  • getQueryString():返回HTTP請求中的查詢字串,即URL中的 ? 後面的內容。

4.8 ServletResponse介面

在 Servlet 介面的 service(ServletRequest req, ServletResponse res) 方法中有一個 ServletResponse 型別的引數。Servlet 通過 ServletResponse 物件來生成響應結果。當 Servlet 容器接收到客戶端要求訪問特定 Servlet 的請求時容器會建立一個 ServletResponse 物件,並把它作為引數傳給 Servlet 的 service() 方法。

在 ServletResponse 介面中定義了一系列與生成響應結果相關的方法。

  • getOutputStream():返回一個 ServletOutputStream 物件,Servlet 用它來輸出二進位制的正文資料。

  • getWriter():返回一個PrintWriter物件,Servlet 用它來輸出字串像是的正文資料。

ServletResponse 中響應正文的預設 MIME 型別為 text/plain,即純文字型別。而 HttpServletResponse 中響應正文的預設 MIME 型別為 text/html,即HTML文件型別。

為了提高輸出資料的效率,ServletOutputStream 和 PrintWriter 先把資料寫到緩衝區內。當緩衝區內的資料被提交給客戶後,ServletResponse 的 isCommitted() 方法返回 true。在以下幾種情況下,緩衝區內的資料會被提交給客戶,即資料被髮送到客戶端:

  • 當緩衝區內的資料 已滿 時,ServletOutputStream 或 PrintWriter 會自動把緩衝區內的資料傳送給客戶端,並且清空緩衝區。

  • Servlet 呼叫 ServletResponse 物件的 flushBuffer() 方法。

  • Servlet 呼叫 ServletOutputStreamPrintWriter 物件的 flush() 方法或 close() 方法。

為了確保 ServletOutputStream 或 PrintWriter 輸出的所有資料都會被提交給客戶,比較安全的做法是在所有資料都輸出完畢後,呼叫 ServletOutputStream 或 PrintWriter 的 close() 方法。

在 Tomcat 的實現中,如果 Servlet 的 service() 方法沒有呼叫 ServletOutputStream 或 PrintWriter 的 close() 方法,那麼 Tomcat 在呼叫完 Servlet 的 service() 方法後,會 關閉 ServletOutputStream 或 PrintWriter,從而確保 Servlet 輸出的所有資料被提交給客戶。

值得注意的是,如果要設定響應正文的MIME型別和字元編碼,必須 先呼叫 ServletResponse 物件的 setContentType()setCharacterEncoding() 方法,然後 再呼叫 ServletResponse 的 getOutputStream() 或 getWriter() 方法,或者提交緩衝區內的正文資料。只有滿足這樣的操作順序,所做的設定才能生效。

4.9 HttpServletResponse 介面

HttpServletResponse 介面是 ServletResponse 的子介面,HttpServlet 類的過載 service() 方法及 doGet 和 doPost() 等方法都有一個 HttpServletResponse 型別的引數。

HttpServletResponse介面提供了與HTTP協議相關的一些方法,Servlet可通過這些方法來設定HTTP響應頭或向客戶端寫Cookie。

  • addHeader(String name,String value):向HTTP響應頭中加入一項內容。

  • setHeader(String name,String msg):設定HTTP響應頭中的一項內容。如果在響應頭中已經存在這項內容,那麼原先所做的設定將被覆蓋。

  • sendError(int sc):向客戶端傳送一個代表特定錯誤的HTTP響應狀態程式碼。

  • sendError(int sc,String msg):向客戶端傳送一個代表特定錯誤的HTTP響應狀態程式碼,並且傳送具體的錯誤訊息。

  • setStatus(int sc):設定HTTP響應的狀態程式碼。

  • addCookie(Cookie cookie):向HTTP響應中加入一個Cookie。

以下3種方式都能設定 HTTP 響應正文的 MIME 型別及字元編碼:

// 方式一 
response.setContentType(“text/html;charset=utf-8”); 
// 方式二 
response.setContentType(“text/html”); 
response.setCharacterEncoding(“utf-8”); 
// 方式三 
response.setHeader(“Content-type”,”text/html;charset=utf-8”); 
複製程式碼

4.10 ServletConfig 介面

Servlet介面的 init(ServletConfig congfig) 方法有一個 ServletConfig 型別的引數。當 Servlet 容器初始化一個 Servlet 物件時,會為這個 Servlet 物件建立一個 ServletConfig 物件。在 ServletConfig 物件中包含了 Servlet 的 初始化引數資訊,此外,ServletConfig 物件還與當前Web應用的 ServletContext 物件關聯。

ServletConfig 介面中定義了以下方法。

  • getInitParameter(String name):根據給定的初始化引數名,返回匹配的初始化引數值。

  • getInitParameterNames():返回一個Enumeration物件,裡面包含了所有的初始化引數名。

  • getServletContext():返回一個ServletContext物件。

每個初始化引數包括一對引數名和引數值。在web.xml檔案中配置一個Servlet時,可以通過 元素來設定初始化引數。 元素的 子元素設定引數名, 子元素設定引數值。

HttpServlet 類繼承 GenericServle t類,而 GenericServlet 實現了 ServletConfig 介面,因此在 HttpServlet 或GenericServlet 類及子類中都可以直接呼叫 ServletConfig 介面中的方法。

4.11 ServletContext 介面

ServletContextServletServlet 容器 之間 直接通訊 的介面。Servlet容器在啟動一個Web應用時,會為它建立一個ServletContext物件。每個Web應用都有 唯一 的ServletContext物件,可以把 ServletContext 物件形象地理解為Web應用的總管家,同一個Web應用中的所有Servlet物件都共享一個總管家(敲黑板了,所有Servlet物件共享一個 ServletContext 物件) ,Servlet物件們可通過這個總管家來訪問容器中的各種資源。

Servlet 容器在 啟動一個Web應用時,會為它建立 唯一 的 ServletContext 物件。當 Servlet 容器終止一個Web應用時,就會銷燬它的ServletContext物件。由此可見,ServletContext 物件與Web應用具有 相同的生命週期

下面展示 ServletContext 介面經常使用的幾個方法:

  1. 用於 Web應用範圍內 存取 共享資料 的方法。

    • setAttribute(String name,Object object):把一個Java物件與一個屬性名繫結,並把它存入到ServletContext中。

    • getAttribute(String name):根據引數給定的屬性名,返回一個Object型別的物件,它表示ServletContext中與屬性名匹配的屬性值。

    • getAttributeNames():返回一個Enumeration物件,該物件包含了所有存放在ServletContext中的屬性名。

    • removeAttribute(String name):根據引數指定的屬性名,從ServletContext中刪除匹配的屬性。

  2. 訪問當前Web應用的資源。

    • getContextPath():返回當前Web應用的URL入口。

    • getInitParameter(String name):根據給定的引數名,返回Web應用範圍內的匹配的初始化引數值。在web.xml檔案中,直接在 根元素下定義的 元素表示應用範圍內的初始化引數。

    • getInitParameterNames():返回一個Enumeration物件,它包含了Web應用範圍內所有初始化引數名。

    • getServletContextName():返回Web應用的名字,即web.xml檔案中<display-name>元素的值。

    • getRequestDispatcher(String path):返回一個用於向其他Web元件轉發請求的RequestDispatcher物件。

在 ServletConfig 介面中定義了 getServletContext() 方法。HttpServlet 類繼承 GenericServlet類,而GenericServlet 類實現了 ServletConfig 介面,因此在 HttpServlet 類或 GenericServlet 類及子類中都可以直接呼叫 getServletContext() 方法,從而得到當前Web應用的 ServletContext 物件。

5. JavaWeb 應用的生命週期

JavaWeb 應用的生命週期是由 Servlet 容器來控制的。歸納起來,JavaWeb 應用的生命週期包括 3 個階段。

  • 啟動階段:載入Web應用的有關資料,建立ServletContext物件 ,對Filter和一些Servlet進行初始化。

  • 執行時階段:為客戶端服務。

  • 終止階段:釋放Web應用所佔用的各種資源。

5.1 啟動階段

Servlet 容器在啟動 JavaWeb 應用時,會完成以下操作:

  1. web.xml 檔案中的資料載入到記憶體中。

  2. 為 JavaWeb 應用建立一個 ServletContext 物件。

  3. 對所有的 Filter 進行初始化。

  4. 對那些需要在 Web 應用 啟動時就被初始化Servlet 進行初始化。

5.2 執行時階段

這是 JavaWeb 應用最主要的生命階段。在這個階段,它的所有 Servlet 都處於待命狀態,隨時可以響應客戶端的特定請求,提供相應的服務。加入客戶端請求的 Servlet 還不存在,Servlet 容器會先初始化Servlet ,然後再呼叫它的 service() 服務。

5.3 終止階段

Servlet 容器在終止 JavaWeb 應用時,會完成以下操作:

  1. 銷燬 JavaWeb 應用中所有處於執行時狀態的 Servlet。

  2. 銷燬 JavaWeb 應用中所有處於執行時狀態的 Filter。

  3. 銷燬所有與 JavaWeb 應用相關的物件,如果 ServletContext 物件等,並且釋放 Web 應用所佔用的相關資源。

6. Servlet 的生命週期

JavaWeb 應用的生命週期由 Servlet 容器來控制,而 Servlet 作為 JavaWeb 應用的最核心的元件,其生命週期也由 Servlet 容器來控制。Servlet 的生命週期可以分為3個階段:初始化階段、執行時階段和銷燬階段。在javax.servlet.servlet介面中定義了3個方法:init()、service() 和 destroy(),它們將分別在 Servlet 的不同階段被 Servlet 容器呼叫。

6.1 初始化階段

Servlet 的初始化階段包括 4 個步驟:

  1. Servlet 容器載入 Servlet 類,把它的 .class 檔案中的資料讀入到記憶體中。

  2. Servlet 容器建立 ServletConfig 物件。 ServletConfig 物件包含了 特定 Servlet 的 初始化配置資訊,如 Servlet 的初始化引數。此外, Servlet 容器還會使得 ServletConfig 物件與當前Web應用的 ServletContext 物件 關聯

  3. Servlet 容器建立 Servlet 物件。

  4. Servlet 容器呼叫 Servlet 物件的 init(ServletConfig config) 方法。

以上初始化步驟建立了 Servlet 物件和 ServletConfig 物件,並且 Servlet 物件與 ServletConfig 物件關聯,而 ServletConfig 物件又與當前 Web 應用的 ServletContext 物件關聯。當 Servlet 容器初始化完 Servlet 後,Servlet 物件只要通過 getServletContext() 方法就能得到當前Web應用的 ServletContext 物件。

在下列情況之一,Servlet會進入初始化階段。

  1. 當前Web應用處於執行時階段,特定 Servlet 被客戶端首次請求訪問。多數 Servlet 都會在這種情況下被 Servlet 容器初始化階段。

  2. 如果在 web.xml 檔案中為一個 Servlet 設定了 元素,那麼當 Servlet 容器啟動 Servlet 所屬的Web應用時,就會初始化這個Servlet。假如 Servlet1 和 Servlet2 的 的值分別為1和2,因此Servlet容器啟動當前Web應用時,Servlet1第一個初始化,Servlet2被第二個初始化。而沒有配置 元素的Servlet,當Servlet容器啟動當前Web應用時將不會被初始化,只有當客戶端首次請求訪問該Servlet時,它才會被初始化。

  3. 當Web應用被重新啟動時,Web應用中的所有Servlet都會在特定的時刻被重新初始化。

6.2 執行時階段

這是 Servlet 的宣告週期中的最重要階段。在這個階段,Servlet 可以隨時響應客戶端的請求。當 Servlet 容器接收到要求訪問特定 Servlet 的客戶請求時,Servlet 容器會建立針對於這個請求的 ServletRequest 物件和 ServletResponse 物件,然後呼叫相應 Servlet 物件的 service() 方法。service() 方法從 ServletRequest 物件中獲得客戶請求資訊並處理該請求,在通過 ServletResponse 物件生成響應結果。

當 Servlet 容器把 Servlet 生成的 響應結果傳送 給了客戶,Servlet 容器就會 銷燬ServletRequest 物件和 ServletResponse 物件。

6.3 銷燬階段

當Web應用被終止時,Servlet 容器會先呼叫Web應用中 所有Servlet 物件的 destroy() 方法,然後 再銷燬 這些Servlet物件。在destroy()方法的實現中,可以釋放Servlet所佔用的資源。

此外,容器還會銷燬與物件關聯的 ServletConfig 物件。

7. 使用 ServletContextListener 監聽器

在 Servlet API 中有一個 ServletContextListener 介面,它能夠監聽 ServletContext 物件的生命週期,實際上就是監聽 Web應用 的生命週期。

當Servlet容器啟動或終止Web應用時,它能夠監聽 ServletContextEvent 事件,該事件由ServletContextListener來處理。在ServletContextListener介面中定義了處理ServletContextEvent事件的兩個方法。

  • contextInitialized(ServletContextEvent sec):當Servlet容器 啟動 Web應用時呼叫該方法。在呼叫完該方法 之後,容器再對 Filter初始化 ,並且對那些在Web應用啟動時就需要被初始化的 Servlet 進行初始化。

  • contextDestroyed(ServletContextEvent sec):當Servlet容器 終止 Web應用呼叫該方法。在呼叫該方法 之前,容器會先 銷燬 所有的 ServletFilter 過濾器。

可以看出,在Web應用的生命週期中,ServletContext 物件最早被建立,最晚被銷燬。

使用者自定義的 ServletContextListener 監聽器只有先向 Servlet 容器註冊,Servlet 容器在啟動或終止 Web 應用時,才會呼叫該監聽器的相關方法。在 web.xml 為檔案中, 元素用於向容器註冊監聽器

<listener> 
<listener-class>LinstnerClass</listener-class>
</listener> 
複製程式碼

8. Cookie

Cookie的英文原意是“點心”,它是在客戶端訪問Web伺服器時,伺服器客戶端硬碟上存放的資訊。當客戶端 首次 訪問伺服器時,伺服器現在客戶端存放包含該客戶的相關資訊的Cookie,以後客戶端每次請求訪問伺服器時,都會在 HTTP 請求資料中包含 Cookie,伺服器解析 HTTP 請求中的 Cookie,就能由此獲得關於使用者的相關資訊。

Cookie的執行機制是由HTTP協議規定的,多數 Web 伺服器和瀏覽器都支援 Cookie。Web伺服器為了支援Cookie,需要具備以下功能。

  • 在HTTP響應結果中新增Cookie資料。

  • 解析HTTP請求中的Cookie資料。

瀏覽器為了支援Cookie,需要具備以下功能。

  • 解析HTTP響應結果中的Cookie資料。

  • 把Cookie資料儲存到本地硬碟。讀取本地硬碟上的Cookie資料,把它新增到HTTP請求中。

Tomcat 作為Web伺服器,對Cookie提供了良好的支援。那麼,執行在Tomcat中的Servlet該如何訪問Cookie呢?Java Servlet API為Servlet訪問Cookie提供了簡單易用的介面,Cookie 用 javax.servlet.http.Cookie 類來表示,每個 Cookie 物件包含一個 Cookie 名字和 Cookie 值。

下面程式碼建立了一個 Cookie 物件,然後呼叫 HttpServletResponse 的 addCookie() 方法,把 Cookie 新增到 HTTP 響應結果中:

Cookie theCookie = new Cookie(“username”,”Tom”); 
response.addCookie(theCookie); 
複製程式碼

如果Servlet想讀取來自客戶端的Cookie,那麼可以通過以下方式從HTTP請求中取得所有的Cookie:

Cookie[] cookies = request.getCookies(); 
複製程式碼

對於每個 Cookie 物件,可呼叫 getName() 方法來獲得 Cookie 的名字,呼叫 getValue() 方法來獲得 Cookie 的值。

當 Servlet 向客戶端寫 Cookie 時,還可以通過 Cookie 類的 setMaxAge(int expiry) 方法來設定 Cookie 的有效期。引數 expiry 以秒為單位,它具有以下含義:

  • 如果 expiry 大於零,就指示瀏覽器在客戶端硬碟上儲存 Cookie 的時間為 expiry 秒,有效期內,其他瀏覽器也能訪問這個 Cookie。

  • 如果 expiry 等於零,就指示瀏覽器刪除當前 Cookie。

  • 如果 expiry 小於零,就指示瀏覽器不要把 Cookie 儲存到客戶端硬碟。Cookie 僅僅存在於當前瀏覽器程式中,當瀏覽器程式關閉,Cookie 也就消失。

Cookie預設的有效期為-1。

伺服器對客戶端進行讀寫 Cookie 操作,會給客戶端帶來安全隱患。伺服器可能會向客戶端傳送包含惡意程式碼的Cookie 資料,此外,伺服器可能會依據客戶端的 Cookie 來竊取使用者的保密資訊。因此出於安全起見,多數瀏覽器可以設定是否啟用 Cookie。

9. Session

當客戶端訪問 Web 應用時,在許多情況下,Web 伺服器必須能夠跟蹤客戶的狀態。

Web伺服器跟蹤使用者的狀態通常有4種方法:

  • 在HTML表單中加入隱藏欄位,它包含用於跟蹤使用者狀態的資料。

  • 重寫URL,使它包含用於跟蹤客戶狀態的資料。

  • 用Cookie來傳送用於跟蹤客戶狀態的資料。

  • 使用會話(Session)機制 。

9.1 會話簡介

HTTP 是無狀態的協議。在Web開發領域,會話機制是用於跟蹤客戶狀態的普遍解決方案。會話指的是在一段時間內,單個客戶與Web應用的一連串相關的互動過程。在一個會話中,客戶可能多次請求訪問Web應用的同一個網頁,也有可能請求訪問同一個Web應用中的多個網頁。

Servlet 規範制定了基於Java的會話的具體運作機制。在Servlet API中定義了代表會話的 javax.servlet.http.HttpSession 介面,Servlet容器必須實現這一介面。當一個會話開始時,Servlet容器將建立一個 HttpSession 物件,在該物件中可以存放表示客戶狀態的資訊。Servlet容器為每個 HttpSession 物件分配一個唯一標誌符,符號位 Session ID

key:客戶端 Cookie 只負責存 Session ID,而 Session 物件是儲存在伺服器上的。

下面以一個名為 bookstore 應用為例,介紹會話的運作流程:

  1. 一個瀏覽器程式第一次請求訪問bookstore應用中的任意一個支援會話的網頁,Servlet 容器試圖尋找 HTTP 請求中表示 Session ID 的 Cookie ,由於還不存在這樣的 Cookie ,因此就認為一個新的會話開始了,於是建立一個 HttpSession 物件,為它分配唯一的 Session ID ,然後把 Session ID 作為 Cookie 新增到 HTTP 響應結果中。當瀏覽器接收到 HTTP 響應結果後,會把其中表示 Session ID 的 Cookie 儲存在客戶端。

  2. 瀏覽器程式繼續請求訪問 bookstore 應用中的任意一個支援會話的網頁,在本次 HTTP 請求中會包含表示Session ID 的 Cookie。Servlet 容器試圖尋找 HTTP 請求中表示 Session ID 的 Cookie,由於能得到這樣的 Cookie 。因此認為本次請求已經處於一個會話中了,Servlet容器不再建立新的 HttpSession 物件,而是從 Cookie 中獲取 Session ID ,然後根據 Session ID 找到記憶體中對應的 HttpSession 物件。

  3. 瀏覽器程式重複步驟二,直到當前會話被銷燬,HttpSession物件就會結束生命週期。

表示Session ID的Cookie的有效期為-1,這意味著該Cookie也就消失,本次會話也會結束。在兩個瀏覽器程式中顯式的Session ID的值不一樣,因為兩個瀏覽器程式分別對應不同的會話,而每個會話都有唯一的Session ID。

9.2 HttpSession 的生命週期及會話範圍

會話範圍是指瀏覽器端與一個Web應用進行一次會話的過程。在具體實現上,會話範圍與 HttpSession 物件的生命週期對應。因此,Web元件只要共享同一個 HttpSession 物件,也就能共享會話範圍內的共享資料。

HttpSession介面中的方法描述如下表,Web應用中的JSP或Servlet元件可通過這些方法來訪問會話。

方法 描述
getId() 返回 Session ID。
invalidate() 銷燬當前的會話,Servlet容器會釋放HttpSession物件佔用的資源。
setAttribute(String name, Object value) 將一對name/value屬性儲存在HttpSession物件中。
getAttribute(String name) 根據name引數返回儲存在HttpSession物件中的屬性值。
getAttributeNames() 以陣列的方式返回HttpSession物件中所有屬性名。
removeAttribute(String name) 從HttpSession物件中刪除name引數的指定屬性。
isNew() 判斷是否是新建立的會話
setMaxInactiveInterval(int interval) 設定一個會話可以處於不活動狀態的最長時間,以秒為單位。如果超過這個時間,Servlet容器會自動銷燬會話。如果把引數interval設定為負數,表示不限制會話處於不活動狀態的時間,即會話永遠不會過期。Tomcat為會話設定的預設的保持不活動狀態的最長時間為1800秒。
getMaxInactiveInterval() 讀取當前會話可以處於不活動狀態的最長時間。

在以下情況下,會開始一個新的會話,即Servlet容器會建立一個新的 HttpSession 物件:

  • 一個瀏覽器程式第一訪問Web應用中的支援會話的任意一個網頁。

  • 當瀏覽器程式與Web應用的一次會話已經被銷燬後,瀏覽器程式再次訪問Web應用中的支援會話的任意一個網頁。

在以下情況下,會話被銷燬,即Servlet容器使HttpSession物件結束生命週期,並且存放在會話範圍內的共享資料也都被銷燬:

  • 瀏覽器程式終止。

  • 伺服器端執行 HttpSession 物件的 invalidate() 方法。

  • 會話過期。

當Tomcat中的Web應用被終止時,它的會話不會被銷燬,而是被Tomcat持久化到永久性儲存裝置中,當Web應用重啟後,Tomcat會重新載入這些會話。

當一個會話開始後,如果瀏覽器程式突然關閉,Servlet容器端無法立即知道瀏覽器程式已經被關閉,因此Servlet容器端的HttpSession物件不會立即結束生命週期。不過,當瀏覽器程式關閉後,這個會話就進入不活動狀態,等到超過了 setMaxInactiveInterval(int interval) 方法設定的時間,會話就會因為過期而被 Servlet 容器銷燬。

key:是不是屬於同一個 session,看 sessionId 是否相同就可以了。而 sessionId 又儲存在瀏覽器的 cookie 中,顯然不同瀏覽器儲存的cookie是不一樣的。

9.3 獲取 Session

可以通過 HttpServletRequest 物件來獲得 HttpSession 物件。

在 HttpServletRequest 介面中提供了兩個與會話有關的方法:

  • getSession():是的當前 HttpServlet 支援會話。假如會話已經存在,就返回相應的 HttpSession 物件,否則就建立一個新會話,並返回新建的 HttpSession 物件。該方法等價於呼叫 HttpServletRequest 的 getSession(true) 方法。

  • getSession(boolean create):如果引數 create 為 true,等價於呼叫 HttpServletRequest 的 getSession() 方法;如果引數 create 為 false ,那麼假如會話已經存在,就返回響應的HttpSession物件,否則就返回 null。

key:一個常見的誤解是以為s ession 在有客戶端訪問時就被建立,然而事實是直到某 server 端程式呼叫HttpServletRequest.getSession(true)這樣的語句時才被建立。

10. session 與 cookie 的區別

  1. cookie資料存放在客戶的瀏覽器上,session資料放在伺服器上。

  2. cookie不是很安全,別人可以分析存放在本地的COOKIE並進行COOKIE欺騙,考慮到安全應當使用session。

  3. session會在一定時間內儲存在伺服器上。當訪問增多,會比較佔用你伺服器的效能,考慮到減輕伺服器效能方面,應當使用COOKIE。

  4. 單個cookie儲存的資料不能超過4K,很多瀏覽器都限制一個站點最多儲存20個cookie。

11. 請求轉發

轉發可以將請求轉發給同一Web應用的元件。

// 把請求轉發給OutputServlet 
ServletContext context = getServletContext(); 
RequestDispatcher dispatcher = context.getRequestDispatcher("/OutputServlet");// ok 
// RequestDispatcher dispatcher = context.getRequestDispatcher("outputServlet");//worng 
// RequestDispatcher dispatcher = req.getRequestDispatcher("outputServlet");//ok 
PrintWriter out = res.getWriter(); 
out.println("Output from CheckServlet before forwading request."); 
System.out.println("Output from CheckServlet before forwading request."); 
// throw IllegalArgumentException:Cannot forward after response has been committed 
//out.close();
dispatcher.forward(req, res); 
out.println("Output from CheckServlet after forwading request."); 
System.out.println("Output from CheckServlet after forwading request."); 
複製程式碼
public class OutputServlet extends GenericServlet { 

@Override 
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { 
// 讀取CheckServlet存放在請求範圍內的訊息 
String message = (String) req.getAttribute("msg"); 
PrintWriter out = res.getWriter(); 
out.println(message); 
out.close(); 
} 
} 
複製程式碼

控制檯列印結果:

Output from CheckServlet before forwading request.

Output from CheckServlet after forwading request.

以上 dispatcher.forward(request,response) 方法的處理流程如下:

  1. 清空用於存放響應正文資料的緩衝區。

  2. 如果目標元件為 Servlet 或 JSP,就呼叫它們的 service() 方法,把該方法產生的響應結果傳送到客戶端;如果目標元件為檔案系統中的靜態 HTML 文件,就讀取文件中的資料並把它傳送到客戶端。

dispatcher.forward(request,response) 方法的處理流程可以看出,請求轉發具有以下特點:

  • 由於 forward() 方法先清空用於存放響應正文資料的緩衝區,因此 Servlet 源元件生成的響應結果不會被髮送到客戶端,只有目標元件生成的響應結果才會被髮送到客戶端。

  • 如果源元件在進行請求轉發 之前,已經提交了響應結果(例如呼叫ServletResponse的flushBuffer()方法,或者呼叫與ServletResponse關聯的輸出流的close()方法),那麼 forward() 方法會丟擲 lllegalStateException。為了避免該異常,不應該在源元件中提交響應結果。

  • 在 Servlet 源元件中呼叫 dispatcher.forward(request,response) 方法之後的程式碼也會被執行。同理呼叫out.close() 之後的程式碼也會執行。只是 out.close() 之後的程式碼不會再通過 response 返回到客戶端。

12. 包含

下面程式碼把 header.html 的內容,GreetServlet 生成的響應內容以及 foot.html 的內容都包含到自己的響應結果中。也就是說,下面的Servlet返回給客戶的HTML文件是由自身、header.htm、GreetServlet,以及foot.htm共同產生的。

ServletContext context = getServletContext(); 
RequestDispatcher headDispatcher = context.getRequestDispatcher("/header.htm"); 
RequestDispatcher greetDispatcher = context.getRequestDispatcher("/greet"); 
RequestDispatcher footDispatcher = context.getRequestDispatcher("/footer.htm"); 

headDispatcher.include(request, response); 
greetDispatcher.include(request, response); 
footDispatcher.include(request, response); 
複製程式碼

RequestDispatcher物件的include()方法的處理流程如下。

  1. 如果目標元件為Servlet或JSP,就呼叫它們的相應的service()方法,把該方法產生的響應正文新增到源元件的響應結果中;如果目標元件為HTML文件,就直接把文件的內容新增到源元件的響應結果中。

  2. 返回到源元件的服務方法中,繼續執行後續程式碼塊。

包含與請求轉發相比,前者有以下特點:

  • 源元件與被包含的目標元件的輸出資料都會被新增到響應結果中。

  • 在目標元件中對響應狀態程式碼或者響應頭所做的修改都會被忽略。

當源元件和目標元件之間為請求轉發關係或者包含關係時,對於每一次客戶請求,它們都共享同一個ServletRequest物件及ServletResponse物件,因此源元件和目標元件能共享請求範圍內的共享資料。

13. 重定向

HTTP 協議規定了一種重定向機制,重定向的運作流程如下:

  1. 使用者在瀏覽器輸入特定URL,請求訪問伺服器端的某個元件。

  2. 伺服器端的元件返回一個狀態碼為302的響應結果,響應結果的含義為:讓瀏覽器端再請求訪問另一個Web元件。在響應結果中提供了另一個Web元件的URL。另一個Web元件有可能在同一個Web伺服器上,也可能不在同一個Web伺服器上。

  3. 當瀏覽器端接收到這種響應結果後,再立即自動請求訪問另一個Web元件。

  4. 瀏覽器端接收到來自另一個Web元件的響應結果。

在Java Servlet API中,HttpServletResponse介面的 sendRedirect(String location)方法用於重定向。

重定向之後,導航欄的 URL 會變成目標元件的 URL 地址。response.sendRedirect(String location) 方法具有以下特點:

  • Servlet源元件生成的響應結果不會被髮送到客戶端。response.sendRedirect(String location) 方法一律返回狀態碼為302的響應結果,瀏覽器接收到這種響應結果後,再立即自動請求訪問重定向的目標Web元件,客戶端最後接收到的是目標Web元件的響應結果。

  • 如果源元件在進行重定向之前,已經提交了響應結果,那麼 sendRedirect() 方法會丟擲異常。為了避免該異常,不應該在源元件中提交響應結果。

  • 在Servlet源元件中呼叫 response.sendRedirect(String location) 方法之後的程式碼也會被執行。

  • 源元件和目標元件不共享同一個 ServletRequest 物件,因此不共享請求範圍內的共享資料。

  • 對於 response.sendRedirect(String location) 方法中的引數location,如果以”/“開頭,表示相對於當前伺服器根路徑的URL,如果以”http://“開頭,表示一個完整的URL。

  • 目標元件不必是同一個伺服器上的同一個Web應用中的元件,它可以是Internet上的任意一個有效的網頁。

sendRedirect() 方法是在 HttpServletResponse 介面中定義的,而在 ServletResponse 介面中沒有 sendRedirect() 方法,因為重定向機制是由 HTTP 協議規定的。

14. 轉發與重定向的區別

  1. forward 方法只能轉發給同一個web站點的資源,而 sendRedirect 方法還可以定位到同一個web站點的其他應用。

  2. forward 轉發後,瀏覽器 URL 地址不變,sendRedirect 重定向後,瀏覽器url地址變為目的 URL 地址。

  3. forward 轉發的過程,在一個 servlet 中呼叫了同一個web應用的另一個 servlet 的 service() 方法,所以還是屬於一次請求響應。sendRedirect,瀏覽器先向目的Servlet傳送一次請求,Servlet 看到 sendRedirect 將目的 URL 返回到瀏覽器,瀏覽器再去請求目的 URL ,目的 URL 再返回response到瀏覽器。瀏覽器和伺服器兩次請求響應。

  4. forward 方法的呼叫者與被呼叫者之間共享 Request 和 Response。 sendRedirect 方法由於兩次瀏覽器伺服器請求,所以有兩個 Request 和 Response。

15. 過濾器

各個 Web 元件中的 相同操作 可以放到一個 過濾器 中來完成,這樣就能減少重複編碼。過濾器能夠對一部分客戶請求先進行預處理操作,然後再把請求轉發給響應的 Web 元件,等到 Web 元件生產了響應結果後,過濾器還能對響應結果進行檢查和修改,然後再把修改後的響應結果傳送給客戶。

過濾器負責過濾的 Web 元件可以是 Servlet、JSP 或 HTML 檔案,過濾器的過濾過程如下圖所示。

1

15.1 建立過濾器

所有自定義的過濾器都必須實現 javax.servlet.Filter 介面,這個介面含以下 3 個必須實現的方法。

  • init(FilterConfig filterConfig):這是過濾器的初始化方法。在 Web 應用啟動時, Servlet 容器先建立包含了過濾器配置資訊的 FilterConfig 物件,然後建立 Filter 物件,接著呼叫 Filter 物件的 init(FilterConfig filterConfig) 方法,在這個方法中可通過 config 引數來讀取 web.xml 檔案中為過濾器配置的初始化引數。

  • doFilter(ServletRequest request, ServletResponse response,FilterChain chain):這個方法完成實際的過濾操作。當客戶請求訪問的 URL 與為過濾器對映的 URL 匹配時, Servlet 容器將先呼叫過濾器的 doFilter() 方法。FilterChain 引數用於訪問 後續過濾器 或者 Web元件

  • destroy():Servlet 容器在銷燬過濾器物件前呼叫該方法,在這個方法中可以釋放過濾器佔用的資源。

15.2 Filter 中的 doFilter()方法

doFilter()是整個過濾器中最為關鍵的一個方法,servlet 容器呼叫該方法完成實際的過濾操作

  • 該方法接收兩個引數,分別為 request、response 引數,所以我們可以對 request 進行一些預處理之後再繼續傳遞下去,也可以對返回的 response 結果進行一些額外的操作,再返回到客戶端(前提是response沒有被 close)。

  • Filter.doFilter() 方法中不能直接呼叫 Servlet 的 service 方法,而是呼叫 FilterChain.doFilter 方法來啟用目標 Servlet 的 service 方法,FilterChain 物件時通過 Filter.doFilter 方法的引數傳遞進來的。

  • 在一個 Web 應用程式中可以註冊多個 Filter 程式,如果有多個 Filter 程式都可以對某個 Servlet 程式的訪問過程進行攔截,當針對該 Servlet 的訪問請求到達時,Web 容器將把這多個 Filter 程式組合成一個 Filter 鏈(也叫過濾器鏈)。

  • Filter 鏈中的各個 Filter 的攔截順序與它們在 web.xml 檔案中的對映順序一致,上一個 Filter.doFilter 方法中呼叫 FilterChain.doFilter 方法將啟用下一個 Filter的doFilter 方法,最後一個 Filter.doFilter 方法中呼叫的 FilterChain.doFilter 方法將啟用目標 Servlet的service 方法。

  • 只要 Filter 鏈中任意一個 Filter 沒有呼叫 FilterChain.doFilter 方法,則目標 Servlet 的 service 方法都不會被執行。

15.3 例項

下面通過一個簡單的例子演示基本的過濾器使用。

定義過濾器

該過濾器主要的功能就是在請求之前列印一下初始化引數,請求之後拼接一下返回結果:

public class LogFilter implements Filter { 

private String ip; 

public void init(FilterConfig filterConfig) throws ServletException { 
System.out.println("執行LogFilter初始化方法"); 
// 獲取Filter初始化引數 
this.ip = filterConfig.getInitParameter("ip"); 
} 

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 

throws IOException, ServletException { 
System.out.println("正在執行 LogFilter.doFilter()方法,LogFilter初始化引數ip=" + ip); 
System.out.println("呼叫LogFilter中的chain.doFilter()之前"); 
chain.doFilter(request, response); 
System.out.println("呼叫LogFilter chain.doFilter()之後"); 
PrintWriter out = response.getWriter(); 

// 拼接返回結果 
out.println("This msg is from LogFilter"); 
out.flush(); 

} 

public void destroy() { 
System.out.println("正在執行 LogFilter 的銷燬方法"); 
} 

} 
複製程式碼

TestFilter servlet

public class TestFilter extends HttpServlet { 

protected void doGet(HttpServletRequest request, HttpServletResponse response) 

throws ServletException, IOException { 
System.out.println("正在執行 TestFilter 的 doGet() 方法"); 
PrintWriter out = response.getWriter(); 
out.println("This msg is from HelloServlet doGet()"); 
// 如果執行 close 操作,後續過濾器中對 response 的操作 將無法返回到客戶端 
// out.close(); 
out.flush(); 
} 
} 
複製程式碼

在 web.xml 中配置過濾器

<filter> 
<filter-name>LogFilter</filter-name> 
<filter-class>controller.LogFilter</filter-class> 
<init-param> 
<param-name>ip</param-name> 
<param-value>127.0.0.1</param-value> 
</init-param> 
</filter> 

<filter-mapping> 
<filter-name>LogFilter</filter-name> 
<url-pattern>/testFilter</url-pattern> 
</filter-mapping> 
複製程式碼

控制檯列印結果如下:

正在執行 LogFilter.doFilter()方法,LogFilter初始化引數ip=127.0.0.1

呼叫LogFilter中的chain.doFilter()之前

正在執行 TestFilter 的 doGet() 方法

呼叫LogFilter chain.doFilter()之後

http響應正文如下:

This msg is from HelloServlet doGet()

This msg is from LogFilter

15.4 串聯過濾器

多個過濾器可以串聯起來協同工作,Servlet 容器將根據它們在 web.xml 中定義的先後順序,一次呼叫它們的doFilter() 方法。假定有兩個過濾器串聯起來,它們的 doFilter() 方法均採用以下結構:

Code1; //表示chain.doFilter()前的程式碼 
chain.doFilter(); 
Code2; //表示chain.doFilter()後的程式碼 
複製程式碼

假定這兩個過濾器都會為同一個 Servlet 預處理客戶請求。當客戶請求訪問這個 Servlet 時,這兩個過濾器及 Servlet 的工作流程如下圖所示。

681945C9-9ACE-4542-8621-5D10C988A858

由於篇幅的問題,這裡就不再展示串聯過濾器的用法了,感興趣的同學可以自行寫個小demo嘗試一下。只需要自己新建一個過濾器,然後早web.xml檔案中新增一些相關配置即可。

16. 合理決定在 Servlet 中定義的變數的作用域型別

在 Java 語言中,區域性變數和例項變數有著不同的作用域,它們的區別如下:區域性變數在一個方法中定義,每個執行緒都擁有自己的區域性變數。例項變數在類中定義。類的每一個例項都擁有自己的例項變數,如果一個例項結束生命週期,那麼屬於它的例項變數也就結束生命週期。如果有多個執行緒同時執行一個例項的方法,而這個方法會訪問一個例項變數,那麼這些執行緒訪問的是同一個例項變數。

最後

覺得對您有幫助,歡迎評論轉發點贊哦!

參考資料:

相關文章