Tomcat 是Servlet 规范的「实现者」

Servlet 是 Java 定义的一套「Web 服务端接口规范」(JSR 315 等),目的是统一 “Web 容器(如 Tomcat/Jetty)” 和 “业务代码” 的交互规则 —— 就像 “接口和实现类” 的关系:

  • Servlet 规范定义了「服务端如何接收请求、处理请求、返回响应」的标准接口(Servlet 接口);

  • Tomcat 是 Servlet 规范的「实现者」(符合 Servlet 容器标准),它内置了 Servlet 规范的核心实现逻辑,能识别并执行遵循该规范的业务类(如自定义的 HttpServlet 子类)。

只要你的类遵循 Servlet 规范(如继承 HttpServlet),Tomcat 就能通过反射实例化该类,并按照规范调用其生命周期方法 —— 这也是 “跨容器兼容” 的核心(换 Jetty 也能跑,因为都遵循 Servlet 规范)。

Servlet 核心链路

前端发送 HTTP 请求 → Tomcat Connector 监听端口 → 解析请求报文为 HttpServletRequest 对象 → 
Tomcat 找到对应的 Servlet 实例 → 调用 Servlet 的 service 方法 → 业务代码处理(获取请求参数、执行业务逻辑)→ 
通过 HttpServletResponse 封装响应报文 → Tomcat 把响应转回 HTTP 格式返回给前端

Sevlet实现对接口+抽象类设计模式的应用

1、Servlet 接口是规范的根,但它的方法太 “底层”(如 init(ServletConfig config)、service(ServletRequest req, ServletResponse res)),实际开发中很少直接实现,而是通过「抽象类封装」降低开发成本,这也是 Java 设计模式中 “接口 + 抽象类” 的典型应用。

2、在 javax.servlet 和 javax.servlet.http 包中,主要包含以下三个关键角色:

1)Servlet 接口 (Interface)

2)GenericServlet 抽象类 (Abstract Class)

enericServlet 是 Servlet 接口的「通用抽象实现」,它是一个协议无关的抽象类。它处理了 Servlet 生命周期中通用的部分(初始化、销毁、配置获取),只把最核心的“如何处理请求”(service 方法)留给子类去实现。

3)HttpServlet 抽象类 (Abstract Class)

GenericServlet 是 “通用的”(不绑定 HTTP 协议),而我们开发 Web 应用都是基于 HTTP 协议,因此 HttpServlet 继承 GenericServlet 后,做了「HTTP 协议的专属适配」,这是实际开发中最常用的类。因为 99% 的 Web 应用都是基于 HTTP 协议的。

下面我们就来好好的认识他们的实现:

=====》

1. GenericServlet:对 Servlet 接口的 “基础封装”

GenericServlet:对 Servlet 接口的 “基础封装”核心做了 3 件事:

  • ServletConfig(Servlet 配置信息,如初始化参数)保存为成员变量,提供 getServletConfig() 等便捷方法,避免每次手动传参;

  • Servlet 接口中 “生命周期无关” 的方法(如 getServletInfo())做了空实现,只把核心的 service() 方法保留为抽象方法(强制子类实现);

  • 补充了通用功能(如日志方法 log()),简化子类开发。

简单说:GenericServlet 帮你 “处理了 Servlet 接口的通用琐事”,让你只需要关注核心的 service() 方法。

2. HttpServlet:对 GenericServlet 的 “HTTP 专属封装”

实现:

  • 重写 service(ServletRequest req, ServletResponse res) 方法,先把参数强转为 HttpServletRequest/HttpServletResponse(HTTP 专属的请求 / 响应对象),再调用重载的 service(HttpServletRequest req, HttpServletResponse resp)

  • service(HttpServletRequest req, HttpServletResponse resp) 中,解析 HTTP 请求的「方法类型」(GET/POST/PUT/DELETE 等),然后分发到对应的 doGet()/doPost()/doPut() 等方法;

  • 你无需重写 service(),只需重写 doGet()/doPost() 即可(这是实际开发中最常用的方式)。

// HttpServlet 是普通类,继承 GenericServlet(GenericServlet 是抽象类)
public class HttpServlet extends GenericServlet {
​
    // 1. 重写 GenericServlet 的 service 方法(Servlet 接口的核心方法)
    @Override
    public void service(ServletRequest req, ServletResponse res) 
            throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        // 强转为 HTTP 专属的请求/响应对象
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("非HTTP请求");
        }
        // 调用 HTTP 专属的 service 方法
        service(request, response);
    }
​
    // 2. HTTP 专属的 service 方法(核心分发逻辑)
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 获取 HTTP 请求方法(GET/POST/PUT/DELETE 等)
        String method = req.getMethod();
        // 根据请求方法分发到对应的 doXXX 方法
        if (method.equals(METHOD_GET)) {
            doGet(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } 
        // 省略 HEAD/OPTIONS/TRACE 等方法的分发逻辑...
        else {
            // 默认处理:返回 405 Method Not Allowed
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, errMsg);
        }
    }
​
    // 3. doXXX 系列方法:提供默认实现(而非抽象方法)
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 默认实现:返回 405 Method Not Allowed
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
​
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 默认实现:同样返回 405
        String msg = lStrings.getString("http.method_post_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
​
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 同理:默认 405
        String msg = lStrings.getString("http.method_put_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
​
    // 省略 doDelete/doHead/doOptions 等方法...
}

tomcat管理servlet的生命周期

生命周期阶段 执行方法 触发时机 执行次数 Tomcat 底层逻辑
实例化 构造器 ① 第一次请求该 Servlet 时;② 容器启动时(配置 load-on-startup) 1 次 Tomcat 通过反射调用无参构造器创建 Servlet 实例(默认单例,所有请求共用一个实例)
初始化 init() 实例化完成后立即执行 1 次 执行 init(ServletConfig),GenericServlet 会保存 ServletConfig,供后续使用
处理请求 service() 每次前端发送匹配该 Servlet 的请求时 N 次 Tomcat 为每个请求分配独立线程调用 service (),因此需注意 Servlet 线程安全问题
销毁 destroy() Tomcat 容器关闭 / 重启时 1 次

ServletConfig

Servlet 的 “专属配置对象” 1、本质:Tomcat 为每个 Servlet 实例创建的「专属配置载体」,是 Servlet 规范定义的接口,Tomcat 提供 StandardServletConfig 实现; 2、使用逻辑:Tomcat 在 init 阶段通过反射传给 Servlet,开发者可通过 getServletConfig().getInitParameter("xxx") 获取专属配置; 3、作用域:仅属于当前 Servlet 实例,不同 Servlet 的 ServletConfig 相互隔离。

ServletContext

Web 应用的 “全局上下文” 1、本质:Tomcat 为每个 Web 应用(Context 组件)创建的「全局唯一对象」,是 Servlet 规范定义的接口,Tomcat 提供 StandardServletContext 实现; 2、核心数据: Web 应用的全局初始化参数(<context-param>); Web 应用的根路径(如 /myapp); 资源路径(如 WEB-INF/classes); 3、所有 Servlet 共享的属性(setAttribute/getAttribute) 创建时机:Web 应用启动时(Context 初始化),Tomcat 创建 ServletContext,所有 Servlet 的 ServletConfig 都持有该对象的引用; 作用域:整个 Web 应用,所有 Servlet 共享,是 Servlet 间通信的 “全局容器”。

拓展

一、SpringBoot 内置 Tomcat 的核心机理

1、SpringBoot和 Tomcat的关系

Tomcat 是一个服务器,Java 应用后端都需要有一个服务器”

它的职责正是你所说的:调配资源(管理线程池)、监听端口、解析请求、封装响应。

若spirngboot项目也逃不过这个原理,其内部就是自嵌了tomcat服务器;才能够让后端程序进行工作

,但 Spring Boot 其实不仅限于 Tomcat。它通过抽象层支持多种内嵌服务器,你可以通过修改依赖轻松切换

2、SpringBoot 打包时对tomcat的处理

SpringBoot 打包时会将 Tomcat 相关依赖(如 tomcat-embed-core、tomcat-embed-webapp 等)打入最终的 jar 包,启动 SpringBoot 应用时,通过代码启动内置 Tomcat 服务器,无需将 war 包放入外部 Tomcat 的 webapps 目录,也无需依赖外部 Tomcat 的配置。

二、两种打包 / 运行方式的对比

1、什么是 WAR 包?

全称:Web Application ARchive(Web 应用归档文件)。

本质:它其实就是一个特定结构的 ZIP 压缩包(你可以直接把后缀 .war 改成 .zip 解压看看)。

核心结构:WAR 包严格遵循 Java EE (现 Jakarta EE) 的 Servlet 规范,内部必须包含特定的目录结构:

  • /WEB-INF/安全目录,浏览器无法直接访问。

    • web.xml部署描述符(传统项目必备,定义 Servlet 映射、过滤器等,Spring Boot 通常不需要这个)。

    • /classes/:存放编译后的后端 .class 文件。

    • /lib/:存放项目依赖的第三方 JAR 包。

  • 其他根目录:存放 HTML、CSS、JS、图片等静态资源。

运行方式:不能独立运行。它必须被“投喂”给一个外部的 Servlet 容器(如 Tomcat, Jetty, WebLogic, WebSphere)。

===》

在 Spring Boot 出现之前(大约 2014 年以前),Java Web 开发的标准模式就是“代码与容器分离”。公司会维护一套统一的、经过调优的 Tomcat 集群,开发人员只负责写代码并打成 WAR 包,运维人员负责把 WAR 包部署到现有的容器中。这种方式便于统一管理和监控。

2、什么是 JAR 包?

全称:Java ARchive(Java 归档文件)。

本质:同样是一个 ZIP 压缩包,但结构更简单、更通用。

核心结构

  • /META-INF/MANIFEST.MF:清单文件,指定程序的入口类(Main-Class)。

  • 各种 .class 文件和资源文件。

运行方式:

1)普通 JAR:通常作为类库被其他项目引用。

2)可执行 JAR (Executable JAR):这是 Spring Boot 的关键。它包含了一个 main 方法,可以通过命令 java -jar app.jar 直接运行。

====》

Spring Boot 的 JAR:这是一种特殊的“胖 JAR”(Fat Jar)或“可执行 JAR”。它不仅包含你的代码,还把 Tomcat、Spring 框架、所有依赖库 全部打包进了这一个 JAR 文件里。启动时,它自己就在内存中启动了一个 Tomcat。

3、两种包的对比
特性 WAR 包 (Web Application Archive) JAR 包 (Spring Boot Executable Jar)
主要用途 传统 Java Web 应用 微服务、Spring Boot 应用、工具库
依赖容器 强依赖。必须部署到外部安装的 Tomcat/Jetty 等 不依赖。内部自嵌容器,独立运行
启动命令 无直接命令,需启动外部容器 (如 startup.sh) java -jar your-app.jar
内部结构 必须有 WEB-INF, web.xml 等标准目录 结构灵活,Spring Boot 有特殊的嵌套结构
部署流程 复杂。需先装容器 -> 配置容器 -> 复制 WAR -> 重启容器 极简。上传 JAR -> 运行命令即可
端口配置 通常在容器的 server.xml 中统一配置 在应用的 application.properties 中独立配置
适用场景 单体大应用、需要严格统一容器管理的传统企业 微服务架构、云原生、DevOps、快速迭代

总结

1、本文介绍了tomcat实现了servelet规范,可自动管理servlet的生命周期;又介绍了servlet的核心链路(从前端请求到tomcat响应);然后顺势分享了 sevlet实现对于Java 设计模式中 “接口 + 抽象类” 的典型应用:介绍GenericServlet是继承Servlet的抽象类,提供了service()抽象方法;又介绍了HttpServlet继承了GenericServlet ,从而做了「HTTP 协议的专属适配」(指出其对service方法签名的重写,及一般在其中要做的事情);

2、又介绍了tomcat管理servlet生命周期的四个阶段(实例化,初始化,处理请求,销毁)的执行方法,触发时机,执行次数,tomcat底层原理;

3、介绍了ServletConfig的本质(每个servlet的专属配置载体),使用逻辑,作用域(不同 Servlet 的 ServletConfig 相互隔离);然后介绍了ServletContext的本质(每个web应用的全局唯一对象),核心数据,创建时机,作用域(所有servlet共享,是servlet间通信的“全局容器”)。

4、又指出了SpringBoot和 Tomcat的关系 ,以及SpringBoot 打jar包时会将 Tomcat 相关依赖一起打包,从而带来的方便。

5、介绍了两种包格式:war包(Web Application ARchive)和jar包(Java ARchive)的本质(一个zip压缩包),核心结构,运行方式;以及jar包的分类(普通jar包,可执行jar包)。又对比了两种包的主要用途,对容器的依赖,启动命令,内部结构,部署流程,端口配置,使用场景。

Logo

助力合肥开发者学习交流的技术社区,不定期举办线上线下活动,欢迎大家的加入

更多推荐