Servlet讀書筆記系列文《Servlet就是這樣》第三篇:Servlet的一些細節
細節一:Servlet簡介
l Servlet是sun公司提供的一門用於開發動態web資源的技術。
l Sun公司在其API中提供了一個servlet接口,用戶若想開發一個動態web資源(即開發一個Java程序向瀏覽器輸出數據),需要完成以下2個步驟:
1) 編寫一個Java類,實現servlet接口。
2) 把開發好的Java類部署到web服務器中。
細節二:Servlet的運行過程
l Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet訪問請求後:
1) Web服務器首先檢查是否已經裝載並創建了該Servlet的實例對象。如果是,則直接執行第④步,否則,執行第②步。
2) 裝載並創建該Servlet的一個實例對象。
3) 調用Servlet實例對象的init()方法。
4) 創建一個用於封裝HTTP請求消息的HttpServletRequest對象和一個代表HTTP響應消息的HttpServletResponse對象,然後調用Servlet的service()方法並將請求和響應對象作爲參數傳遞進去。
5) WEB應用程序被停止或重新啓動之前,Servlet引擎將卸載Servlet,並在卸載之前調用Servlet的destroy()方法。
細節三:Servlet的生命週期
l 生命週期的概念:一件事物,什麼時候生,什麼時候死,以及在其生存階段的某一時點會觸發的事件,統稱爲該事物的生命週期。
l Servlet的生命週期:
1. 通常情況下,服務器會在Servlet第一次被調用時創建該Servlet類的實例對象(servlet出生);一旦被創建出來,該Servlet實例就會駐留在內存中,爲後續請求服務;直至web容器退出,servlet實例對象纔會被銷燬(servlet死亡)。
2. 在Servlet的整個生命週期內,Servlet的init方法只有在servlet被創建時被調用一次。
3. 而對一個Servlet的每次訪問請求都導致Servlet引擎調用一次servlet的service方法。對於每次訪問請求,Servlet引擎都會創建一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,然後將這兩個對象作爲參數傳遞給它調用的Servlet的service()方法,service方法再根據請求方式分別調用doXXX方法。
4. servlet被銷燬前,會調用destroy()方法。
細節四:Servlet接口實現類
l 當編寫一個servlet時,必須直接或間接實現servlet接口,Servlet接口SUN公司定義了兩個默認實現類,分別爲:GenericServlet、HttpServlet。要實現javax.servlet.Servlet接口(即寫自己的Servlet應用),你可以寫一個繼承自javax.servlet.GenericServletr的Generic Servlet ,也可以寫一個繼承自java.servlet.http.HttpServlet的HTTP Servlet(這就是爲什麼我們自定義的Servlet通常是extentdsHttpServlet的)
l 查看Servlet的官方API,Servlet有5個要實現的方法,如下:
1) init(servletconfig config)
2) service(servletrequest req,servletresponse resp)
3) destroy()
4) getservletconfig()
5) getservletinfo()
l 我們查看下ServletAPI,其中是這樣描述 GenericServlet 的:
GenericServlet 定義了一個通用的,無關協議的的Servlet 。如果要在 Web 應用中使用 Http 進行 Servlet 通信,請擴展 HttpServlet (即繼承 HttpServlet )
l 我們再查看下GenericServlet、HttpServlet它們的源碼:
GenericServlet
public abstractclass GenericServlet implements Servlet,ServletConfig, Serializable {
……
……
public void destroy(){}
public String getInitParameter(String name){}
public Enumeration getInitParameterNames(){}
public ServletConfig getServletConfig(){}
public ServletContext getServletContext(){}
public String getServletInfo(){}
public void init(ServletConfig config){}
public void init(){}
public void log(String msg){}
public void log(String message, Throwable t){}
public String getServletName(){}
public abstractvoid service(ServletRequest paramServletRequest,ServletResponse paramServletResponse) throws ServletException, IOException;
}
HttpServlet
public abstract class HttpServlet extendsGenericServlet implements Serializable {
……
……
protected long getLastModified(HttpServletRequestreq) {
protected void doHead(HttpServletRequest req,HttpServletResponse resp)
protected void doPost(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doGet(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doPut(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doDelete(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
private static Method[] getAllDeclaredMethods(Classc) {
protected void doOptions(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doTrace(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protectedvoid service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Stringmethod = req.getMethod();
if (method.equals("GET")) {
long lastModified = getLastModified(req);
if (lastModified == -1L) {
doGet(req, resp);
} else {
long ifModifiedSince =req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified /1000L * 1000L) {
maybeSetLastModified(resp,lastModified);
doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")){
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals("POST")){
doPost(req, resp);
} else if (method.equals("PUT")){
doPut(req, resp);
} else if (method.equals("DELETE")){
doDelete(req, resp);
} else if(method.equals("OPTIONS")) {
doOptions(req, resp);
} else if(method.equals("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);
resp.sendError(501, errMsg);
}
}
private void maybeSetLastModified(HttpServletResponseresp,
long lastModified) {
public void service(ServletRequest req,ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try{
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
}catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
}
l 請注意查看上面這兩個類的源碼(PS:這兩個類中的大部分方法我只是寫出了它們的方法頭,簡短,方便查看),我們注意到,這兩個類是抽象類,不能直接進行實例化,必須給出子類才能實例化(注意這一塊),它們的類中分別有一個抽象方法,就是service()方法,這個是要我們手動實現的。
l 總得來看,GenericServlet給出了設計 servlet的一些骨架,定義了 servlet 生命週期,還有一些得到名字、配置、初始化參數的方法,其設計的是和應用層協議無關的,也就是說你有可能用非 http 協議實現它(其實目前 Java Servlet 還是隻有 Http 一種)。
l HttpServlet 是採用 Http 協議進行通信的,所以它也實現 Http 協議中的多種方法,HttpServlet 的 service() 方法比較特殊,帶 public 關鍵字的 service() 方法明顯是繼承自父類,它只接收 HTTP 請求,這裏把相應的 request 和 response 轉換爲了基於 HTTP 協議的相應對象,最終將請求轉到帶 protected 關鍵字的 service() 方法中。protected service() 方法根據請求的類型將請求轉發到相應的 doDelete() 、 doGet() 、 doOptions() 、 doPost() 、 doPut() 等方法中。 所以開發自己的 Servlet 時,不需要覆蓋HttpServlet 的 service() 方法,因爲該方法最終將請求轉發相相應的 doXXX 方法中,只需要覆蓋相應的 doXXX 方法進行請求處理即可。如果重寫了該方法,那麼就不會根據方法名調用其他具體的方法了。
細節五:Servlet映射URL
l 由於客戶端是通過URL地址訪問web服務器中的資源,所以Servlet程序若想被外界訪問,必須把servlet程序映射到一個URL地址上,這個工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
l <servlet>元素用於註冊Servlet,它包含有兩個主要的子元素:<servlet-name>和<servlet-class>,分別用於設置Servlet的註冊名稱和Servlet的完整類名。
l 一個<servlet-mapping>元素用於映射一個已註冊的Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name>和<url-pattern>,分別用於指定Servlet的註冊名稱和Servlet的對外訪問路徑。
細節六:Servlet的調用和生命週期
l Servlet是一個供其他Java程序(Servlet引擎)調用的Java類,它不能獨立運行,它的運行完全由Servlet引擎來控制和調度。
l 針對客戶端的多次Servlet請求,通常情況下,服務器只會創建一個Servlet實例對象,也就是說Servlet實例對象一旦創建,它就會駐留在內存中,爲後續的其它請求服務,直至web容器退出,servlet實例對象纔會銷燬。
l 在Servlet的整個生命週期內,Servlet的init方法只被調用一次。而對一個Servlet的每次訪問請求都導致Servlet引擎調用一次servlet的service方法。對於每次訪問請求,Servlet引擎都會創建一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,然後將這兩個對象作爲參數傳遞給它調用的Servlet的service()方法,service方法再根據請求方式分別調用doXXX方法。
細節七:Servlet的默認映射
l 如果某個Servlet的映射路徑僅僅爲一個正斜槓(/),那麼這個Servlet就成爲當前Web應用程序的缺省Servlet。
l 凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給缺省Servlet處理,也就是說,缺省Servlet用於處理所有其他Servlet都不處理的訪問請求。
l 在<tomcat的安裝目錄>\conf\web.xml文件中,註冊了一個名稱爲org.apache.catalina.servlets.DefaultServlet的Servlet,並將這個Servlet設置爲了缺省Servlet。
l 當訪問Tomcat服務器中的某個靜態HTML文件和圖片時,實際上是在訪問這個缺省Servlet。
細節八:線程安全
l 當多個客戶端併發訪問同一個Servlet時,web服務器會爲每一個客戶端的訪問請求創建一個線程,並在這個線程上調用Servlet的service方法,因此service方法內如果訪問了同一個資源的話,就有可能引發線程安全問題。
l 如果某個Servlet實現了SingleThreadModel接口,那麼Servlet引擎將以單線程模式來調用其service方法。
l SingleThreadModel接口中沒有定義任何方法,只要在Servlet類的定義中增加實現SingleThreadModel接口的聲明即可。
l 對於實現了SingleThreadModel接口的Servlet,Servlet引擎仍然支持對該Servlet的多線程併發訪問,其採用的方式是產生多個Servlet實例對象,併發的每個線程分別調用一個獨立的Servlet實例對象。
l 實現SingleThreadModel接口並不能真正解決Servlet的線程安全問題,因爲Servlet引擎會創建多個Servlet實例對象,而真正意義上解決多線程安全問題是指一個Servlet實例對象被多個線程同時調用的問題。事實上,在Servlet API 2.4中,已經將SingleThreadModel標記爲Deprecated(過時的)。
細節九:ServletContext
l WEB容器在啓動時,它會爲每個WEB應用程序都創建一個對應的ServletContext對象,它代表當前web應用。
l ServletConfig對象中維護了ServletContext對象的引用,開發人員在編寫servlet時,可以通過ServletConfig.getServletContext方法獲得ServletContext對象。
l 由於一個WEB應用中的所有Servlet共享同一個ServletContext對象,因此Servlet對象之間可以通過ServletContext對象來實現通訊。ServletContext對象通常也被稱之爲context域對象。
l 查看ServletContextAPI文檔,瞭解ServletContext對象的功能。