一文搞懂 Servlet:定义、执行流程与生命周期全解析

一、Servlet是什么?

Servlet 是 Java 提供的一门动态 Web 资源开发技术,也是 JavaEE 规范中的重要组成部分。其本质是一个接口,开发者需要自定义类实现该接口,并由 Web 服务器(如 Apache Tomcat)负责运行和管理。

核心职责:

接收浏览器发送的 HTTP 请求 → 处理业务逻辑 → 将动态生成的响应结果返回给浏览器,从而在 B/S 架构下实现服务端逻辑的开发。


二、Servlet执行流程

当浏览器发起一个 HTTP 请求(例如 http://localhost:8080/web-demo/demo1)时,完整的执行流程如下:

  1. 浏览器将 HTTP 请求发送至 Web 服务器(Tomcat)。
  2. Tomcat 解析请求 URL,定位到对应的 Web 项目(web-demo)。
  3. 在项目中找到实现了 Servlet 接口的自定义类(例如 ServletDemo1)。
  4. Tomcat 自动创建该 Servlet 的实例,并调用其 service() 方法来处理请求。
  5. service() 方法执行完毕后,Tomcat 将生成的响应结果返回给浏览器。
  6. 浏览器解析响应数据并渲染页面。

关键问题解答

  • Servlet 由谁创建?方法由谁调用?
    Servlet 对象由 Web 服务器(Tomcat)负责创建,其所有生命周期方法(如 service())也均由服务器调用。开发者只需专注于接口逻辑的实现。
  • 服务器如何确保一定有 service() 方法?
    因为自定义 Servlet 类必须实现 Servlet 接口,而该接口强制定义了 service() 方法,因此服务器可以安全地对该方法进行调用。

三、Servlet生命周期

Servlet 运行于 Servlet 容器(即 Web 服务器)中,其生命周期完全由容器管理。整个过程可划分为 4 个核心阶段:

阶段 触发时机 执行方法 执行次数 核心作用
加载和实例化 默认首次访问时(可通过配置修改为启动时) - 1次 创建 Servlet 对象
初始化 实例化完成后立即执行 init() 1次 加载配置、建立连接等初始化工作
请求处理 每次接收到访问请求时 service() N次 处理具体的 HTTP 请求并生成响应
服务终止 服务器关闭或进行内存释放时 destroy() 1次 释放资源,对象等待被垃圾回收

loadOnStartup 配置说明

可以通过 @WebServlet(urlPatterns = "/demo", loadOnStartup = 1) 注解中的 loadOnStartup 属性来控制 Servlet 的创建时机:

  • 负整数(默认值) :懒加载模式,仅在 Servlet 第一次被访问时才创建对象。
  • 0 或正整数 :饿汉式加载,Tomcat 启动时 即创建 Servlet 对象。数字越小,启动时被初始化的优先级越高。
深度解析:loadOnStartup 的优先级到底指什么?

结论先行:该优先级仅代表服务器启动时 Servlet 的实例化与初始化顺序,与 URL 访问匹配优先级完全无关。

1. 核心规则

  • 负数 / 不配置:默认懒加载,首次访问时才创建。
  • 0 或正整数 :Tomcat 启动时立即创建。数字越小,越早被实例化和执行 init() 方法。

2. 代码示例说明

复制代码
@WebServlet(urlPatterns = "/a", loadOnStartup = 1) // 第二个创建
public class ServletA{}

@WebServlet(urlPatterns = "/b", loadOnStartup = 2) // 第三个创建
public class ServletB{}

@WebServlet(urlPatterns = "/c", loadOnStartup = 0) // 第一个创建
public class ServletC{}

Tomcat 启动时的创建顺序为:ServletC (0) → ServletA (1) → ServletB (2)

3. 应用场景

  • 需要在项目启动时预加载配置、建立数据库连接池或初始化缓存的 Servlet,应将 loadOnStartup 设置为较小的正整数(如 1)。
  • SpringMVC 的 DispatcherServlet 必须配置此项,以确保其在启动时就绪。

四、Servlet核心方法详解

Servlet 接口定义了 5 个核心方法,职责分明:

  1. void init(ServletConfig config)
    初始化方法。在 Servlet 对象创建后由容器调用,仅执行 1 次 。参数 ServletConfig 可用于获取 Servlet 的初始化配置参数。
  2. void service(ServletRequest req, ServletResponse res)
    核心服务方法。每次请求访问该 Servlet 时都会被调用 。业务逻辑的核心代码通常在此方法中编写。ServletRequest 封装了请求数据,ServletResponse 用于构建响应信息。
  3. void destroy()
    销毁方法。当 Servlet 被容器决定移除时(如服务器关闭)调用,仅执行 1 次。用于释放文件句柄、数据库连接等系统资源。
  4. ServletConfig getServletConfig()
    用于返回当前 Servlet 的 ServletConfig 对象。
  5. String getServletInfo()
    用于返回 Servlet 的描述性信息,如作者、版本等。

五、Servlet体系结构与HttpServlet原理

直接实现 Servlet 接口需要实现所有 5 个方法,开发效率不高。因此,Java Servlet API 提供了更为便捷的抽象继承体系:

  • Servlet 接口:根基接口,定义核心规范。
  • GenericServlet 抽象类 :对 Servlet 接口的通用实现,提供了除 service() 外所有方法的默认实现,并将 service() 保留为抽象方法。
  • HttpServlet 抽象类 :继承自 GenericServlet,专门针对 HTTP 协议 进行了封装。

HttpServlet 使用步骤

在实际 B/S 项目开发中,我们通常通过继承 HttpServlet 类来创建自定义 Servlet:

  1. 定义一个类,继承 javax.servlet.http.HttpServlet
  2. 根据需要重写 doGet()doPost() 方法。

HttpServlet 内部原理

HttpServlet 重写了 service() 方法。其内部逻辑会自动解析请求的 HTTP 方法(GET、POST 等),并根据方法类型分发 到对应的 doXxx() 方法中:

  • GET 请求 → 调用 doGet()
  • POST 请求 → 调用 doPost()

这使开发者无需在 service() 方法中手动编写冗长的 if-else 判断逻辑。

直观对比:HttpServlet 到底简化了什么?

如果直接实现 Servlet 接口,我们需要在 service() 方法中手动判断请求类型,写出如下臃肿的代码:

复制代码
public class OldWayServlet implements Servlet {
    // 必须实现 init, destroy, getServletConfig, getServletInfo 等方法 ...

    @Override
    public void service(ServletRequest req, ServletResponse res) {
        // 1. 强制类型转换,因为 ServletRequest 没有获取 HTTP 方法的 API
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 2. 获取请求方式
        String method = request.getMethod();

        // 3. 臃肿的 if-else 判断
        if ("GET".equals(method)) {
            // 处理 GET 请求的逻辑
            doGetLogic(request, response);
        } else if ("POST".equals(method)) {
            // 处理 POST 请求的逻辑
            doPostLogic(request, response);
        } else if ("PUT".equals(method)) {
            // 处理 PUT 请求 ...
        }
        // ... 还要处理 HEAD, DELETE, OPTIONS 等
    }

    private void doGetLogic(HttpServletRequest req, HttpServletResponse resp) { /* ... */ }
    private void doPostLogic(HttpServletRequest req, HttpServletResponse resp) { /* ... */ }
}

而使用 HttpServlet 之后,代码变得极其简洁:

复制代码
public class ModernServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        // 专注处理 GET 请求,无需任何类型转换和 if-else 判断
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        // 专注处理 POST 请求
        doGet(req, resp); // 可以方便地复用逻辑
    }
}

💡 一句话总结: HttpServlet 实际就是简化了原始 Servlet 接口中判断请求方法的臃肿代码,让开发者可以更专注地编写针对不同 HTTP 方法的业务逻辑。


六、Servlet urlPattern 配置规则

@WebServlet 注解中,urlPatterns(或 value)属性用于配置 Servlet 的访问路径。它支持以下 4 种匹配模式,且遵循明确的优先级顺序:

匹配模式 配置示例 访问示例 优先级 说明
1. 精确匹配 /user/select .../user/select 最高 路径必须完全一致。
2. 目录匹配 /user/* .../user/aaa .../user/bbb 第二 匹配 /user/ 开头的所有路径。
3. 扩展名匹配 *.do .../aaa.do .../bbb.do 第三 匹配以 .do 结尾的所有路径。注意:不能以 / 开头。
4. 任意匹配 //* 所有未匹配路径 最低 详见下方深度解析。

//* 的核心区别(表格速览)

路径 行为描述 应用场景
/ 覆盖 Tomcat 内置的 DefaultServlet。当所有其他 url-pattern 都无法匹配时,请求会交由该 Servlet 处理。 SpringMVC 的前端控制器 DispatcherServlet 常配置为此路径。
/* 拦截所有请求 ,优先级高于 /。包括 .jsp.html.css 等静态资源请求都会被拦截。 通常用于全局过滤器(Filter),极少用于 Servlet 映射。

深度解析://* 对静态资源影响的终极区别

1. 为什么配置 / 会导致静态资源无法访问?

这是 90% 的新手都会遇到的经典问题。其根本原因在于 Tomcat 内置了一个名为 DefaultServlet 的 Servlet,专门负责处理静态资源

  • Tomcat 的默认配置
    在 Tomcat 的全局配置文件 conf/web.xml 中有如下配置:

    复制代码
    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    DefaultServlet 的映射路径正是 /。它的作用就是读取服务器磁盘上的 .html.css.js、图片等文件并返回给客户端。

  • 覆盖机制
    当你在项目中定义了 @WebServlet("/") 时,应用级别的配置会覆盖 Tomcat 的全局默认配置 。此时,所有本应交给 DefaultServlet 处理的静态资源请求,都进入了你的自定义 Servlet。

  • 后果
    你的自定义 Servlet 只处理业务逻辑,并不会去读取和返回静态文件。因此,浏览器请求静态资源时会收到 404 错误或进入了错误的业务逻辑。

  • 通俗举例:为什么原来的 http://.../aaa/a.html 突然不能用了?

    • 原来(无覆盖时) :访问 /aaa/a.html → 无精确匹配的 Servlet → 路由给 Tomcat 的 DefaultServlet → 找到文件并返回 ✅
    • 覆盖后(写了 @WebServlet("/") :访问 /aaa/a.html → 无精确匹配的 Servlet → 路由给你的 Servlet → 你的 Servlet 无法处理静态文件 → 报错或404 ❌

一句话总结:/ 路径只能由一个 Servlet 接管。你接盘后,原来的"静态资源管理员"就下岗了。

2. /* 模式的真正威力
  • /* 是一个真正意义上的全局通配符。
  • 它的优先级极高,会无条件拦截所有请求 ,包括 .jsp.html.png.js 等。
  • 后果 :即使没有覆盖 DefaultServlet,所有请求也会先被 /* 拦截。若处理不当,静态资源将彻底失效,且恢复起来更为复杂。
3. 匹配优先级总结

精确路径 > 目录路径 > 扩展名路径 > /* > /


七、Servlet 的两种配置方式

1. 注解配置(推荐,Servlet 3.0+)

无需编写 web.xml 文件,直接在类上使用 @WebServlet 注解即可完成配置。

复制代码
@WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
public class ServletDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 处理 GET 请求逻辑
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 处理 POST 请求逻辑,可复用 doGet
        doGet(req, resp);
    }
}

2. XML 配置(传统方式,Servlet 3.0 之前)

在项目的 WEB-INF/web.xml 文件中进行集中配置。

步骤:

  1. 编写 Servlet 类。

  2. web.xml 中注册 Servlet 并配置映射路径。

    <web-app> <servlet> <servlet-name>demo5</servlet-name> <servlet-class>com.itheima.web.servlet.ServletDemo5</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
    复制代码
     <!-- 2. 配置 Servlet 映射 -->
     <servlet-mapping>
         <servlet-name>demo5</servlet-name>
         <url-pattern>/demo5</url-pattern>
     </servlet-mapping>
    </web-app>

web.xml 核心说明:

  • 该文件必须放置在 WEB-INF/ 目录下,是 Tomcat 的强制约定。
  • Tomcat 启动时会自动扫描并加载此文件中的配置。

八、Servlet 与 SpringMVC 的关系

许多初学者容易混淆 @Controller 注解的类与 Servlet 的关系,这里给出明确界定:

  • Tomcat 只认 Servlet 接口。 我们编写的带 @Controller 的类本身并不是 Servlet
  • SpringMVC 中,真正实现 Servlet 接口的是 DispatcherServlet(前端控制器)。
  • DispatcherServlet 是 Tomcat 中唯一的中央调度 Servlet 。所有进入 SpringMVC 应用的请求,首先会被 DispatcherServlet 拦截,再由它根据 @RequestMapping 等规则分发给具体的 @Controller 处理。

核心协作流程

  1. Tomcat 启动,加载 DispatcherServlet
  2. 浏览器发起请求 → Tomcat 将请求交给 DispatcherServlet
  3. DispatcherServlet 解析 URL,调用对应的 @Controller 方法。
  4. @Controller 返回处理结果 → DispatcherServlet 将结果响应给浏览器。

九、总结

Servlet 是 Java Web 开发的基石,也是 SpringMVC、Struts 等上层框架的底层核心。深入理解 Servlet 的工作原理、生命周期及配置细节,可以帮助我们:

  1. 透彻理解 Web 容器处理请求的完整流程。
  2. 快速诊断和解决项目中的请求拦截、路径映射错误等问题。
  3. 为深入学习 SpringMVC 等框架的底层实现打下坚实基础。

附录:经典面试题速览

  1. Servlet 生命周期是什么?
    答:实例化 → 初始化(init())→ 服务(service())→ 销毁(destroy())。
  2. //* 的区别是什么?
    答:/ 会覆盖 Tomcat 默认的静态资源 Servlet,拦截其他规则未匹配的请求;/* 优先级更高,会拦截所有请求,包括静态资源。
  3. HttpServlet 中 service() 方法的作用是什么?
    答:自动解析请求的 HTTP 方法,并将其分发到对应的 doGet()doPost() 等处理方法中。
  4. Servlet 是单例还是多例?
    答:Servlet 是单例的,服务器全局仅创建一个实例。因此,应避免在 Servlet 中定义线程不安全的成员变量。
  5. loadOnStartup 的作用和优先级指什么?
    答:配置 Servlet 对象的创建时机。负数代表懒加载,0/正整数代表启动时加载,数字越小启动时初始化的顺序越靠前。它与 URL 的访问优先级无关。
  6. 为什么配置 @WebServlet("/") 后静态资源无法访问?
    答:因为覆盖了 Tomcat 自带的 DefaultServlet,导致静态资源请求没有合适的处理器进行响应。
相关推荐
人道领域1 天前
【黑马点评日记02】Redis缓存优化:商户查询性能提升百倍
java·spring boot·spring·servlet·tomcat·intellij-idea
m0_744724931 天前
Servlet原理
servlet
lifewange1 天前
Jenkins Windows MSI 安装包完整安装教程
windows·servlet·jenkins
我登哥MVP1 天前
【SpringMVC笔记】 - 6 - RESTFul编程风格
java·spring boot·spring·servlet·tomcat·maven·restful
我登哥MVP2 天前
【SpringMVC笔记】 - 5 - View
java·spring boot·spring·servlet·tomcat·maven·intellij-idea
我登哥MVP2 天前
【SpringMVC笔记】 - 4 - 三个域对象
java·spring boot·spring·servlet·tomcat·maven·intellij-idea
HoneyMoose3 天前
Jenkins Cloudflare 部署提示错误
java·servlet·jenkins
我登哥MVP3 天前
【SpringMVC笔记】 - 2 - @RequestMapping
java·spring boot·spring·servlet·tomcat·intellij-idea·springmvc
我登哥MVP3 天前
【SpringMVC笔记】 - 3 - 获取请求数据
java·spring boot·spring·servlet·tomcat·maven·intellij-idea