程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> JSP編程 >> 關於JSP >> Servlet框架基礎和生命周期(結合源碼)、destroy()的思考

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

編輯:關於JSP

  前言
                 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

                    方法的。         

[java]  * 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 

* 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
 

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

/**
 *
 * 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中的主要的邏輯處理階段。


[java]   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); 

   } 
    

  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”這種說法本身就是離譜的!查閱源碼:          

[java]   
 /**
     * 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(); 

 
 /**
     * 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配置文件。 

[html] <?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> 

<?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的代碼:


[java]  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方法被執行"); 

 

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服務器

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved