一、問題介紹
在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