1. 程式人生 > >圖解 & 深入淺出 JavaWeb:Servlet 再說幾句

圖解 & 深入淺出 JavaWeb:Servlet 再說幾句

Writer      :李強強(泥沙磚瓦漿木匠)原文連結 傳送門

上一篇的《 Servlet必會必知 》受到大家一致好評 — (感謝 讀者 及 OSC 推薦 每日一’搏’),後來覺得還有些東西沒點到,這邊補充補充。image

一、回到 HttpServlet 的 service方法

Servlet 基礎介面定義了用於客戶端請求處理的service方法。 當請求到達Servlet容器,由Servlet容器路由到一個Servlet例項

比如說 javax.servlet.http.HttpServlet 類 ,其中有一個 protected void service 方法如下:

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 LSTRING_FILE =
        "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);
/**
 * HTTP狀態碼304
 */
public static final int SC_NOT_MODIFIED = 304;

/**
 * 接收來自 public service方法的標準HTTP請求,
 * 並將它們分發給此類中定義的doXXX方法。
 */
protected void service(HttpServletRequest req, HttpServletResponse resp) 
		throws ServletException, IOException {
	// 獲取請求方法名
	String method = req.getMethod();
	// 如果是GET請求
	if (method.equals(METHOD_GET)) {
		// 上一次修改HttpServletRequest物件的時間
		long lastModified = getLastModified(req);
		// 沒有改變
		if (lastModified == -1) {
			doGet(req, resp);
		} else {
			long ifModifiedSince;
			try {
				// 獲取請求頭中伺服器修改時間
				ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
			} catch (IllegalArgumentException iae) {
				// 獲取無效
				ifModifiedSince = -1;
			}
			// 如果請求頭伺服器修改時間遲
			if (ifModifiedSince < (lastModified / 1000 * 1000)) {
				// 設定修改HttpServletResponse物件的時間,重新設定瀏覽器的引數
                              //maybeSetLastModified(resp, lastModified);
				// 呼叫doGet方法
                                doGet(req, resp);
			} else {
				// 304 HTTP狀態碼
				resp.setStatus(SC_NOT_MODIFIED);
			}
		}
	} else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
      //maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_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);
        // 501 HTTP狀態碼
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

程式碼邏輯詳解如下:

1、HttpServletprotected void service方法 用於接受 public service接收的標準HTTP請求

也就是說,HttpServlet 重寫了父類 GenericServletservice方法。如圖顯示的是該方法,將從容器獲取的 ServletRequestServletResponse 物件強制轉化成 用於HTTP處理的 HttpServletRequestHttpServletResponse 物件。然後將兩個物件路由給了 HttpServletprotected void service方法(圖中程式碼選中處)

image

2、然後根據請求的方法名,分發到此類定義的doXXX方法。如果沒有被請求到的話,則返回501 HTTP 狀態碼。

這樣子彷彿明白了什麼,也就是說,如果你在 HelloServlet重寫doGet方法,這裡分發到就是HttpServlet的子類HelloServlet的doGet方法。

哦~ 還有,501 HTTP 狀態碼未實現(Not implemented)表示伺服器不支援實現請求所需要的功能。例如,客戶發出了一個伺服器不支援的PUT請求。原來如此,所謂死記硬背這些HTTP 狀態碼有什麼用?這樣的記憶才是最有效的。

休息休息,小廣告插一下 :(維持生計,O(∩_∩)O~)

涉及到的程式碼

都會在開源專案 servlet-core-learning簡介 — Servlet/JSP學習積累的例子,是Java EE初學者及Servlet/JSP核心技術鞏固的最佳實踐

大致就是這兩步驟。這就是service的工作流程

1、接受 public service接收的標準HTTP請求。

2、分發到定義的doXXX方法

二、GET 請求的處理詳解

上面對於GET請求程式碼處理如下:

// 如果是GET請求
if (method.equals(METHOD_GET)) {
	// 上一次修改HttpServletRequest物件的時間
	long lastModified = getLastModified(req);
	// 沒有改變
	if (lastModified == -1) {
		doGet(req, resp);
	} else {
		long ifModifiedSince;
		try {
			// 獲取請求頭中伺服器修改時間
			ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
		} catch (IllegalArgumentException iae) {
			// 獲取無效
			ifModifiedSince = -1;
		}
		// 如果請求頭伺服器修改時間遲
		if (ifModifiedSince < (lastModified / 1000 * 1000)) {
			// 設定修改HttpServletResponse物件的時間,重新設定瀏覽器的引數
          //maybeSetLastModified(resp, lastModified);
			// 呼叫doGet方法
            doGet(req, resp);
		} else {
			// 304 HTTP狀態碼
			resp.setStatus(SC_NOT_MODIFIED);
		}
	}
}

這裡,

1、定義了 getLastModified(req) 方法。用於獲取上一次修改HttpServletRequest物件的時間。如果lastModified為預設–1L,則總是重新整理

這個getLastModified,是HttpServlet定義了用於支援有條件GET操作。即當客戶端通過GET請求獲取資源時,當資源自第一次獲取那個實際點發生更改後才再次發生資料,否則將使用客戶端快取的資料。

在一些適當的場合,實現此方法可以更有效的利用網路資源,減少不必要的資料傳送。

2、如果getLastModified方法的返回值是一個正數,那就要分以下兩種情況考慮:

    (1)如果請求頭沒有包含If-Modified-Since頭欄位(應該是第一次訪問資源時候) 或者 其getLastModified返回值比If-Modified-Since頭欄位指定時間,則呼叫doGet返回生成 response設定Last-Modified 訊息頭

    (2)如果其getLastModified返回值比If-Modified-Since頭欄位指定時間,則返回一個304狀態給客戶端,表示讓客戶端繼續使用以前快取的頁面

比如說 304 這個場景我在《 JavaEE 要懂的小事:一、圖解Http協議 》文章中提到,第一次訪問 百度 首頁時,有些資源會成功獲取 返回200再次F5,有些資源或直接呼叫客戶端的快取資料,則返回304

image1_thumb

三、Servlet執行緒問題

Servlet容器可以併發路由多個請求到 Servlet 的 service方法。為了處理這些請求,Servlet必須在併發及執行緒安全問題做好處理。上一篇的《 Servlet必會必知 》提到定義全域性變數會造成執行緒安全問題。在開發Servlet時,考慮執行緒安全問題提出了一下解決

1、實現 SingleThreadModel 介面

Servlet2.4 已經提出不提倡使用。實現此介面,Servlet容器為每個新的請求建立一個單獨的Servlet例項。這會有嚴重效能問題

2、同步鎖

使用synchronized關鍵字,雖然可以保證只有一個執行緒可以訪問被保護區段,已達到保證執行緒安全。但是系統性能及併發量大大降低。不可取~

3、避免使用例項變數,即Servlet中全域性變數。使用區域性變數 (推薦)

方法中的區域性變數分配在空間,每個執行緒有私有的棧空間。因此訪問是執行緒安全的。

我想到了以下一個問題:

既然Sevlet的全域性變數是執行緒不安全的,那SpringMVC Controller 也一樣。那我們在Controller定義個 XXXService 變數會不會造成執行緒安全呢?
答:因為這是Spring的一個Service Bean,是執行緒安全的,所以可以作為單例使用,不會造成執行緒安全。

四、總結(別忘了點贊哦)

補充文章內容要點:

HttpServlet service 方法詳解

深入理解 程式碼 對HTTP狀態碼的運用

Servlet的執行緒安全問題

歡迎點選我的部落格及GitHub — 部落格提供RSS訂閱哦