Writer????? :BYSocket(泥沙磚瓦漿木匠)
微???????? 博:BYSocket
豆???????? 瓣:BYSocket
FaceBook:BYSocket
Twitter??? :BYSocket
上一篇的《?Servlet必會必知?》受到大家一致好評 — (感謝?讀者 及 OSC 推薦 每日一’搏’)
后來覺得還有些東西沒點到,這邊補充補充。
一、回到 HttpServlet 的 service方法
Servlet 基礎接口定義了用于客戶端請求處理的service方法。 當請求到達Servlet容器,由Servlet容器路由到一個Servlet實例。
比如說?javax.servlet.http.HttpServlet?類 ,其中有一個?protected?void service 方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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、HttpServlet的?protected?void service方法 用于接受?public?service接收的標準HTTP請求。
也就是說,HttpServlet?重寫了父類?GenericServlet?的?service方法。如圖顯示的是該方法,將從容器獲取的?ServletRequest?和?ServletResponse?對象強制轉化成 用于HTTP處理的HttpServletRequest?和?HttpServletResponse?對象。然后將兩個對象路由給了?HttpServlet的?protected?void service方法(圖中代碼選中處)
?
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請求代碼處理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 如果是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。
三、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訂閱哦
———-?http://www.bysocket.com/?————-?https://github.com/JeffLi1993?———-