目录
[Servlet 的继承结构](#Servlet 的继承结构)
[1. 顶级的 Servlet 接口](#1. 顶级的 Servlet 接口)
[2. 抽线的类 GenericServlet](#2. 抽线的类 GenericServlet)
[3. HttpServlet 抽象类](#3. HttpServlet 抽象类)
[4. 自定义 Servlet](#4. 自定义 Servlet)
生命周期简介
什么是生命周期?
应用程序中的对象,不仅在空间上有层次结构的关系,在实践上也会因为处于运行过程中的不同阶段,而表现出不同状态和不同行为,这就是对象的生命周期
简单的叙述生命周期:就是对象在开始创建,到最后销毁的过程。
Servlet 容器:
Servlet 对象是 Servlet 容器创建的,生命周期方法都是由容器(我们目前使用 Tomcat)调用的。这一点和我们之前所编写的代码,由很大的不同!在今后的学习中,我们越来越多的对象,都要交给容器或框架来创建,越来越多的方法,都要由容器或框架来调用。
我们作为程序员,要尽可能的将经历放在业务逻辑的实现上!
Servlet 主要的生命周期执行特点:

生命周期测试
还在我们前面的项目工程中,开发一个测试代码:
java
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("构造器");
}
@Override
public void init() throws ServletException {
System.out.println("初始化");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("服务");
}
@Override
public void destroy() {
System.out.println("销毁");
}
}
在四处打印的地方打上断点:

debug 模式运行
为浏览器配置好 url 后,回车,发现代码卡在了"构造器"的断点处

一步一步向后运行代码

此时并没有销毁。并且,当我们在浏览器,刷新重新打开的时候,就只会再打印"服务"了~

停止项目后,会打印"销毁"

结论:
通过生命周期的测试,我们发现:Servlet 对象在容器中是单例的。但,容器又是需要并发的,处理用户请求的,每个请求在容器中,都会开启一个线程。
多个线程可能会使用相同的 Servlet 对象,但 Servlet 对象在容器中是单例的,所以 Servlety 的成员变量,在多个线程之中也是共享的 ==》 非常非常不建议,在 service 中修改成员变量,在并发请求的时候,会引发线程安全问题~
load-on-startup
我们在前面的 @WebServlet 提到过这个成员变量~
其值是一个数字,含义是:tomcat 在启动时,实例化该 servlet 的顺序。(如果顺序号冲突了,tomcat 会自动协调启动顺序~)
XML
<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 接口
源码如下:
java
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
源码如下:
java
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 方法的处理
源代码较长,此处选部分重要的进行理解:
java
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";
上述属性,用于定义常见请求方式名的常量值
java
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 方法
java
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 为例:
java
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
java
public class servlet1 extends HttpServlet {
@Override
protected 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 优先