您的位置:首页 > 编程语言 > Java开发

Servlet生命周期详解

2018-08-26 22:46 417 查看

一、问题介绍

在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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java EE Jetty