(三)Servlet就是這樣-Servlet的一些細節

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);

  }

}

請注意查看上面這兩個類的源碼(PS:這兩個類中的大部分方法我只是寫出了它們的方法頭,簡短,方便查看),我們注意到,這兩個類是抽象類,不能直接進行實例化,必須給出子類才能實例化(注意這一塊),它們的類中分別有一個抽象方法,就是service()方法,這個是要我們手動實現的。

l  總得來看,GenericServlet給出了設計 servlet的一些骨架,定義了 servlet 生命週期,還有一些得到名字、配置、初始化參數的方法,其設計的是和應用層協議無關的,也就是說你有可能用非 http 協議實現它(其實目前 Java Servlet 還是隻有 Http 一種)。

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

由於客戶端是通過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對象的功能。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章