前言
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的線程安全問題筆者會找個時間在學習下。