目录
生命周期简介
生命周期测试
load-on-startup
补充:defaultServlet
Servlet 的继承结构
1. 顶级的 Servlet 接口
2. 抽线的类 GenericServlet
3. HttpServlet 抽象类
4. 自定义 Servlet
补充:
完!
生命周期简介
什么是生命周期?
应用程序中的对象,不仅在空间上有层次结构的关系,在实践上也会因为处于运行过程中的不同阶段,而表现出不同状态和不同行为,这就是对象的生命周期
简单的叙述生命周期:就是对象在开始创建,到最后销毁的过程。
Servlet 容器:
Servlet 对象是 Servlet 容器创建的,生命周期方法都是由容器(我们目前使用 Tomcat)调用的。这一点和我们之前所编写的代码,由很大的不同!在今后的学习中,我们越来越多的对象,都要交给容器或框架来创建,越来越多的方法,都要由容器或框架来调用。
我们作为程序员,要尽可能的将经历放在业务逻辑的实现上!
Servlet 主要的生命周期执行特点:
生命周期测试
还在我们前面的项目工程中,开发一个测试代码:
package com.zzz.servlet;import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;/** @author zzr* @date: 2025/07/04 21:24* @description: 测试 servlet 的生命周期*/
@WebServlet("/servletLifeCycle")
public class servletLifeCycle extends HttpServlet {public servletLifeCycle() {System.out.println("构造器");}@Overridepublic void init() throws ServletException {System.out.println("初始化");}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("服务");}@Overridepublic void destroy() {System.out.println("销毁");}
}
在四处打印的地方打上断点:
debug 模式运行
为浏览器配置好 url 后,回车,发现代码卡在了“构造器”的断点处
一步一步向后运行代码
此时并没有销毁。并且,当我们在浏览器,刷新重新打开的时候,就只会再打印“服务”了~
停止项目后,会打印“销毁”
结论:
通过生命周期的测试,我们发现:Servlet 对象在容器中是单例的。但,容器又是需要并发的,处理用户请求的,每个请求在容器中,都会开启一个线程。
多个线程可能会使用相同的 Servlet 对象,但 Servlet 对象在容器中是单例的,所以 Servlety 的成员变量,在多个线程之中也是共享的 ==》 非常非常不建议,在 service 中修改成员变量,在并发请求的时候,会引发线程安全问题~
load-on-startup
我们在前面的 @WebServlet 提到过这个成员变量~
其值是一个数字,含义是:tomcat 在启动时,实例化该 servlet 的顺序。(如果顺序号冲突了,tomcat 会自动协调启动顺序~)
<servlet><servlet-name>servletLifeCycle</servlet-name><servlet-class>com.zzz.servlet.servletLifeCycle</servlet-class>
<!--默认值是 -1 含义是 tomcat 启动时不会实例化该 servlet其他正整数,例如 15,含义是 在 tomcat启动时,实例化该 servlet 的顺序
--><load-on-startup>15</load-on-startup></servlet><servlet-mapping><servlet-name>servletLifeCycle</servlet-name><url-pattern>/servletLifeCycle</url-pattern></servlet-mapping>
将 servletLifeCycle 中的 @WebServlet 注释删掉,取消所有断点,直接运行程序:
发现自动就为我们准备好了构造器和初始化~
当然也可以在 @WebServlet 中设置 loadOnStartup 的值
注意:我们可以在 tomcat 文件夹下的 conf/web.xml 进行查找,发现有些序号已经占用了,1 - 5 号都已经被占用,我们如果要使用,最好不要重复 1 - 5 号~(这个序号就算不连贯也是可以的,tomcat 会自己匹配~)
补充:defaultServlet
defaulyServlet 是用于加载静态资源的 servlet,默认随服务器启动,默认启动序号为 1
Servlet 的继承结构
1. 顶级的 Servlet 接口
源码如下:
public interface Servlet {void init(ServletConfig var1) throws ServletException;ServletConfig getServletConfig();void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;String getServletInfo();void destroy();
}
Servlet 规范接口,所有的 servlet 必须实现
init:
初始化方法,容器在构造 servlet 对象后,自动调用的方法,容器负责实例化一个 ServletConfig 对象,并在调用该方法的时候传入。
ServletConfig 对象可以为 servlet 对象提供初始化参数
getServletConfig:
获取 ServletConfig 对象的方法,后续可以通过该对象获取 servlet 初始化参数
service:
处理请求并做出响应的服务方法,每次请求产生的时候,都是由容器调用。
容器创建一个 ServletRequest 对象喝 ServletResponse 对象,容器在调用 service 方法时候,传入这两个对象。
getservletInfo:
获取 servletInfo 信息的方法
destory:
servlet 实例在销毁之前调用的方法,用用作资源释放
2. 抽线的类 GenericServlet
源码如下:
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {private static final long serialVersionUID = 1L;private transient ServletConfig config;public GenericServlet() {}public void destroy() {}public String getInitParameter(String name) {return this.getServletConfig().getInitParameter(name);}public Enumeration<String> getInitParameterNames() {return this.getServletConfig().getInitParameterNames();}public ServletConfig getServletConfig() {return this.config;}public ServletContext getServletContext() {return this.getServletConfig().getServletContext();}public String getServletInfo() {return "";}public void init(ServletConfig config) throws ServletException {this.config = config;this.init();}public void init() throws ServletException {}public void log(String message) {ServletContext var10000 = this.getServletContext();String var10001 = this.getServletName();var10000.log(var10001 + ": " + message);}public void log(String message, Throwable t) {this.getServletContext().log(this.getServletName() + ": " + message, t);}public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;public String getServletName() {return this.config.getServletName();}
}
GenericServlet 抽象类,是对 Servlet 接口一些固定功能的粗糙实现,以及对 service 方法的再次抽象声明,并定义了一些其他相关功能方法。
private transit ServletConfig config:
初始化配置对象作为属性(transit 是一个特殊的关键字。当对象被序列化时,被 transit 修饰的变量不会被序列化,也就是不会被持久化存储或通过网络传输)
public GenericServle:
构造器,为了满足继承而准备
public void destory:
将 Servlet 中的抽象方法,重写为普通方法,在方法内部中没有任何实现的代码,称为 destory 的平庸实现 ==》 让子类可根据需要选择是否重写,实现销毁相关逻辑
public void init() {
this.config() = config;
this.init();
}
tomcat 在调用 init 方法时,会读取配置信息进入一个 ServletConfig 对象,并将该对象传入 init 方法。此方法将 config 对象存储为当前的属性,并且调用了重载的无参的 init 方法
public void inti:
重载的初始化方法,即我们重写初始化方法对应的方法。
public ServletConfig getServletConfig:
返回 ServletConfig 的方法
public abstract void service:
再次抽象声明 service 方法
3. HttpServlet 抽象类
在这个抽象里,侧重于 service 方法的处理
源代码较长,此处选部分重要的进行理解:
private static final String METHOD_DELETE = "DELETE";private static final String METHOD_HEAD = "HEAD";private static final String METHOD_GET = "GET";private static final String METHOD_OPTIONS = "OPTIONS";private static final String METHOD_POST = "POST";private static final String METHOD_PUT = "PUT";private static final String METHOD_TRACE = "TRACE";
上述属性,用于定义常见请求方式名的常量值
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest)req;response = (HttpServletResponse)res;} catch (ClassCastException var6) {throw new ServletException(lStrings.getString("http.non_http"));}this.service(request, response);}
request = (HttpServletRequest)req 和 response = (HttpServletResponse)res 都是参数的父转子操作(子类的方法属性更多一些~) 再调用重载的 service 方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();if (method.equals("GET")) {this.doGet(req, resp);} else if (method.equals("HEAD")) {this.doHead(req, resp);} else if (method.equals("POST")) {this.doPost(req, resp);} else if (method.equals("PUT")) {this.doPut(req, resp);} else if (method.equals("DELETE")) {this.doDelete(req, resp);} else if (method.equals("OPTIONS")) {this.doOptions(req, resp);} else if (method.equals("TRACE")) {this.doTrace(req, resp);} else {resp.sendError(501, errMsg);}}
在 protected 修饰的 service 方法中,先是获取了请求的方式,然后根据请求方式,调用对应的 do*** 方法
do*** 方法大同小异,这里以 doGet 为例:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String msg = lStrings.getString("http.method_get_not_supported");this.sendMethodNotAllowed(req, resp, msg);}
先获取对应的字符串,然后调调用 sendMethodNotAllowed 方法,即 sendError 方法,故意响应 405 请求方式不允许的信息。
4. 自定义 Servlet
public class servlet1 extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("servlet1 执行了");}
}
在自定义的 Servlet 中,实现接收用户请求信息,然后做出资源响应
补充:
如果我们自定义的 servlet 中,没有重写 service 方法,就会运行父类的 HttpServlet 中的 service 方法,在父类的 service 方法中,就会执行默认的 doGet 和 doPost 方法 ==》 响应 405
我们也可以自定义的 service 方法中,不重写 service 方法,直接重写 doGet 和 doPost 方法~
有些从程序员,推荐在自定义的 servlet 中重写 do*** 方法处理请求,理由:父类中的 service 方法中可能做了一些处理,如果我们直接重写 service 方法,父类中的 service 方法中的一些处理可能会失效。
但是,目前观察,直接重写 service 并不会有什么问题~
后续使用 SpringMVC 框架之后,我们则无需继承 HttpServlet,处理请求的方法也无需是 do*** 和 service 了
补充:在此处,我们自定义的 servlet 中,要么重写 service 方法,要么重写 do*** 方法~
如果同时重写了 service 和 do*** 方法,service 优先