Web篇--Servlet的起点和终点

版权声明:Wizard原创博客,多多支持 https://blog.csdn.net/ysj4428/article/details/81905998

Servlet的生命周期:

这里使用常用的开发web项目模式进行讲解。

首先创建Web项目同时使用tomcat容器部署使用。

周期一:Web容器将Servlet加载

首先必须明确Servlet是属于Web容器提供给我们得API,即servlet-api.jar是我们通过tomcat添加的类库。如果有使用Weblogic的会知道,项目部署Weblogic上和Tomcat上获取到的servletContext是不一样的,因为虽然都是Web中间件,但是weblogic提供的jar包为javax.servlet.XXX.jar

首先我们创建一个Web项目,然后添加到Tomcat中。新建一个Servlet,而配置信息我们写:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>demo</servlet-name>
        <servlet-class>pers.sjyang.servlet.ServletDemo</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
</web-app>
package pers.sjyang.servlet;
import javax.jws.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
//@WebService(name = "/demo")
public class ServletDemo extends HttpServlet {
    @Override
    public void init() throws ServletException {
        super.init();
        System.out.printf("初始化方法");
    }

    @Override
    public void destroy() {
        super.destroy();
        System.out.printf("销毁方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
        System.out.printf("doPost方法");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.printf("doGet方法");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
        System.out.printf("Service方法");
    }
}

然后启动程序,访问欢迎页。

资料显示Web容器会在启动时,将web.xml中配置的servlet加载到容器中,注意此时并没有初始化。因为我们的init方法并没有打印任何信息。下面看看Web容器加载Servlet容器过程。

tomcat源码这个时候需要好好读一下啦(以后好还研究Tomcat架构模型)。找资料信息为:

  • 通过配置loadOnStartup可以设置Servlet是否在Tomcat启动时加载,以及按值大小进行有序加载,其最小有效值为0,最大有效值为Integer.MAX_VALUE。
  • Jsp Servlet的类是org.apache.jasper.servlet.JspServlet。
  • Jsp Servlet是强制性启动时加载,其loadOnStartup的默认值,或其值是失效值时,将使用最大有效值。
  • 通过配置Context或Host的failCtxIfServletStartFails属性值,优先使用Context的,设置tomcat启动时加载servlet时,是否忽略抛出的ServletException异常,如果不忽略,则tomcat启动失败,默认不忽略。
  • servlet有多线程模式,和单线程模式,默认是多线程模式,单线程模式需要实现SingleThreadModel接口。单线程模式下,开启一个实例池,同一时间,一个实例只分配给一个线程占用。

代码层面详解:

1.tomcat启动后首先调用loadOnStartup方法,参数为Container[]的数组。该数组内容为我们配置的servlet信息,包括web.xml以及使用@webservice(name=" “)配置的。

2.定义TreeMap。然后遍历数组中的servlet信息,并将其转化为Wrapper类型。通过Wrapper的getLoadOnStartup()方法,获取servlet信息的loadonstart数据,将小于0的抛出在外,剩余的添加到TreeMap中,而TreeMap会自动排序。其中key为loadOnstart值。最后将排序好的Wrapper加入到顺序集合list中。

TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
    Wrapper wrapper = (Wrapper) children[i];
    int loadOnStartup = wrapper.getLoadOnStartup();
    if (loadOnStartup < 0)
        continue;
    Integer key = Integer.valueOf(loadOnStartup);
    ArrayList<Wrapper> list = map.get(key);
    if (list == null) {
        list = new ArrayList<>();
        map.put(key, list);
    }
    list.add(wrapper);
}

3.实际上Wrapper是一个接口,真正实现Wrapper方法的是StandardWrapper,而StandardWrapper还实现了servlet规范中的ServletConfig接口。但是,最后传递给应用使用的ServletConfig并非StandardWrapper,而是StandardWrapperFacade,当然也是实现ServletConfig接口。正如名字上的意思,使用外观模式再一次包装StandardWrapper,然后调用其的同名方法。所以servlet的所有配置信息都存放在StandardWrapper,如成员变量loadOnStartup就是配置servlet中的一项,其默认值为-1。

public class StandardWrapper extends ContainerBase 
    implements ServletConfig, Wrapper

public final class StandardWrapperFacade
    implements ServletConfig

protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);

protected int loadOnStartup = -1;

4.下一步执行load方法。

load方法如果抛出ServletException异常,将通过getComputedFailCtxIfServletStartFails获取的值来决定是否让Tomcat启动失败。

而获取的值是Context配置的failCtxIfServletStartFails属性,如果该属性未配置,则使用Host配置failCtxIfServletStartFails的属性,其默认值为false。

进入load方法后,load方法是一个线程安全的方法,为什么需要线程安全?因为,servlet大多数情况下是不进行预加载,什么时候用到,就什么时候加载。换句话说,为了节省从来没用到的servlet资源空间,所以等到有请求访问到servlet时才加载。

所以这种后加载的方式,就有可能出现两个请求,在同一时间访问到同一个servlet时,这时候就需要线程安全了。那什么时候需要预加载,什么时候不需要预加载,这个问题可以思考到,为什么定义Jsp Servlet时,tomcat是强制性要预加载,这里不再往深叙述了。

成员变量instance就是配置的servlet类的实例,实例是需要加载与初始化,才能对请求做处理。

public synchronized void load() throws ServletException {
    instance = loadServlet();

    if (!instanceInitialized) {
        initServlet(instance);
    }
}

5.剩下的就是验证servlet单实例与多实例。如果是单例,并且实例已经存在,那就返回该实例。如果是多例,不管实例存与不存在,都将再创建实例。

判断是使用单例还是多例,由成员变量singleThreadModel的值来判断,其含意就是如果同一个servlet实例,只允许在同一时间让一个请求(线程)访问,所以叫单线程模式。反之,同一个servlet实例,允许同一时间,让多个请求(线程)访问,就叫多线程模式。

显然,单线程与多线程的区别在于,servlet的成员变量共享与不共享,设计的重点就得往这里思考。那变量singleThreadModel默认值为false,即servlet模认是多线程模式,其如何改变为单线程模式在后面。如果servlet需要得到Wrapper对象实例,那么servlet实现ContainerServlet接口,并且在Context的配置里设置属性privileged为true,默认为false。来到initServlet方法,如果servlet是多线程模式,则实例只执行一次初始化。反之,如果servlet是单线程模式,每次创建的实例,都会执行一次初始化。

周期二:实例化

下面调用servlet查看实例化过程:

web.xml中配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>demo</servlet-name>
        <servlet-class>pers.sjyang.servlet.ServletDemo</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
</web-app>

servlet类

package pers.sjyang.servlet;
import javax.jws.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
//@WebService(name = "/demo")
public class ServletDemo extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
        System.out.println("初始化方法");
    }

    @Override
    public void destroy() {
        super.destroy();
        System.out.println("销毁方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost方法");
        req.setCharacterEncoding("UTF-8");
        resp.setHeader("Content-type", "text/html;charset=UTF-8");

        //这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859

        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().write("调用servlet成功");

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("do Get方法");


    }

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

}

启动开始调用:

可以看到结果出现了:

初始化方法
doPost方法
Service方法

周期三:销毁

我们关闭tomcat容器


可以看到servlet已经销毁。

总结:

servlet起点就是web容器启动的时候,也就是servlet加载过程

servlet终点就是web容器关闭的时候,当然也可以主动销毁。

在起点和重点之间就是:初始化(init)-  调用service(根据请求判断方法方式get/post),然后调用相应方法

整个生命周期:

猜你喜欢

转载自blog.csdn.net/ysj4428/article/details/81905998