首先我們先來看一下servlet家族圖譜,方便分析。
Servlet API的核心就是javax.servlet.Servlet接口,所有的Servlet 類(抽象的或者自己寫的)都必須實現這個接口。在Servlet接口中定義了5個方法,其中有3個方法是由Servlet 容器在Servlet的生命週期的不同階段來調用的特定方法。
先看javax.servlet.servlet接口源碼:
Java代碼
package javax.servlet; //Tomcat源碼版本:6.0.20
import java.io.IOException;
public interface Servlet {
//負責初始化Servlet對象。容器一旦創建好Servlet對象後,就調用此方法來初始化Servlet對象
public void init(ServletConfig config) throws ServletException;
//方法負責響應客戶的請求,提供服務。當容器接收到客戶端要求訪問特定的servlet請求時,就會調用Servlet的service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
/*
Destroy()方法負責釋放Servlet 對象佔用的資源,當servlet對象結束生命週期時,servlet容器調用此方法來銷燬servlet對象.
**/
public void destroy();
/*
說明:Init(),service(),destroy() 這三個方法是Servlet生命週期中的最重要的三個方法。
**/
/*
返回一個字符串,在該字符串中包含servlet的創建者,版本和版權等信息
**/
public String getServletInfo();
/*
GetServletConfig: 返回一個ServletConfig對象,該對象中包含了Servlet初始化參數信息
**/
public ServletConfig getServletConfig();
}
GenericServlet抽象類實現了Servlet接口,它只是通用的實現,與任何網絡應用層協議無關。
同時,HttpServlet這個抽象類繼承了GenericServlet抽象類,在Http協議上提供了通用的實現。
查看GenericSerlvet抽象類源碼:
Java代碼
package javax.servlet;
import java.io.IOException;
import java.util.Enumeration;
//抽象類GenericServlet實現了Servlet接口的同時,也實現了ServletConfig接口和Serializable這兩個接口
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
//私有變量,保存 init()傳入的ServletConfig對象的引用
private transient ServletConfig config;
//無參的構造方法
public GenericServlet() { }
/*
------------------------------------
以下方法實現了servlet接口中的5個方法
實現Servlet接口方法開始
------------------------------------
*/
/*
實現接口Servlet中的帶參數的init(ServletConfig Config)方法,將傳遞的ServletConfig對象的引用保存到私有成員變量中,
使得GenericServlet對象和一個ServletConfig對象關聯.
同時它也調用了自身的不帶參數的init()方法
**/
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init(); //調用了無參的 init()方法
}
//無參的init()方法
public void init() throws ServletException {
}
//空實現了destroy方法
public void destroy() { }
//實現了接口中的getServletConfig方法,返回ServletConfig對象
public ServletConfig getServletConfig()
{
return config;
}
//該方法實現接口<Servlet>中的ServletInfo,默認返回空字符串
public String getServletInfo() {
return "";
}
//唯一沒有實現的抽象方法service(),僅僅在此聲明。交由子類去實現具體的應用
//在後來的HttpServlet抽象類中,針對當前基於Http協議的Web開發,HttpServlet抽象類具體實現了這個方法
//若有其他的協議,直接繼承本類後實現相關協議即可,具有很強的擴展性
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
/*
------------------------------------
實現Servlet接口方法結束
------------------------------------
*/
/*
---------------------------------------------
*以下四個方法實現了接口ServletConfig中的方法
實現ServletConfig接口開始
---------------------------------------------
*/
//該方法實現了接口<ServletConfig>中的getServletContext方法,用於返回servleConfig對象中所包含的servletContext方法
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
//獲取初始化參數
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
//實現了接口<ServletConfig>中的方法,用於返回在web.xml文件中爲servlet所配置的全部的初始化參數的值
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
//獲取在web.xml文件中註冊的當前的這個servlet名稱。沒有在web.xml 中註冊的servlet,該方法直接放回該servlet的類名。
//法實現了接口<ServleConfig>中的getServletName方法
public String getServletName() {
return config.getServletName();
}
/*
---------------------------------------------
實現ServletConfig接口結束
---------------------------------------------
*/
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
}
HttpServlet是繼承了GenericServlet抽象類的一個抽象類,但是他的裏面並沒有任何抽象方法,這就是說他並不會強迫我們去做什麼。我們只是按需選擇,重寫HttpServlet中的的部分方法就可以了。
下面是抽象類HttpServlet的源碼:
Java代碼
package javax.servlet.http;
..... //節約篇幅,省略導入包
public abstract class HttpServlet extends GenericServlet
implements java.io.Serializable
{
private static final String METHOD_GET = "GET";
private static final String METHOD_POST = "POST";
......
/**
* Does nothing, because this is an abstract class.
* 抽象類HttpServlet有一個構造函數,但是空的,什麼都沒有
*/
public HttpServlet() { }
/*分別執行doGet,doPost,doOpitions,doHead,doPut,doTrace方法
在請求響應服務方法service()中,根據請求類型,分貝調用這些doXXXX方法
所以自己寫的Servlet只需要根據請求類型覆蓋響應的doXXX方法即可。
*/
//doXXXX方法開始
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
.......
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
//todo
}
//doXXXX方法結束
//重載的service(args0,args1)方法
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);
}
}
//實現父類的service(ServletRequest req,ServletResponse res)方法
//通過參數的向下轉型,然後調用重載的service(HttpservletRequest,HttpServletResponse)方法
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); //調用重載的service()方法
}
......//其他方法
}
值得一說的是,很多介紹servlet的書中,講解servlet第一個實例時候,都習慣去覆蓋HttpServlet的service(HttpServletRequest request,HttpServletResponse response)方法來演示servlet.
當然,直接覆蓋sevice()方法,作爲演示,可以達到目的。 因爲servlet首先響應的就是調用service()方法,直接在此方法中覆蓋沒問題。但在實際的開發中,我們只是根據請求的類型,覆蓋響應的doXXX方法即可。最常見的是doGet和doPost方法。
從HtttpServlet的源碼中關於service() 方法的實現,可以看出,它最終也是根據請求類型來調用的各個doxxx 方法來完成響應的。如果自己寫的servlet覆蓋了service()方法,若沒對相應的請求做處理,則系統無法調用相關的doxxx方法來完成提交的請求任務。
---------------------------------------------------------------------/////////////////////////////////////////////////////////////////////////
在javax.servlet.Servlet接口中,定義了針對Servlet生命週期最重要的三個方法,按照順序,依次是init(),Serveice()和destroy()這三個方法.
.
Servlet初始化階段,包括執行如下四個步驟:
1. servlet容器(如tomcat)加載servlet類,讀入其.class類文件到內存
2. servlet容器開始針對這個servlet,創建ServletConfig對象
3. servlet容器創建servlet對象
4. servlet容器調用servlet對象的init(ServletConfig config)方法,在這個init方法中,建立了sevlet對象和servletConfig對象的關聯,執行了如下的代碼:
Java代碼
public void init(ServletConfig config) throws ServletException
{
this.config = config; //將容器創建的servletConfig 對象傳入,並使用私有成員變量引用該servletConfig對象
this.init();
}
通過以上的初始化步驟建立了servlet對象和sevletConfig對象的關聯,而servletConfig對象又和當前容器創建的ServleContext對象獲得關聯.
運行時階段:
當容器接受到訪問特定的servlet請求時,針對這個請求,創建對應的ServletRequest對象和ServletResponse對象,並調用servlet的service()方法,service()根據從ServletRequest對象中獲得客戶的請求信息
並將調用相應的doxxx方法等進行響應,再通過ServletResponse對象生成響應結果,然後發送給客戶端,最後銷燬創建的ServletRequest 和ServletResponse
銷燬階段:
只有當web應用被終止時,servlet纔會被銷燬。servlet容器現調用web應用中所有的Servlet對象的destroy()方法,然後再銷燬這些servlet對象,此外,容器還銷燬了針對各個servlet所創建的相關聯的serveltConfig對象
以下程序代碼演示了Servlet對象的生命週期:
Java代碼
/*
* 本程序代碼演示了Servlet週期中init()、serveic()、destroy() 三個方法的調用的情況
* Author: Laurence Luo
* Date; 2009-10-21
*
*
*/
package com.longweir;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class LifeServlet extends HttpServlet
{
private int initvar=0;
private int servicevar=0;
private int destroyvar=0;
private String name;
//覆蓋父類的銷燬方法,加入銷燬的計數器
public void destroy()
{
destroyvar++;
System.out.println(name+">destroy()被銷燬了"+destroyvar+"次");
}
//覆蓋父類的init(ServletConfig config)方法 加入計數
/* public void init(ServletConfig config) throws ServletException
{
super.init(config); //調用父類的帶參數的init方法
name=config.getServletName();
initvar++;
System.out.println(name+">init():Servlet 被初始化了"+initvar+"次");
}*/
/* 根據分析GenericServlet的源碼,其init(ServletConfig config)方法最終還是會調用了不帶參數的init()方法,
* 所以也可以在自己編寫的的servlet中覆蓋不帶參數的init()方法來加入統計計數
*/
public void init() //覆蓋父類的不帶參數的初始化方法
{
initvar++;
//name=getServleConfig.getServletNmae();
name=getServletName(); //直接返回
System.out.println(name+"init(): servlet被初始化了了 "+initvar+"次");
}
*/
public void service(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
{
servicevar++; //請求服務計數器自增
System.out.println(name+">service():servlet共響應了"+servicevar+"請求");
String content1="初始化次數: "+initvar;
String content2="銷燬次數: "+destroyvar;
String content3="請求服務次數 "+servicevar;
response.setContentType("text/html;charset=GBK");
PrintWriter out=response.getWriter();
out.print("<html><head><title>LifeServlet</title></head>");
out.print("<body>");
out.print("<h1>"+content1+"</h1><br>");
out.print("<h1>"+content2+"</h1><br>");
out.print("<h1>"+content3+"</h1><br>");
out.print("</body>");
out.print("</head>");
out.close();
}
}
之前提到servlet 生命週期中的三個階段,第一個階段中servlet容器會執行init方法來初始化一個servlet.
init方法和destroy這兩個方法在servlet生命週期中之執行一次。servlet容器(或者說是servlet引擎)創建了servlet實例對象後立即調用該init方法。
Init方法是在servlet對象被創建後,再由servlet容器調用的方法,其執行位於構造方法之後,在執行init方法時,會傳遞一個serveltConfig對象。
所以,如果要在初始代碼中用到servletConfig對象,則這些初始操作只能在init方法中編寫,不能在構造方法中編寫.
Java代碼
GenericServlet中關於init方法的示意源碼:
public void init(ServletConfig config) throws ServletException
{
this.config=config;
......
}
如果在要在自己編寫的servlet類中增加一些額外的初始化功能,則必須覆蓋GenricServlet類的init(ServletConfig config)方法,如果覆蓋後,父類的init(ServletConfig config)方法就不會被調用。GenericServlet類中有些方法依賴init方法執行的結果,例如
Java代碼
getServletName()方法:
public ServletContext getServeltContext()
{
return config.getServletContext;
}
這裏的config對象的引用就來自Init(ServletConfig config)執行的結果.
所以如果子類需要覆蓋了父類的init(ServletConfig config)方法,則首先要調用父類的init(ServletConfig config)方法,也就是先加入 super.init(config) 語句來先執行父類的方法.否則,會產生空指針異常 java.lang.NullPointerException ,因爲config對象的引用爲空。
爲了避免這樣的情況的產生,GenericServlet的設計人員 在GenericServlet中又定義了一個無參數的init()空方法. 且在init(servletConfig config)方法最後也調用了這個無參的空方法
Java代碼
public void init(ServletConfig config) throws ServletException
{
this.config=config;
init();
}
所以,我們只需覆蓋這個無參的init方法加入自己的初始代碼即可,而無需覆蓋帶參數的父類的init方法.
如果有多個重載的init方法,對以servlet而言,servlet容器始終就之調用servlet接口中的那個方法:init(ServletConfig config) (當然也會執行已經覆蓋的無參的init()方法),其他的覆蓋的init方法不會執行。
分析 2中關於servlet生命週期的那個例子程序中的一個代碼:
Java代碼
//覆蓋父類的init(ServletConfig config)方法 加入計數
public void init(ServletConfig config) throws ServletException
{
super.init(config); //調用父類的init方法
name=config.getServletName();
initvar++;
System.out.println(name+">init():Servlet 被初始化了"+initvar+"次");
}
/* 父類的帶參數的init(ServletConfig config)方法最終還是會調用了不帶參數的init()方法,
* 所以也可以在自己的servlet中覆蓋不帶參數的init()方法來加入自己的統計參數代碼
*/
public void init() //覆蓋父類的不帶參數的初始化方法
{
//name=getServleConfig.getServletNmae(); //因爲已經實現了servletConfig接口,則直接調用其方法即可
name=getServletName(); //直接返回
initvar++;
System.out.println(name+"init(): servlet被初始化了了 "+initvar+"次");
}
觀察GenericServlet源碼中關於service()方法的實現:
Java代碼
//實現父類的service(ServletRequest req,ServletResponse res)方法
//通過參數的向下轉型,然後調用重載的service(HttpservletRequest,HttpServletResponse)方法
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); //調用重載的service(HttpServelertRequest,HttpServletResponse)方法
}
//重載的service(HttpServletRequest req, HttpServletResponse resp)方法
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);
}
}
在抽象類中GenericServlet中service()是一個抽象方法,但在HttpServlet中對這個方法進行了實現。
servlet接口中定義的service()方法中的兩個參數分別是servletRequest 和 ServletResponse 這兩個類型。當前的http請求,如果需要在這個service()方法內部使用http消息特有的功能,也就是要調用HttpServletRequest 和HttpServletResponse來中定義的方法時,需要將請求和響應對象進行一個類型的轉換,所以,在GenericServlet中,使用了兩個方法來共同完成這個工作。
實現父類GenericServlet中的service(ServltRequest req,ServeltResponse res)抽象方法
Java代碼
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); //這裏是調用重載的service(HttpServelertRequest,HttpServletResponse)方法
}
service(HttpServelertRequest,HttpServletResponse)方法:
//重載的service(HttpServletRequest req, HttpServletResponse resp)方法 ,注意參數類型
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);
}
}