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),然后调用相应方法
整个生命周期: