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相关文章推荐
- J2EE知识扫肓--Servlet生命周期详解
- servlet生命周期详解
- jsp页面的执行过程与servlet的生命周期详解
- Servlet生命周期详解与图解
- Servlet生命周期详解
- 详解servlet的生命周期
- Servlet生命周期详解
- servlet 生命周期 和 作原理详解
- Servlet详解---Servlet的生命周期
- Servlet 生命周期详解
- Servlet简介及其生命周期详解
- Servlet的生命周期、反射机制详解Servlet生命周期
- servlet配置方法及其生命周期详解
- 详解servlet生命周期
- Servlet Servlet生命周期详解
- web.xml 中的listener、 filter、servlet 加载顺序及其详解
- JAVA基础(三) web.xml 中的listener、 filter、servlet 加载顺序及其详解
- web.xml 中的listener、 filter、servlet 加载顺序及其详解
- servlet的生命周期,doGet 及doPost
- web.xml 中的listener、 filter、servlet 加载顺序及其详解