通过源码详解 Servlet
Servlet 結構
1、Servlet
Servlet 該接口定義了5個方法。
- init(),初始化 servlet 對象,完成一些初始化工作。它是由 servlet 容器控制的,該方法只能被調用一次
- service(),接受客戶端請求對象,執行業務操作,利用響應對象響應客戶端請求。
- destroy(),當容器監測到一個servlet從服務中被移除時,容器調用該方法,釋放資源,該方法只能被調用一次。
- getServletConfig(),ServletConfig 是容器向 servlet 傳遞參數的載體。
- getServletInfo(),獲取 servlet 相關信息。
Servlet 的生命周期:
1,初始化階段 調用 init() 方法
2,響應客戶請求階段 調用 service() 方法
3,終止階段 調用 destroy() 方法
在 Servlet 接口中的五個方法中涉及的接口有三個:ServletConfig 、 ServletRequest、 ServletResponse
這里先講講 ServletRequest 和 ServletResponse。
1)ServletRequest 由 Servlet 容器來管理,當客戶請求到來時,容器創建一個 ServletRequest 對象,封裝請求數據,同時創建一個 ServletResponse 對象,封裝響應數據。這兩個對象將被容器作為 service()方法的參數傳遞給 Servlet,Serlvet 利用 ServletRequest 對象獲取客戶端發來的請求數據,利用 ServletResponse 對象發送響應數據。
下面是 ServletRequest 中所有的方法,根據方法名大概就可以猜到這些方法到底是干啥用的。
2)ServletResponse 發送響應數據
2、ServletConfig
ServletConfig 是容器向 servlet 傳遞參數的載體。
ServletConfig的4個常用方法:
1)public String getInitParameter(String name):返回指定名稱的初始化參數值;
2)public Enumeration getInitParameterNames():返回一個包含所有初始化參數名的 Enumeration 對象;
3)public String getServletName():返回在 DD 文件中<servlet-name>元素指定的 Servlet 名稱;
4)public ServletContext getServletContext():返回該 Servlet 所在的上下文對象;
這里詳細講下 ServletContext :
Servlet 上下文對象(ServletContext):每個Web應用程序在被啟動時都會創建一個唯一的上下文對象,Servlet 可通過其獲得 Web 應用程序的初始化參數或 Servlet 容器的版本等信息,也可被 Servlet 用來與其他 Servlet 共享數據。
1、獲得 ServletContext 應用:
(1)、直接調用 getServletContext()方法
ServletContext context = getServletContext();
(2)、使用 ServletConfig 應用,再調用它的 getServletContext()方法
ServletContext context = getServletConfig.getServletContext();
2、獲得應用程序的初始化參數:
(1)、public String getInitParameter(String name):返回指定參數名的字符串參數值,沒有則返回 null;
(2)、public Enumeration getInitParameterNames():返回一個包含多有初始化參數名的 Enumeration 對象;
3、通過 ServletContext 對象獲得資源
(1)、public URl getResource(String path):返回由給定路徑的資源的 URL 對象,以 “/” 開頭,為相對路徑,相對于Web 應用程序的文檔根目錄;
(2)、public InputStream getResourceAsStream(String path):從資源上獲得一個 InputStream 對象,等價于getResource(path).oprenStream();
(3)、public String getRealPath(String path):返回給定的虛擬路徑的真實路徑;
4、登陸日志:使用 log()方法可以將指定的消息寫到服務器的日志文件中
(1)、public void log(String msg):參數 msg 為寫入日志文件消息
(2)、public void log(String msg,Throwable throwable):將 msg 指定的消息和異常的棧跟蹤信息寫入日志文件
5、使用 RequestDispatcher 實現請求轉發
(1)、RequestDispatcher getRequestDiapatcher(String path):必須以 “/“ 開頭相對于應用程序根目錄,而ServletRequest 可以傳遞一個相對路徑
(2)、RequestDipatcher getNamedDiapatcher(String name):參數 name 為一個命名的 Servlet 對象
6、使用 ServletContext 對象存儲數據
(1)、public void serAttribute(String name,Object object):將給定名稱的屬性值對象綁定到上下文對象上;
(2)、public Object getAttribute(String name):返回綁定到上下文對象的給定名稱的屬性值;
(3)、public Enumeration getAttributeNames():返回綁定到上下文對象上的所有屬性名的 Enumeration 對象;
(4)、public void removeAttribute(String name):刪除綁定到上下文對象指定名稱的屬性;
ServletRequest 共享的對象僅在請求的生存周期中可以被訪問;
HttpSession 共享的對象僅在會話的生存周期中可以被訪問;
ServletContext 共享的對象在整個 Web 應用程序啟動的生存周期中可以被訪問;
7、檢索 Servlet 容器的信息
(1)、public String getServletInfo():返回 Servlet 所運行容器的名稱和版本;
(2)、public int getMajorVersion():返回容器所支持的 Servlet API 的主版本號;
(3)、public int getMinorVersion():返回容器所支持的 Servlet API 的次版本號;
(4)、public String getServletContext():返回 ServletContext 對應的 web 應用程序名稱?<display-name>元素定義的名稱;
3、GenericServlet 抽象類
GenericServlet 定義了一個通用的,不依賴具體協議的 Servlet,它實現了 Servlet 接口和 ServletConfig 接口,它的方法在文章的第一張圖就給出了。
4、HttpServlet 抽象類
4.1、HTTP 請求方式
4.2、對應的服務方法:
4.3、SERVLET SERVICE 方法詳解
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; // 如果傳入的 HTTP 請求和 HTTP 響應不是 HTTP 的領域模型,則拋出 Servlet 異常,這個異常會被 Servlet 容器所處理 if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } // 既然是 HTTP 協議綁定的 Serlvet, 強制轉換到 HTTP 的領域模型 request = (HttpServletRequest) req; response = (HttpServletResponse) res; // 如果傳入的請求和響應是預期的 HTTP 請求和 HTTP 響應,則調用 HttpServlet 的 service() 方法。 service(request, response); } |
4.4、HTTPSERVLET 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 | protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 從 HTTP 請求中取得這次請求所使用的 HTTT 方法 String method = req.getMethod(); // 如果這次請求使用 GET 方法 if (method.equals(METHOD_GET)) { // 取得這個 Servlet 的最后修改的時間 long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic //-1 代表這個 Servlet 不支持最后修改操作,直接調用 doGet() 進行處理 HTTP GET 請求 doGet(req, resp); } else { // 如果這個 Servlet 支持最后修改操作,取得請求頭中包含的請求的最后修改時間 long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less // 如果請求頭中包含的修改時間早于這個 Servlet 的最后修改時間,說明這個 Servlet 自從客戶上一次 HTTP 請求已經被修改了 , 設置最新修改時間到響應頭中 maybeSetLastModified(resp, lastModified); // 調用 doGet 進行進行處理 HTTP GET 請求 doGet(req, resp); } else { // 如果請求頭中包含修改時間晚于這個 Servlet 的最后修改時間,說明這個 Servlet 自從請求的最后修改時間后沒有更改過,這種情況下,僅僅返回一個 HTTP 響應狀態 SC_NOT_MODIFIED resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { // 如果這次請求使用 HEAD 方法 // 如果這個 Servlet 支持最后修改操作,則設置這個 Servlet 的最后修改時間到響應頭中 long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); // 和對 HTTP GET 方法處理不同的是,無論請求頭中的修改時間是不是早于這個 Sevlet 的最后修改時間,都會發 HEAD 響應給客戶,因為 HTTP HEAD 響應是用來查詢 Servlet 頭信息的操作 doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 如果這次請求使用 POST 方法 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { // 如果這次請求使用 PUT 方法 doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { // 如果這次請求使用 DELETE 方法 doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { // 如果這次請求使用 OPTIONS 方法 doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { // 如果這次請求使用 TRACE 方法 doTrace(req,resp); } else { // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // 如果這次請求是其他未知方法,返回錯誤代碼 SC_NOT_IMPLEMENTED 給 HTTP 響應,并且顯示一個錯誤消息,說明這個操作是沒有實現的 String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } |
5、Servlet 的多線程問題
1、當涉及到 Servlet 需要共享資源是,需保證 Servlet 是線程安全的
2、注意事項:
(1)、用方法的局部變量保持請求中的專有數據;
(2)、只用 Servlet 的成員變量來存放那些不會改變的數據;
(3)、對可能被請求修改的成員變量同步(用 Synchronized 關鍵字修飾);
(4)、如果 Servlet 訪問外部資源,那么需要同步訪問這些資源;
3、實現 SingleThreadModel 接口的 Servlet 在被多個客戶請求時一個時刻只能有一個線程運行,不推薦使用。
4、如果必須在 servlet 使用同步代碼,應盡量在最小的范圍上(代碼塊)進行同步,同步代碼越少,Servlet 執行才能越好,避免對 doGet() 或 doPost() 方法同步。
總結
全文首先通過一張 Servlet 中的核心 Servlet 類圖關系,了解了幾種 Servlet 之間的關系及其內部方法。然后在分別介紹這幾種 Servlet,通過分析部分重要方法的源碼來了解,還介紹了 Servlet 中多線程的問題的解決方法。
注:文章原創,首發于:zhisheng 的博客,文章可轉載但請注明地址為:http://www.54tianzhisheng.cn/2017/07/09/servlet/
總結
以上是生活随笔為你收集整理的通过源码详解 Servlet的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 线程池艺术探索
- 下一篇: 看透 Spring MVC 源代码分析与