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

Writer      :BYSocket(泥沙磚瓦漿木匠)java

微         博:BYSocketgit

豆         瓣:BYSocketgithub

FaceBook:BYSocketweb

Twitter    :BYSocket瀏覽器

上一篇的《 Servlet必會必知 》受到你們一致好評 — (感謝 讀者 及 OSC 推薦 每日一’搏’)image緩存

後來以爲還有些東西沒點到,這邊補充補充。
安全

1、回到 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);
    }
}

 

代碼邏輯詳解以下:併發

一、HttpServlet的 protected void service方法 用於接受 public service接收的標準HTTP請求

也就是說,HttpServlet 重寫了父類 GenericServlet 的 service方法。如圖顯示的是該方法,將從容器獲取的 ServletRequest 和 ServletResponse 對象強制轉化成 用於HTTP處理的HttpServletRequest 和 HttpServletResponse 對象。而後將兩個對象路由給了 HttpServlet的 protected void service方法(圖中代碼選中處)

image

二、而後根據請求的方法名,分發到此類定義的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的工做流程

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

二、分發到定義的doXXX方法

2、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);
        }
    }
}

這裏,

一、定義了 getLastModified(req) 方法。用於獲取上一次修改HttpServletRequest對象的時間。若是lastModified爲默認的 –1L,則老是刷新

這個getLastModified,是HttpServlet定義了用於支持有條件GET操做。即當客戶端經過GET請求獲取資源時,當資源自第一次獲取那個實際點發生更改後纔再次發生數據,不然將使用客戶端緩存的數據。

在一些適當的場合,實現此方法能夠更有效的利用網絡資源,減小沒必要要的數據發送。

二、若是getLastModified方法的返回值是一個正數,那就要分如下兩種狀況考慮:

    (1)若是請求頭沒有包含If-Modified-Since頭字段(應該是第一次訪問資源時候) 或者 其getLastModified返回值比If-Modified-Since頭字段指定時間,則調用doGet返回生成response 和 設置Last-Modified 消息頭

    (2)若是其getLastModified返回值比If-Modified-Since頭字段指定時間,則返回一個304狀態給客戶端,表示讓客戶端繼續使用之前緩存的頁面

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

image1_thumb

3、Servlet線程問題

Servlet容器能夠併發路由多個請求到 Servlet 的 service方法。爲了處理這些請求,Servlet必須在併發及線程安全問題作好處理。上一篇的《 Servlet必會必知 》提到定義全局變量會形成線程安全問題。在開發Servlet時,考慮線程安全問題提出了一下解決

一、實現 SingleThreadModel 接口

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

二、同步鎖

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

三、避免使用實例變量,即Servlet中全局變量。使用局部變量 (推薦)

    方法中的局部變量分配在空間,每一個線程有私有的棧空間。所以訪問是線程安全的。

我想到了如下一個問題:

既然Sevlet的全局變量是線程不安全的,那SpringMVC Controller 也同樣。那咱們在Controller定義個 XXXService 變量會不會形成線程安全呢?
答:由於這是Spring的一個Service Bean,是線程安全的,因此能夠做爲單例使用,不會形成線程安全。

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

補充文章內容要點:

HttpServlet service 方法詳解

深刻理解 代碼 對HTTP狀態碼的運用

Servlet的線程安全問題

歡迎點擊個人博客及GitHub — 博客提供RSS訂閱哦

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

相關文章
相關標籤/搜索