Servlet生命周期以及工作原理

Java 发表评论


  最近感觉到用久了SpringMVC、Struts2等框架,反而对它们的底层实现,即Servlet,的相关知识有了许多遗忘。现在参考了网上的一些博客,来进行一次知识点总结。
  

Servlet响应客户端请求的过程

image_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命周期

  1. init方法:当Servlet容器第一次加载并创建Servlet实例时,在调用该Servlet的构造函数后立即调用init方法对该Servlet对象进行初始化。构造器和init方法都只会被调用一次,这说明Servlet是单实例的(需要考虑线程安全的问题,不推荐在其中写全局变量)。
  2. service方法:每次请求都会调用service方法,它是实际用于响应请求的方法,可以被多次调用。
  3. destroy方法:在服务器端停止且卸载Servlet时执行该方法,用于释放当前Servlet所占用的资源,只会被调用一次。

以上方法都由Servlet容器(例如Tomcat)负责调用。
 
 

ServletConfig

  Servlet接口的init方法会接收一个ServletConfig对象作为参数,即init(ServletConfig servletConfig),ServletConfig对象封装了该Serlvet的配置信息,并且可以获取ServletContext对象。例如:
  
在web.xml中配置Servlet的初始化参数:

<span class="hljs-tag"><<span class="hljs-title">servlet</span>></span>
        <span class="hljs-comment"><!-- Servlet注册的名字 --></span>
        <span class="hljs-tag"><<span class="hljs-title">servlet-name</span>></span>helloServlet<span class="hljs-tag"></<span class="hljs-title">servlet-name</span>></span>
        <span class="hljs-comment"><!-- Servlet全类名 --></span>
        <span class="hljs-tag"><<span class="hljs-title">servlet-class</span>></span>servlet.HelloServlet<span class="hljs-tag"></<span class="hljs-title">servlet-class</span>></span>
        <span class="hljs-comment"><!-- 配置该Servlet的初始化参数 --></span>
        <span class="hljs-tag"><<span class="hljs-title">init-param</span>></span>
            <span class="hljs-tag"><<span class="hljs-title">param-name</span>></span>username<span class="hljs-tag"></<span class="hljs-title">param-name</span>></span>
            <span class="hljs-tag"><<span class="hljs-title">param-value</span>></span>root<span class="hljs-tag"></<span class="hljs-title">param-value</span>></span>
        <span class="hljs-tag"></<span class="hljs-title">init-param</span>></span>
        <span class="hljs-tag"><<span class="hljs-title">init-param</span>></span>
            <span class="hljs-tag"><<span class="hljs-title">param-name</span>></span>password<span class="hljs-tag"></<span class="hljs-title">param-name</span>></span>
            <span class="hljs-tag"><<span class="hljs-title">param-value</span>></span>123456<span class="hljs-tag"></<span class="hljs-title">param-value</span>></span>
        <span class="hljs-tag"></<span class="hljs-title">init-param</span>></span>
    <span class="hljs-tag"></<span class="hljs-title">servlet</span>></span>

可以在HelloServlet的init方法中获取这些参数的信息(在实际开发中,通常使用的Servlet都继承了HttpServlet类,可以通过getServletConfig()在该Servlet对象中获取到ServletConfig对象,不一定只能在init方法中使用,下面的getServletContext()也是一样):

<span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>(ServletConfig servletConfig) <span class="hljs-keyword">throws</span> ServletException {
        <span class="hljs-comment">//获取Servlet初始化参数相关的信息</span>
        String username = servletConfig.getInitParameter(<span class="hljs-string">"username"</span>);
        System.out.println(<span class="hljs-string">"username is "</span>+username);
        Enumeration<String> names = servletConfig.getInitParameterNames();
        <span class="hljs-keyword">while</span>(names.hasMoreElements()){
            String name = names.nextElement();
            System.out.println(<span class="hljs-string">"name is "</span>+name);
            System.out.println(<span class="hljs-string">"value is "</span>+servletConfig.getInitParameter(name));
        }

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB
 
 

ServletContext

  ServletContext对象代表着当前的WEB应用。可以认为SerlvetContext是当前 WEB应用的一个大管家,可以从中获取到当前WEB应用的各个方面的信息。
  ServletContext对象可以由SerlvetConfig获取:

ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 获取当前WEB应用的初始化参数:
  
在web.xml中配置当前WEB应用的初始化参数:(这些参数可以被所有的 Servlet通过ServletContext获取,而上面配置在Servlet中的参数只能由对应的Servlet获取)

<span class="hljs-tag"><<span class="hljs-title">context-param</span>></span>
        <span class="hljs-tag"><<span class="hljs-title">param-name</span>></span>driver<span class="hljs-tag"></<span class="hljs-title">param-name</span>></span>
        <span class="hljs-tag"><<span class="hljs-title">param-value</span>></span>com.mysql.jdbc.Driver<span class="hljs-tag"></<span class="hljs-title">param-value</span>></span>
    <span class="hljs-tag"></<span class="hljs-title">context-param</span>></span>
    <span class="hljs-tag"><<span class="hljs-title">context-param</span>></span>
        <span class="hljs-tag"><<span class="hljs-title">param-name</span>></span>jdbcUrl<span class="hljs-tag"></<span class="hljs-title">param-name</span>></span>
        <span class="hljs-tag"><<span class="hljs-title">param-value</span>></span>jdbc:mysql:///xiangwanpeng<span class="hljs-tag"></<span class="hljs-title">param-value</span>></span>
    <span class="hljs-tag"></<span class="hljs-title">context-param</span>></span>

在init方法中获取到这些参数:

<span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>(ServletConfig servletConfig) <span class="hljs-keyword">throws</span> ServletException {
        <span class="hljs-comment">//获取ServletContext对象,它包含了当前WEB应用的各种信息</span>
        ServletContext servletContext = servletConfig.getServletContext();
        <span class="hljs-comment">//获取WEB应用的初始化信息,方法和servletConfig类似</span>
        String driver = servletContext.getInitParameter(<span class="hljs-string">"driver"</span>);
        System.out.println(<span class="hljs-string">"driver is "</span>+driver);
        Enumeration<String> names2 = servletContext.getInitParameterNames();
        <span class="hljs-keyword">while</span>(names2.hasMoreElements()){
            String name2 = names2.nextElement();
            System.out.println(<span class="hljs-string">"name2 is "</span>+name2);
            System.out.println(<span class="hljs-string">"value2 is "</span>+servletContext.getInitParameter(name2));
        }
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 获取当前WEB应用的某一个文件在服务器上的绝对路径,而不是部署前的路径。

例如现在在根目录下新建一个note.txt:

image_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中获取:

String realPath = servletContext.getRealPath(<span class="hljs-string">"/note.txt"</span>);
        System.out.println(<span class="hljs-string">"realPath is "</span>+realPath);

获取请求后在控制台输出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 获取当前 WEB应用的名称:

String contextPath = servletContext.getContextPath();
System.out.println(contextPath);

④ 获取当前 WEB应用的某一个文件对应的输入流:

在src目录下新建一个jdbc.properties:

image_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中获取该文件的输入流:

InputStream is = servletContext.getResourceAsStream(<span class="hljs-string">"/WEB-INF/classes/jdbc.properties"</span>);
        System.out.println(<span class="hljs-string">"is is "</span>+is);

输出:

image_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意区别另一种方法,即通过getClassLoader获取,这种方法的路径是没有/WEB-INF/classes/前缀的:

InputStream is2 = <span class="hljs-keyword">this</span>.getClass().getClassLoader().getResourceAsStream(<span class="hljs-string">"jdbc.properties"</span>);
        System.out.println(<span class="hljs-string">"is2 is "</span>+is2);

⑤ 和attribute相关的几个方法。
 
 

GenericServlet

  GenericServlet是Servlet接口和ServletConfig接口的实现类,是一个抽象类,因为其中的service()方法为抽象方法。它的作用是:如果新建的 Servlet程序直接继承GenericSerlvet会使开发更简洁。
  
具体实现:
① 在GenericServlet中声明了一个SerlvetConfig类型的成员变量,在init(ServletConfig)方法中对其进行了初始化。
② 利用servletConfig成员变量的方法实现了 ServletConfig接口的方法。
③ 还定义了一个 init()方法,在init(SerlvetConfig)方法中对其进行调用,子类可以直接覆盖init()在其中实现对Servlet的初始化。
④ 不建议直接覆盖 init(ServletConfig),因为如果忘记编写super.init(config),而还是用了SerlvetConfig接口的方法,则会出现空指针异常。
⑤ 新建的 init(){}并非Serlvet的生命周期方法,而init(ServletConfig)是生命周期相关的方法。

以下是源码:

<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GenericServlet</span> 
    <span class="hljs-keyword">implements</span> <span class="hljs-title">Servlet</span>, <span class="hljs-title">ServletConfig</span>, <span class="hljs-title">java</span>.<span class="hljs-title">io</span>.<span class="hljs-title">Serializable</span>
{</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">transient</span> ServletConfig config;

    <span class="hljs-keyword">public</span> <span class="hljs-title">GenericServlet</span>() { }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">destroy</span>() {
    }

    <span class="hljs-keyword">public</span> String <span class="hljs-title">getInitParameter</span>(String name) {
    <span class="hljs-keyword">return</span> getServletConfig().getInitParameter(name);
    }

    <span class="hljs-keyword">public</span> Enumeration <span class="hljs-title">getInitParameterNames</span>() {
    <span class="hljs-keyword">return</span> getServletConfig().getInitParameterNames();
    }   

    <span class="hljs-keyword">public</span> ServletConfig <span class="hljs-title">getServletConfig</span>() {
    <span class="hljs-keyword">return</span> config;
    }

    <span class="hljs-keyword">public</span> ServletContext <span class="hljs-title">getServletContext</span>() {
    <span class="hljs-keyword">return</span> getServletConfig().getServletContext();
    }


    <span class="hljs-keyword">public</span> String <span class="hljs-title">getServletInfo</span>() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>(ServletConfig config) <span class="hljs-keyword">throws</span> ServletException {
    <span class="hljs-keyword">this</span>.config = config;
    <span class="hljs-keyword">this</span>.init();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>() <span class="hljs-keyword">throws</span> ServletException {

    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">log</span>(String msg) {
    getServletContext().log(getServletName() + <span class="hljs-string">": "</span>+ msg);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">log</span>(String message, Throwable t) {
    getServletContext().log(getServletName() + <span class="hljs-string">": "</span> + message, t);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span>(ServletRequest req, ServletResponse res)
    <span class="hljs-keyword">throws</span> ServletException, IOException;

    <span class="hljs-keyword">public</span> String <span class="hljs-title">getServletName</span>() {
        <span class="hljs-keyword">return</span> config.getServletName();
    }
}

HttpServlet

  HttpServlet是一个继承自 GenericServlet的Servlet,而且它是针对于 HTTP协议所定制。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse强制转换为HttpServletRequest和HttpServletResponse。并调用了重载的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通过request.getMethod()获取请求方式,并根据请求方式创建了doXxx()方法(xxx为具体的请求方式,比如 doGet,doPost)。
  实际开发中, 我们通常直接继承HttpServlet,这样做的好处是可以直接有针对性的覆盖doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要进行类型的强制转换。
  
主要源码如下:

 <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span>(ServletRequest req, ServletResponse res)
    <span class="hljs-keyword">throws</span> ServletException, IOException
    {
    HttpServletRequest  request;
    HttpServletResponse response;

    <span class="hljs-keyword">try</span> {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } <span class="hljs-keyword">catch</span> (ClassCastException e) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ServletException(<span class="hljs-string">"non-HTTP request or response"</span>);
    }
    service(request, response);
    }


  <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service</span>(HttpServletRequest req, HttpServletResponse resp)
    <span class="hljs-keyword">throws</span> ServletException, IOException
    {
    String method = req.getMethod();

    <span class="hljs-keyword">if</span> (method.equals(METHOD_GET)) {
        <span class="hljs-keyword">long</span> lastModified = getLastModified(req);
        <span class="hljs-keyword">if</span> (lastModified == -<span class="hljs-number">1</span>) {
        <span class="hljs-comment">// servlet doesn't support if-modified-since, no reason</span>
        <span class="hljs-comment">// to go through further expensive logic</span>
        doGet(req, resp);
        } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">long</span> ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
        <span class="hljs-keyword">if</span> (ifModifiedSince < (lastModified / <span class="hljs-number">1000</span> * <span class="hljs-number">1000</span>)) {
            <span class="hljs-comment">// If the servlet mod time is later, call doGet()</span>
                    <span class="hljs-comment">// Round down to the nearest second for a proper compare</span>
                    <span class="hljs-comment">// A ifModifiedSince of -1 will always be less</span>
            maybeSetLastModified(resp, lastModified);
            doGet(req, resp);
        } <span class="hljs-keyword">else</span> {
            resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        }
        }

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_HEAD)) {
        <span class="hljs-keyword">long</span> lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_PUT)) {
        doPut(req, resp);   

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);

    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);

    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">//</span>
        <span class="hljs-comment">// Note that this means NO servlet supports whatever</span>
        <span class="hljs-comment">// method was requested, anywhere on this server.</span>
        <span class="hljs-comment">//</span>

        String errMsg = lStrings.getString(<span class="hljs-string">"http.method_not_implemented"</span>);
        Object[] errArgs = <span class="hljs-keyword">new</span> Object[<span class="hljs-number">1</span>];
        errArgs[<span class="hljs-number">0</span>] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
    }

发表回复

您的电子邮箱地址不会被公开。

昵称 *