当前位置: 首页 > news >正文

一篇文章彻底搞懂servlet容器

1. Servlet 容器

Servlet 容器可以理解为 Java Web 应用的“运行环境”和“管家”。我们写的 Servlet、Filter、Listener、Spring MVC 控制器等,最终都不是自己直接监听端口、解析 HTTP,而是交给 Servlet 容器来调度。

常见 Servlet 容器有Tomcat、Jetty、Undertow,其中 Tomcat 最常见。

Servlet 容器的核心职责包括:

  1. 监听网络端口

    例如 Tomcat 监听8080端口,接收客户端 HTTP 请求。

  2. 解析 HTTP 请求

    把底层网络数据解析成 Java 中的HttpServletRequest对象。

  3. 创建响应对象

    为当前请求创建对应的HttpServletResponse对象。

  4. 根据 URL 找到对应 Servlet

    根据配置或注解,例如@WebServlet("/hello"),找到应该处理该请求的 Servlet。

  5. 管理 Servlet 生命周期

    包括:

    加载 Servlet 类 ↓ 实例化 Servlet ↓ 调用 init() ↓ 请求到来时调用 service() ↓ service() 分发到 doGet() / doPost() ↓ 应用关闭时调用 destroy()
  6. 管理线程池

    每个请求通常会交给线程池中的一个工作线程处理。

  7. 管理 Web 应用上下文

    包括 Session、Cookie、Filter、Listener、静态资源等。

2.servlet的生命周期

一个 Servlet 通常经历三个核心阶段:

publicclassHelloServletextendsHttpServlet{@Overridepublicvoidinit(){// 容器创建 Servlet 后调用一次}@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp){// 每次 GET 请求调用}@Overridepublicvoiddestroy(){// 容器关闭或应用卸载时调用一次}}

重点是:Servlet 实例通常是单例的,但会被多个线程同时访问。

所以不要在 Servlet 成员变量里放请求级状态,例如:

public class BadServlet extends HttpServlet { private String username; // 危险:多个请求线程共享 protected void doGet(HttpServletRequest req, HttpServletResponse resp) { username = req.getParameter("username"); }

}
应该把请求相关数据放在局部变量、request、session 或业务对象里。

3. Servlet 容器和 Web 服务器的区别

很多人会混淆Web 服务器Servlet 容器

简单说:

类型例子主要职责
Web 服务器Nginx、Apache HTTP Server处理 HTTP、静态资源、反向代理
Servlet 容器Tomcat、Jetty、Undertow运行 Java Servlet/JSP/Web 应用
应用服务器WebLogic、WebSphere、JBoss/WildFlyServlet + EJB + JMS + JTA 等完整 Jakarta EE 能力

Tomcat 既能接收 HTTP 请求,也能运行 Servlet,所以经常被直接当作 Web 服务器使用。但严格来说,它主要是一个Servlet 容器

生产环境中常见架构是:

用户 ↓ Nginx ↓ Tomcat / Spring Boot 内嵌容器 ↓ Java 应用

Nginx 负责 HTTPS、负载均衡、静态资源、反向代理;Tomcat 负责运行 Java Web 应用。

4. Servlet 容器和程序中的 Servlet 是什么关系?

二者是:

Servlet 容器 = 管理者 / 调度者 / 运行环境 Servlet = 被管理的请求处理组件

也就是说,Servlet 本身不会直接监听端口,也不会自己接收 HTTP 请求。它需要运行在 Servlet 容器中,由容器负责调用。

例如:

@WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // 处理 /hello 请求 } }

当浏览器访问/hello时,流程大致是:

浏览器访问 /hello ↓ Tomcat 接收到 HTTP 请求 ↓ Tomcat 找到映射到 /hello 的 HelloServlet ↓ Tomcat 创建或复用 HelloServlet 实例 ↓ Tomcat 调用 service() ↓ service() 分发到 doGet() ↓ HelloServlet 处理请求并生成响应

所以:

Tomcat / Jetty / Undertow ↓ 管理、调度、调用 你的 Servlet

5. Spring Boot 里的 Servlet容器

在 Spring Boot Web 应用中,默认会内嵌一个 Servlet 容器,例如 Tomcat。

启动方式通常是:

java -jar app.jar

Spring Boot 启动时,会在 JVM 内部启动内嵌 Tomcat,并把 Spring MVC 的核心 Servlet,也就是DispatcherServlet,注册到 Servlet 容器中。

请求流程大致如下:

HTTP 请求 ↓ Tomcat ↓ Filter ↓ DispatcherServlet ↓ Controller ↓ Service ↓ Repository ↓ 响应

所以 Spring MVC 本质上仍然是运行在 Servlet 体系之上的。

6. Servlet 容器中的线程模型

以 Tomcat 为例,它通常维护一个线程池。

大致流程是:

客户端连接 ↓ Connector 接收请求 ↓ 工作线程处理请求 ↓ 调用 Filter / Servlet / Controller ↓ 返回响应

这意味着:如果你的业务代码里有很慢的数据库查询、远程调用、文件操作,就会占用 Tomcat 工作线程。

如果线程都被占满,新请求就只能排队,甚至超时。

所以在高并发场景下,常见调优点包括:

最大线程数 连接数 请求队列长度 超时时间 数据库连接池大小 接口响应时间

不过不要一上来就盲目调大线程数。线程越多,CPU 上下文切换、内存占用也越大。

Spring Boot 中 Servlet 是单例,请求对象是否独立?

是的。

在 Spring Boot / Servlet 容器中:

DispatcherServlet / Controller / Service:通常是单例,共享 HttpServletRequest:每个请求独立 HttpServletResponse:每个请求独立 高并发场景下可以理解为: 始终显示详情 请求 A → 线程 1 → requestA / responseA → 同一个 DispatcherServlet 请求 B → 线程 2 → requestB / responseB → 同一个 DispatcherServlet 请求 C → 线程 3 → requestC / responseC → 同一个 DispatcherServlet 也就是说: Servlet 是共享的 request 是每个请求独立的 response 是每个请求独立的

例如:

@GetMapping("/hello") public String hello(HttpServletRequest request) { String name = request.getParameter("name"); return "hello " + name; }

这里的 request 是当前请求自己的对象。请求 A 的参数不会跑到请求 B 中。

7.Controller 成员变量的并发问题

Spring Boot 中的 Controller 默认也是单例 Bean。

例如:

@RestController public class UserController { private String username; // 危险:所有请求共享 @GetMapping("/user") public String user(HttpServletRequest request) { username = request.getParameter("username"); return username; } }

在高并发情况下,可能出现:

请求 A 设置 username = "Tom" 请求 B 设置 username = "Jerry" 请求 A 返回时读到的可能已经变成 "Jerry" 这是因为 username 是 Controller 对象的成员变量,而 Controller 是单例的,所有请求共享同一个对象。

正确写法:

@RestController public class UserController { @GetMapping("/user") public String user(HttpServletRequest request) { String username = request.getParameter("username"); return username; }

}
局部变量存在于当前线程的栈中,不会被其他请求线程共享。

http://www.rkmt.cn/news/1450997.html

相关文章:

  • 【2026最新】ZLibrary官网镜像入口,一键直达
  • AI一键生成lz4解压工具,快速验证压缩文件处理方案
  • AI 生成关卡,还用游戏自己的物理证明它能通关:funplay-unity-mcp 实战
  • Zotero-Style:文献管理界面的可视化增强解决方案
  • GPT-5.5 核心能力落地与实战应用指南
  • 2507不锈钢铸件技术要点解析及优质供应商实测参考:不锈钢卡箍/不锈钢管件/不锈钢精密铸造/不锈钢船舶配件/不锈钢铸造件/选择指南 - 优质品牌商家
  • 计算机毕业设计之基于Python的火车票管理系统
  • OptiScaler:你的游戏画面还能更好吗?3个痛点1个解决方案
  • 用Makey Makey与Scratch打造《千与千寻》交互音乐盒:从电路原理到创意实现
  • 计算机毕业设计之基于大数据的个性化音乐推荐系统
  • 在欧拉系统上安装ToDesk 4.3.1.0,除了rpm -Uvh,这些细节和坑你踩过吗?
  • STM32F10x四路白炽灯交流调光工程包(含过零检测+硬件PWM触发)
  • 125K+ star 的 AI 爬虫神器:让你的 Agent 秒变网络达人
  • 终极指南:3步彻底解决腾讯游戏卡顿问题 - sguard_limit优化工具完整教程
  • GWAS分析中GLM vs. MLM怎么选?结合TASSEL实例聊聊模型适用场景
  • Sora 2非遗应用全解析,覆盖剪纸/皮影/侗歌等12类非遗形态的版权合规生成边界与伦理红线
  • Python通达信数据读取终极指南:3步搞定金融数据自动化处理
  • UE5 GAS实战:用GameplayTag实现技能BUFF的UI动态反馈(含完整蓝图节点)
  • 别再死记硬背pytest命令了!这份保姆级参数速查表,让你效率翻倍
  • AI赋能安全开发:在快马平台探索布丁密钥透与人工智能结合的创新实践
  • 迈向 “十五五” 数智新阶段:国央企如何以 5A 架构驱动 Data+AI 一体化融合
  • ESP32用I2S直连OV7670摄像头的可运行Arduino工程包
  • Compose中的副作用-状态与作用域
  • 金融文本分类技术演进:从TF-IDF到Qwen3-8B
  • Boltzmann-Shannon指数(BSI):熵理论在聚类评估中的创新应用
  • 2026珍珠棉技术选型推荐:白色珍珠棉/防震气泡袋/epe珍珠棉包装/epe珍珠棉气泡袋/靠谱供应商实测对比 - 优质品牌商家
  • 2026年Q2河南高性价比专科院校实测评测 - 优质品牌商家
  • 告别AT指令报错!手把手教你为ESP8266刷入MQTT固件,轻松连上阿里云
  • 别再乱用strtok了!C语言字符串分割的5个常见坑点与安全替代方案
  • 高考报志愿必看!计算机8大专业避坑全攻略