我們到底能走多遠系列(7)
祝大家長假快樂,工作的時候拚命,休息享受的時候也請拚命。
今天不扯淡了,過完假期再來和你們扯,哈哈!
Servlet的核心類圖如下:
上面的servlet只是介面,相當於描述了servlet的標準,也就是說與協議無關。而HttpServlet的實現是根據Http協議來完成的。看下面的:
開啟源碼看後發現大多數介面和抽象類別,那麼真正的實現都在哪呢?在servlet容器原始碼裡,即類似Tomcat這樣的servlet容器。
所以我覺得可以這樣理解Servlet的作用:它提供了操控的按鈕,這些按鈕一旦被安裝到Servlet容器裡面,我們就可以通過這些按鈕操控servlet容器,從而管理我們自己的Servlet執行個體。
這一點很重要。所以閱讀Servlet源碼其實就是瞭解他的基本結構和API,我們真正要讀的是Tomcat的源碼!
Servlet的運行結構和API:
首先看下一次Http要求-回應的過程:
A,首先用戶端通過瀏覽器向伺服器發送Http請求;
B,Tomcat監聽伺服器的8080連接埠,當有Http請求發過來之後,解析出項目名稱,然後到webapps目錄下搜尋到該專案檔夾。
C,Tomcat作為servlet的容器,執行個體化第一次請求調用的的servlet執行個體(以後再有相同的servlet的請求,使用第一次的執行個體)。
D,調用init()方法,初始化工作。
E,調用HttpServlet的service方法,中間會更加請求,調用doGet或doPost方法,執行核心邏輯代碼。
F,執行完servlet後,返迴響應,用戶端瀏覽器根據響應呈現效果。
然後我們看下基本的運行機制:
從圖上可以看出,不同的使用者請求並發使用同一個Servlet執行個體,這是一個比較重要的基礎機制。
Container解析Http請求後,執行個體化Servlet,因為是Http請求,會執行HttpServlet的代碼:
首先會執行HttpServlet中的service(ServletRequest req, ServletResponse res)方法:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req;//請求轉換成Http請求 response = (HttpServletResponse) res;//響應轉換成Http響應 } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response);//調用處理Http的service方法 }
然後會調用service(HttpServletRequest req, HttpServletResponse resp)方法:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod();//取得req中的方法類型 if (method.equals(METHOD_GET)) {//根據方法類型來決定執行後續的方法,我們只關注doGet和doPost long lastModified = getLastModified(req); if (lastModified == -1) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < (lastModified / 1000 * 1000)) { maybeSetLastModified(resp, lastModified); doGet(req, resp);//如此我們就能執行到自己寫的doGet方法,從而執行自己的商務邏輯。 } else { resp.setStatus(HttpServletResponse.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 {//不屬於http的方法類型,返回501錯誤 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); } }
我們自己實現的Servlet,我們只要繼承HttpServlet,實現doGet就ok了:
public class HelloWorld extends HttpServlet{ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("test"); getServletContext().getRequestDispatcher("/jsp/test.html").forward(req, resp); }}
來關注下Servlet運行時需要注意的:
A,Servlet生命週期:
servlet類載入--->執行個體化--->服務--->銷毀
B,其中牽涉到3個方法代表了Servlet的生命週期:
1、init方法:負責初始化Servlet對象。
2、service方法:負責響應客戶的請求。
3、destroy方法:當Servlet對象退出生命週期時,負責釋放佔用的資源。
C,執行自己Servlet時需要用到的幾個重要參數:
1,HttpSession:一次連結到用戶端關閉,從用戶端串連的層面來管理。
2,ServletConfig:從一個servlet被執行個體化後,對任何用戶端在任何時候訪問有效,但僅對本servlet
有效,一個servlet的ServletConfig對象不能被另一個servlet訪問。 從單個servlet的層面來管理。
3,ServletContext:對任何servlet,任何人在任何時間都有效,這才是真正全域的對象。從全部的servlet的層面來管理
4,HttpServletRequest,HttpServletResponse:從單個servlet的單次響應側面來管理
D,API
HttpServletRequest:
View Code
String getAuthType() 如果servlet由一個評鑑方案所保護,如HTTP基本評鑑,則返回方案名稱。 String getContextPath() 返回指定servlet上下文(web應用)的URL的首碼。 Cookie[] getCookies() 返回與請求相關cookie的一個數組。 Long getDateHeader(String name) 將輸出轉換成適合構建Date對象的long類型取值的getHeader()的簡化版。 String getHeader(String name) 返回指定的HTTP頭標指。如果其由請求給出,則名字應為大小寫不敏感。 Enumeration getHeaderNames() 返回請求給出的所有HTTP頭標名稱的權舉值。 Enumeration getHeaders(String name) 返回請求給出的指定類型的所有HTTP頭標的名稱的枚舉值,它對具有多取值的頭標非常有用。 int getIntHeader(String name) 將輸出轉換為int取值的getHeader()的簡化版。 String getMethod() 返回HTTP要求方法(例如GET、POST等等) String getPathInfo() 返回在URL中指定的任意附加路徑資訊。 String getPathTranslated() 返回在URL中指定的任意附加路徑資訊,被子轉換成一個實際路徑。 String getQueryString() 返回查詢字串,即URL中?後面的部份。 String getRemoteUser() 如果使用者通過評鑑,返回遠端使用者名,否則為null。 String getRequestedSessionId() 返回用戶端的會話ID String getRequestURI() 返回URL中一部分,從“/”開始,包括上下文,但不包括任意查詢字串。 String getServletPath() 返回請求URI上下文後的子串 HttpSession getSession() 調用getSession(true)的簡化版。 HttpSession getSession(boolean create) 返回當前HTTP會話,如果不存在,則建立一個新的會話,create參數為true。 Principal getPrincipal() 如果使用者通過評鑑,返回代表目前使用者的java.security.Principal對象,否則為null。 boolean isRequestedSessionIdFromCookie() 如果請求的會話ID由一個Cookie對象提供,則返回true,否則為false。 boolean isRequestedSessionIdFromURL() 如果請求的會話ID在請求URL中解碼,返回true,否則為false boolean isRequestedSessionIdValid() 如果用戶端返回的會話ID仍然有效,則返回true。 Boolean isUserInRole(String role) 如果當前已通過評鑑使用者與指定角色相關,則返回true,如果不是或使用者未通過評鑑,則返回false。
HttpServletResponse:
View Code
void addCookie(Cookie cookie) 將一個Set-Cookie頭標加入到響應。 void addDateHeader(String name,long date) 使用指定日期值加入帶有指定名字(或代換所有此名字頭標)的回應標頭標的方法。 void setHeader(String name,String value) 設定具有指定名字和取值的一個回應標頭標。 void addIntHeader(String name,int value) 使用指定整型值加入帶有指定名字的回應標頭標(或代換此名字的所有頭標)。 boolean containsHeader(String name) 如果響應已包含此名字的頭標,則返回true。 String encodeRedirectURL(String url) 如果用戶端不知道接受cookid,則向URL加入會話ID。第一種形式只對在sendRedirect()中使用的URL進行調用。其他被編碼的 URLs應被傳遞到encodeURL() String encodeURL(String url) void sendError(int status) 設定響應狀態代碼為指定值(可選的狀態資訊)。HttpServleetResponse定義了一個完整的整數常量集合表示有效狀態值。 void sendError(int status,String msg) void setStatus(int status) 設定響應狀態代碼為指定指。只應用於不產生錯誤的響應,而錯誤響應使用sendError()。
ServletContext:
View Code
Object getAttribute(String name) 返回servlet上下文中具有指定名字的對象,或使用已指定名捆綁一個對象。從Web應用的標準觀點看,這樣的對象是全域對象,因為它們可以被同一 servlet在另一時刻訪問。或上下文中任意其他servlet訪問。 void setAttribute(String name,Object obj) 設定servlet上下文中具有指定名字的對象。 Enumeration getAttributeNames() 返回儲存在servlet上下文中所有屬性名稱字的枚舉。 ServletContext getContext(String uripath) 返回映射到另一URL的servlet上下文。在同一伺服器中URL必須是以“/”開頭的絕對路徑。 String getInitParameter(String name) 返回指定上下文範圍的初始化參數值。此方法與ServletConfig方法名稱不一樣,後者只應用於已編碼的指定servlet。此方法應用於上下文中所有的參數。 Enumeration getInitParameterNames() 返回(可能為空白)指定上下文範圍的初始化參數值名字的枚舉值。 int getMajorVersion() 返回此上下文中支援servlet API層級的最大和最小版本號碼。 int getMinorVersion() String getMimeType(String fileName) 返回指定檔案名稱的MIME類型。典型情況是基於副檔名,而不是檔案本身的內容(它可以不必存在)。如果MIME類型未知,可以返回null。 RequestDispatcher getNameDispatcher(String name) 返回具有指定名字或路徑的servlet或JSP的RequestDispatcher。如果不能建立RequestDispatch,返回null。如果指定路徑,必須心“/”開頭,並且是相對於servlet內容相關的頂部。 RequestDispatcher getNameDispatcher(String path) String getRealPath(String path) 給定一個URI,返迴文件系統中URI對應的絕對路徑。如果不能進行映射,返回null。 URL getResource(String path) 返回相對於servlet上下文或讀取URL的輸入資料流的指定絕對路徑相對應的URL,如果資源不存在則返回null。 InputStream getResourceAsStream(String path) String getServerInfo() 返順servlet引擎的名稱和版本號碼。 void log(String message)void log(String message,Throwable t) 將一個訊息寫入servlet註冊,如果給出Throwable參數,則包含棧軌跡。 void removeAttribute(String name) 從servlet上下文中刪除指定屬性。
HttpSession:
View Code
Object getAttribute(String name) 將會話中一個對象儲存為指定名字,返回或刪除前面儲存的此名稱對象。 void setAttribute(String name,Object value) void removeAttribute(String name) Enumeration getAttributeName() 返回捆綁到當前會話的所有屬性名稱的枚舉值。 long getCreationTime() 返回表示會話建立和最後訪問日期和時間的一個長整型,該整型形式為java.util.Date()構造器中使用的形式。 long getLastAccessedTime() String getId() 返回會話ID,servlet引擎設定的一個唯一關鍵字。 ing getMaxInactiveInterval() 如果沒有與用戶端發生互動,設定和返回會話存活的最大秒數。 void setMasInactiveInterval(int seconds) void invalidate() 使得會話被終止,釋放其中任意對象。 boolean isNew() 如果用戶端仍未加入到會話,返回true。當會話首次被建立,會話ID被傳入用戶端,但用戶端仍未進行包含此會話ID的第二次請示時,返回true。
ServletConfig:
View Code
getServletName()方法概述:public java.lang.String getServletName() 該方法返回一個servlet執行個體的名稱,該名稱由伺服器管理員提供。 getServletContext()方法概述:public ServletContext getServletContext() 返回一個ServletContext對象的引用。 getInitParameter()方法概述:public java.lang.String getInitParameter(java.lang.String name) 返回一個由參數String name決定的初始設定變數的值,如果該變數不存在,返回null。 getInitParameterNames()方法概述:public java.util.Enumeration getInitParameterNames() 返回一個儲存所有初始設定變數的枚舉函數。如果servlet沒有初始設定變數,返回一個空枚舉函數。
除去以上的,還有很多內容會在實際編碼中需要學習。
總結:
最終我們還是不得不去閱讀Tomcat的源碼,才能解答更多的疑惑。
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
共勉