Javaweb - 10.3 Servlet 生命周期

目录

生命周期简介

生命周期测试

load-on-startup

补充:defaultServlet

[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 优先

完!

相关推荐
凌辰揽月21 小时前
Servlet学习
hive·学习·servlet
真实的菜2 天前
Jenkins 插件深度应用:让你的CI/CD流水线如虎添翼 [特殊字符]
servlet·ci/cd·jenkins
天天爱吃肉82182 天前
ZigBee通信技术全解析:从协议栈到底层实现,全方位解读物联网核心无线技术
python·嵌入式硬件·物联网·servlet
Zz_waiting.3 天前
Javaweb - 10.1 Servlet
servlet·tomcat·javaweb
真实的菜3 天前
Jenkins生态与拓展:构建现代化DevOps工具链的终极指南
servlet·jenkins·devops
编程乐学(Arfan开发工程师)13 天前
73、单元测试-断言机制
服务器·数据库·servlet·单元测试·sqlite·log4j·mybatis
程序员的世界你不懂14 天前
将生成的报告通过jenkins发送邮件通知
java·servlet·jenkins
期待のcode15 天前
servlet前后端交互
servlet·交互
武子康15 天前
Java-52 深入浅出 Tomcat SSL工作原理 性能优化 参数配置 JVM优化
java·jvm·后端·servlet·性能优化·tomcat·ssl