Servlet生命週期詳解 原

一、問題介紹

在java項目的實際開發中,很多項目都是java web項目,這裏就離不開servlet,瞭解servlet的生命週期及處理流程對於掌握web開發十分重要。

二、實例及源碼講解

2.1、servlet的生命週期

我們一般都瞭解servlet的大致生命週期爲:init->service->get\post\...->destroy,如下圖:

該圖只是說明了各個方法的前後調用順序,沒有具體說明各個方法的調用時機。我們可以通過解讀源碼來了解具體各個方法的調用時機。

我們編寫了一個樣例應用來了解具體的調用時機。項目依賴如下:


apply plugin: "java"
apply plugin: "idea"
apply plugin: "jetty"

idea {
    module {
        downloadSources = true
        downloadJavadoc = true
    }

}

group 'com.iwill'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
   // providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.0'
    compile group: 'org.mortbay.jetty', name: 'servlet-api', version: '2.5-20081211'
    compile group: 'org.mortbay.jetty', name: 'jetty', version: '6.1.26'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

jettyRun {
    httpPort 8080
    contextPath project.name
    scanIntervalSeconds 0
    reload "automatic"
}

這裏依賴jetty的servlet-api和jetty的原因是我們在使用jetty作爲服務器時(本地,生產環境一般是tomcat),通過打印加載的類信息來知道對應的servlet的group及版本的。樣例代碼:

package com.iwill.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(" invoke get method ");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(" invoke post method ");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(" invoke service method ");
        super.service(req, resp);
    }

    @Override
    public void destroy() {
        System.out.println(" invoke destroy method ");
        super.destroy();
    }

    @Override
    public void init() throws ServletException {
        System.out.println(" invoke init method ");
        super.init();
    }
}

通過在init方法上添加斷點,可以看到,在訪問/hello時,會被org.mortbay.jetty.servlet.ServletHolder#handle處理

org.mortbay.jetty.servlet.ServletHolder#handle的代碼如下:

 /** Service a request with this servlet.
     */
    public void handle(ServletRequest request,
                       ServletResponse response)
        throws ServletException,
               UnavailableException,
               IOException
    {
        if (_class==null)
            throw new UnavailableException("Servlet Not Initialized");
        
        Servlet servlet=_servlet;
        synchronized(this)
        {
            if (_unavailable!=0 || !_initOnStartup)
                servlet=getServlet();
            if (servlet==null)
                throw new UnavailableException("Could not instantiate "+_class);
        }
        
        // Service the request
        boolean servlet_error=true;
        Principal user=null;
        Request base_request=null;
        try
        {
            // Handle aliased path
            if (_forcedPath!=null)
                // TODO complain about poor naming to the Jasper folks
                request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);

            // Handle run as
            if (_runAs!=null && _realm!=null)
            {
                base_request=HttpConnection.getCurrentConnection().getRequest();
                user=_realm.pushRole(base_request.getUserPrincipal(),_runAs);
                base_request.setUserPrincipal(user);
            }
            
            servlet.service(request,response);
            servlet_error=false;
        }
        catch(UnavailableException e)
        {
            makeUnavailable(e);
            throw _unavailableEx;
        }
        finally
        {
            // pop run-as role
            if (_runAs!=null && _realm!=null && user!=null && base_request!=null)
            {
                user=_realm.popRole(user);
                base_request.setUserPrincipal(user);
            }

            // Handle error params.
            if (servlet_error)
                request.setAttribute("javax.servlet.error.servlet_name",getName());
        }
    }

從上面的代碼可以看出,首先會調用 servlet=getServlet(),後面再調用servlet.service(request,response)。

getServlet()方法代碼如下:

 public synchronized Servlet getServlet()
        throws ServletException
    {
        // Handle previous unavailability
        if (_unavailable!=0)
        {
            if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
                throw _unavailableEx;
            _unavailable=0;
            _unavailableEx=null;
        }

        if (_servlet==null)
            initServlet();
        return _servlet;
    }

如果_servlet==null,會去調用initServlet(),該方法的源碼如下:

private void initServlet() 
    	throws ServletException
    {
        Principal user=null;
        try
        {
            if (_servlet==null)
                _servlet=(Servlet)newInstance();
            if (_config==null)
                _config=new Config();
            
            //handle any cusomizations of the servlet, such as @postConstruct
            if (!(_servlet instanceof SingleThreadedWrapper))
                _servlet = getServletHandler().customizeServlet(_servlet);
            
            // Handle run as
            if (_runAs!=null && _realm!=null)
                user=_realm.pushRole(null,_runAs);
            
            _servlet.init(_config);
        }
        catch (UnavailableException e)
        {
            makeUnavailable(e);
            _servlet=null;
            _config=null;
            throw e;
        }
        catch (ServletException e)
        {
            makeUnavailable(e.getCause()==null?e:e.getCause());
            _servlet=null;
            _config=null;
            throw e;
        }
        catch (Exception e)
        {
            makeUnavailable(e);
            _servlet=null;
            _config=null;
            throw new ServletException(e);
        }
        finally
        {
            // pop run-as role
            if (_runAs!=null && _realm!=null && user!=null)
                _realm.popRole(user);
        }
    }

該方法會構造servlet和config,並且調用servlet.init(),即我們javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)方法,這個方法中會去調用javax.servlet.GenericServlet#init(),這是一個空方法,如果我們需要爲我們的servlet進行特別的初始化設置,我們可以覆蓋父類(HttpServlet的init方法)。

這裏我們弄清楚了我們servlet的init方法的調用時機,即第一次調用我們的servlet時會去調用init方法。我們跟蹤org.mortbay.jetty.servlet.ServletHolder#handle方法中的servlet.service(request,response),會來到javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)servlet.service(request,response)。該方法的代碼如下:

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

即將一般的ServletRequest、ServletResponse轉化爲HttpServletRequest、HttpServletResponse,然後調用自身的service方法,該方法實現如下:

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

根據method調用對應的方法。

根據上述分析可知,Servlet的詳細生命週期如下:

在第一次調用servlet時,會進行初始化(即調用init方法);每次調用時都會調用service方法,該方法的作用就是請求分發到具體的方法(get、post等);最後在容器關閉時,會調用destroy方法。因此,除了init、destroy方法是調用一次外,其他方法可以多次調用。

2.2、servlet的類繼承關係及各個方法說明

2.2.1、servlet的類繼承關係如下:

servlet和servletConfig是接口,servletConfig定義了在初始化時,容器傳給servlet的信息,servlet定義了所有servlet都需要實現的方法。

GenericServlet是一個抽象類,定義了通用的、協議無關的servlet。

HttpServlet是一個抽象類,定義了基於web應用的servlet,如果要自己編寫web應用的servlet,繼承HttpServlet,並且必須要重寫對應的方法(doGet\doPost\...)。

2.2.2、servlet的各方法說明

init方法:servlet第一次被調用的時候調用,被調用之後,該servlet可以接受外部的請求,並且只調用一次。我們實現servlet時,可以在init方法中進行一次初始化工作,比如說,數據庫連接池初始化等。

service:servlet接收請求的入口,並且在該方法被調用前,一定要保證init被調用過。並且,在多線程併發環境下,需要使用同步機制來保證共享資源的訪問。

destroy:容器調用destroy方法後,該servlet就不會再對外提供服務了。但是,如果自己在servlet調用destroy方法,不會有什麼影響。一般會在該方法中進行資源的關閉操作,例如:數據庫線程池的關閉等。

 

三、實例代碼

上述涉及到的實例代碼見: https://github.com/yangjianzhou/servlet-lifecycle

 

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