Servlet框架基礎和生命週期(結合源碼)、destroy()的思考

         前言

                 Servlet是一個java編寫的程序,此程序是在服務器端運行的,是按照Servlet規範編寫的一個

             java類。Servlet是處理客戶端的請求,並將處理結果以響應的方式返回給客戶端。Servlet框架

             是怎樣的呢?它的生命週期又是什麼情況呢?這是本文需要探求的。

         Servlet框架

                 網上下載Servlet源碼,解壓之後發現其由兩個包組成:

                         1、javax.servlet

                         2、javax.servlet.http

            javax.servlet

                  此包中定義了所有Servlet類都必須實現的接口或類。

                  接口定義:

                  ServletConfig接口---在初始化過程中由Servlet容器(Tomcat調用)

                  ServletContext接口---定義Servlet用於獲取容器信息的方法

                  ServletRequest接口---向服務器請求信息

                  ServletResponse接口 ---響應客戶端請求

                  Servlet接口---定義所有的Servlet必須實現的方法

                  類定義:

                  ServletInputStream類 --- 用於從客戶端讀取二進制數據

                  ServletOutputStream類 ---用於將二進制數據寫入到客戶端

                  GenricServlet--- 抽象類,定義一個通用的,獨立於底層協議的servlet。

            java.servlet.http

                  此包中定義了使用HTTP通信協議的所有Servlet類應該實現的類、接口。

                  接口定義:

                   HttpServletRequest接口 --- 封裝http請求

                   HttpServletResponse接口 --- 封裝http響應

                   HttpSession接口 --- 用於表示客戶端存儲有關客戶的信息

                   HttpSessionAttributeListener接口---實現這個監聽接口,當用戶獲取Session的屬性列表發生

                                                             改變的時候得到通知。

                   類的定義:

                   HttpServlet類 --- 擴展了GenericServlet的抽象類

                   Cookie類 --- 創建一個Cookie,Cookie技術,用戶存儲服務器發送給客戶端的信息。

               通過閱讀Servlet框架源碼,其主要的框架結構如下圖:

              

             Servlet工作過程

                        通過上述Servlet框架的瞭解我們可以初步描述一下Servlet在Tomcat容器中是如何工作的。

                   來看下面的時序圖:

                

                    1、Web Client 向Servlet容器(Tomcat)發出Http請求

                    2、Servlet容器接收Web Client的請求


                    3、Servlet容器創建一個HttpRequest對象,將Web Client請求的信息封裝到這個對象中

                    4、Servlet容器創建一個HttpResponse對象

                    5、Servlet容器調用HttpServlet對象的service方法,把HttpRequest對象與HttpResponse

                        對象作爲參數傳給 HttpServlet對象

                   6、HttpServlet調用HttpRequest對象的有關方法,獲取Http請求信息

                   7、HttpServlet調用HttpResponse對象的有關方法,生成響應數據

                   8、Servlet容器把HttpServlet的響應結果傳給Web Client

                Tomcat和HttpServlet是如何進行交互的呢?從源碼中我們可以得到

                

            Servlet生命週期

                       在Servlet框架中所有的Servlet類都必須實現Servlet這個接口。其中定義了三個方法:

                            1、init方法:負責初始化Servlet對象。

                            2、service方法:用於響應客戶端的請求

                            3、destroy:銷燬Servlet對象,釋放佔用的資源。

                       Servlet生命週期四個階段:

                             ●  加載階段:加載並實例化(創建Servlet實例)

                             ●  初始化階段:調用init()方法

                             ●  響應客戶請求階段:調用service()方法,doGet、doPost

                             ●  終止階段:調用destroy()方法

                           

                  加載階段

                       Tomcat從文件系統,遠程文件系統或其他網絡服務中通過類加載器來加載Servlet,並調用

                   Servlet的默認構造方法(不帶參構造器)

                  初始化階段init()方法

                       當Servlet容器啓動時:讀取web.xml配置文件中的信息,構造指定的Servlet對象,根據配置

                    文件的信息創建ServletConfig對象,並將其作爲參數傳遞給init方法進行調用。

                       Tomcat啓動後:用戶首次想某個Servlet對象發送請求,Tomcat會判斷內存中是否存在指定的

                     servlet對象,如果沒有則會去創建它,然後創建HttpRequest,HttpResponse對象,調用service

                    方法處理用戶的請求。

                       從Servlet的構造開始我們沒有顯示的看到init()方法的調用,那麼init方法到底是何時進行調用

                    的呢?閱讀源碼可以知道:init方法是在實例化Servlet之後調用的,其參數ServletConfig是在

                    Servlet初始化階段Tomcat根據web.xml配置信息,和操作系統的相關環境生成並傳遞給init

                    方法的。         

* Called by the servlet container to indicate to a servlet that the       * servlet is being placed into service.      *      * <p>The servlet container calls the <code>init</code>      * method exactly once after instantiating the servlet.      * The <code>init</code> method must complete successfully      * before the servlet can receive any requests.      *      * <p>The servlet container cannot place the servlet into service      * if the <code>init</code> method      * <ol>      * <li>Throws a <code>ServletException</code>      * <li>Does not return within a time period defined by the Web server

 

/**  *   * A servlet configuration object used by a servlet container  * to pass information to a servlet during initialization.   *  */   public interface ServletConfig {

               響應客戶請求階段service方法

                     service()方法是在客戶端第一次訪問servlet時執行的,其實init方法同樣也是在有客戶端訪問

                 servlet的時候才被調用。不過需要特別注意的是討論init方法在session級別上時,當存在不同的

                 會話訪問相同的servlet時,Tomcat會開啓一個線程處理這個新的會話,但是此時Tomcat容器

                 不會實例化這個servlet對象,也就是有多個線程在共享這個servlet實例。換句話說Servlet對象在

                 servlet容器中是以單例的形式存在的!然而查看其源碼可以發現,Servlet在多線程下並未使用同

                 步機制,因此,在併發編程下servlet是線程不安全的

                       對於Servlet的併發,線程安全的處理問題,筆者會找個時間好好的整理下思路。

                   對於不同的session訪問相同的serlvet對象,只有一次init的過程,筆者會在接下來予以演示。

                     閱讀HttpServlet的源碼可以知道,基於Http通信協議的HttpServlet在進行客戶端響應處理的

                 時候根據客戶端請求,響應的類別不同分別調用不同的方法,其中最常用的就是doGet、doPost

                 方法,這兩個方法是我們在編寫Servlet中的主要的邏輯處理階段。

  protected void service(HttpServletRequest req, HttpServletResponse resp) 	throws ServletException, IOException     { 	String method = req.getMethod();  	if (method.equals(METHOD_GET)) { 	    long lastModified = getLastModified(req); 	    if (lastModified == -1) { 		// servlet doesn't support if-modified-since, no reason 		// to go through further expensive logic 		doGet(req, resp); 	    } else { 		long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); 		if (ifModifiedSince < (lastModified / 1000 * 1000)) { 		    // 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 		    maybeSetLastModified(resp, lastModified); 		    doGet(req, resp); 		} 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 { 	    // 	    // Note that this means NO servlet supports whatever 	    // method was requested, anywhere on this server. 	    //  	    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); 	}     }     

              終止階段:destroy()方法的調用

                   上面的探討中知道的了Servlet是如何加載、初始化、處理客戶端的請求響應的,那麼Servlet

               在什麼時候終止呢?其生命週期又是在什麼時候結束的呢?

                   我們知道的是Servlet生命週期是有Tomcat容器來管理的,由此在Tomcat關閉、或者Restart

               的時候,servlet的生命週期必然結束,destroy方法也必然被調用過。在客戶端與服務器的一次

               Session會話中,session關閉之後servlet並未銷燬。後續演示。

                  總的來說servlet對象什麼時候destroy的呢?

                      1、Tomcat服務器stop

                      2、web項目reload

                      3、Tomcat容器所在的服務器shutdown(這不廢話嗎?)

            更正之處:對於destroy()方法筆者略有疑惑,“它到底是如何銷燬Servlet的呢?”,基於這個問題

                 特意的去查看了源碼,結果發現destroy()方法在Servlet框架中並未具體去實現。它是由Coder

             自己去實現的。因此“destroy()方法用戶銷燬Servlet”這種說法本身就是離譜的!查閱源碼:          

    /**       * Called by the servlet container to indicate to a servlet that the       * servlet is being taken out of service.  See {@link Servlet#destroy}.       *       *        */     /**      *      * Called by the servlet container to indicate to a servlet that the      * servlet is being taken out of service.  This method is      * only called once all threads within the servlet's      * <code>service</code> method have exited or after a timeout      * period has passed. After the servlet container calls this       * method, it will not call the <code>service</code> method again      * on this servlet.      *      * <p>This method gives the servlet an opportunity       * to clean up any resources that are being held (for example, memory,      * file handles, threads) and make sure that any persistent state is      * synchronized with the servlet's current state in memory.      *      */      public void destroy(); }
          
              閱讀上述註釋,很明白的是destroy的調用是表明Servlet結束其servcie階段,destroy方法的調用

              實際是在servlet銷燬之前,由Tomcat來調用的,其作用是清理一些資源的佔用情況,例如文件、

              線程,而且確保任何持久的狀態和servlet的當前狀態在內存中是同步的。

                   不過destroy的調用情況上述的總結是正確的。

                  也就是說Servlet的銷燬時destroy()一定會被掉用,servlet方法基本是由Tomcat回調的!

              但是destroy()方法的調用只是回收一些資源,並不意味着Servlet已經銷燬。至於何時銷燬,這個

              筆者也不太明瞭,希望有人可以指出,不過據源碼是Servlet結束servcie服務時銷燬,Tomcat關閉

              時也會銷燬。(皮之不存毛將安附焉?)

                    簡單的測試下:

                      我們在doPost()方法裏面簡單的調用下destory方法,run項目,之後另起一個Session訪問

             輸出情況:

                  

              這就說明了destroy()和Servlet的銷燬不存在必然聯繫,只是在Servlet銷燬之前,destroy方法,會

               基由Tomcat回調,進行一些資源的清理,文件關閉。

          Servlet生命週期演示

                 爲了更進一步的瞭解Servlet生命週期(它確實十分重要),筆者新建一個簡單的web項目予以說明

            不當之處請指正,一起交流。

                Eclipse配合Tomcat如何新建一個web項目,這個筆者就不必多說了吧,挺簡單的,網上的總結

            各式各樣的也不少。不過需要注意的是高版本的Eclipse若果讀者不注意的話產生的web項目是沒有

             web.xml文件的,以註解的形式代替了。

             

            runtime選擇自己配置好的Tomcat容器,web Module version選擇2.5就可以了,不要選擇3.0

            這裏筆者貼出項目中的一些歌主要的文件。

                  web.xml配置文件。  

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">   <display-name>Servlet02</display-name>   <welcome-file-list>     <welcome-file>index.html</welcome-file>     <welcome-file>index.htm</welcome-file>     <welcome-file>index.jsp</welcome-file>     <welcome-file>default.html</welcome-file>     <welcome-file>default.htm</welcome-file>     <welcome-file>default.jsp</welcome-file>   </welcome-file-list>   <servlet>     <description></description>     <display-name>HelloServlet</display-name>     <servlet-name>HelloServlet</servlet-name>     <servlet-class>com.kiritor.servlet.HelloServlet</servlet-class>     <init-param>       <description></description>       <param-name>info</param-name>       <param-value>this is a init message</param-value>     </init-param>   </servlet>   <servlet-mapping>     <servlet-name>HelloServlet</servlet-name>     <url-pattern>/HelloServlet</url-pattern>   </servlet-mapping> </web-app>
             注意servlet映射的配置,以及初始化參數的配置。

           自定義Servlet的代碼:

package com.kiritor.servlet; import java.io.IOException; import java.io.PrintWriter;  import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;   public class HelloServlet extends HttpServlet { 	private static final long serialVersionUID = 1L;      /**      * Default constructor.       */     public HelloServlet() {       super();     }      @Override     public void init(ServletConfig config) throws ServletException {     	// TODO Auto-generated method stub     	super.init(config);     	System.out.println("init方法被執行");     	System.out.println("相關的初始化參數:");     	System.out.println(config.getInitParameter("info"));     } 	/** 	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 	 */ 	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 	  this.doPost(request, response); 	}  	/** 	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 	 */ 	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 	   PrintWriter printWriter = response.getWriter(); 	   printWriter.write("Hello world"); 	}   @Override   protected void service(HttpServletRequest arg0, HttpServletResponse arg1)  		throws ServletException, IOException { 	  super.service(arg0, arg1); 	  System.out.println("service方法被執行");  }   @Override public void destroy() { 	super.destroy(); 	System.out.println("destroy方法被執行"); }  } 
                  接下來我們直接run該項目。看看後臺輸出結果,現在貌似可以直接在Eclispe控制檯查看

             輸出信息了,十分方便。

               

                 init方法是在servlet實例化後由Tomcat容器進行調用的,生成了諸多信息,其中包含我們自己

            定義的Servlet配置信息,在init方法中我們通過ServletConfig對象獲取到了。

                 之後service方法執行了。這裏新開啓一個session會話,看看情況。圖我就不貼了,控制檯

            多輸出一句service被執行。證明了servlet在容器中實例單例的形式存在。源碼層面上Servlet不是

            單例的,只是由於容器對其的維護,使之產生了類似單例的效果。

                 接下來我們看destroy方法的調用情況,只是針對上述兩種情況來說的,不可能筆者還去關機‘

            演示。首先我們關閉Tomcat服務器

               

               之後再Tomcat主頁webapp管理上演示下reload,由於我們需要對Tomcat進行管理,我們將項目

           打成war,單獨運行Tomcat併發布war進行測試。

             

             

             點擊reload查看後臺輸出結果

           

                  對Servlet生命週期的問題就總結到這裏了,筆者上述中有什麼問題或者錯誤的地方希望讀者給予

            指正,大家多多交流。

                  對於Servlet的線程安全問題筆者會找個時間在學習下。


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