第68篇:JavaWeb核心技术之Servlet(2026版)
📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第67篇:JVM面试压轴题 |
➡️ 下一篇:第69篇:JSP与EL表达式(待发布)
🗺️ 本文阅读地图(3 分钟速览)
JVM调优专题已收官,本篇开启JavaWeb专题。Servlet是Java Web开发的“地基”——Spring MVC、Spring Boot底层都是Servlet。理解Servlet,才能真正理解Web框架:
| 模块 | 核心问题 | 一句话回答 |
|---|---|---|
| Servlet是什么 | Java Web的“地基”是什么? | 运行在Web容器中的Java类,处理HTTP请求并返回响应 |
| 生命周期 | Servlet从生到死经历什么? | 加载→实例化→init()→service()→destroy() |
| 核心对象 | 请求和响应用什么对象? | HttpServletRequest(请求)、HttpServletResponse(响应) |
| 三大组件 | Servlet规范还有哪些组件? | Servlet + Filter + Listener,三者分工协作 |
| 异步处理 | Servlet 3.0带来了什么? | AsyncContext实现非阻塞处理,释放容器线程 |
| Servlet vs Spring MVC | 现在还用Servlet吗? | Spring MVC底层仍是Servlet,但封装了细节 |
| Jakarta EE迁移 | javax包去哪了? | 从javax.servlet迁移到jakarta.servlet(Jakarta EE 9+) |
| 面试最爱问 | 高频考点有哪些? | 见文末 🎤 小节 |
一、核心知识点
1. Servlet是什么?
Servlet(Server Applet)是Java EE(现Jakarta EE)规范中定义的服务器端组件,用于接收客户端请求并返回响应。它不依赖特定的平台或协议,最常用的是HTTP Servlet。
💡本质:Servlet就是一个运行在Web容器(Tomcat、Jetty等)中的Java类,遵循特定的生命周期规范。
2. Servlet的核心地位
Servlet是Java Web一切框架的底层基石:
无论是Spring MVC的DispatcherServlet,还是Spring Boot的内嵌Tomcat,底层都是Servlet。理解Servlet,是理解一切Java Web框架的前提。
3. 2026年重要背景:Jakarta EE迁移
从Jakarta EE 9开始,所有javax.servlet包名改为jakarta.servlet。Servlet API版本演进:
- Servlet 4.0(Java EE 8):
javax.servlet - Servlet 5.0+(Jakarta EE 9+):
jakarta.servlet - Servlet 6.0(Jakarta EE 10+):去除
SecurityManager相关API
💡面试注意:2026年,新建项目应使用
jakarta.servlet,老项目迁移需注意包名变更。
4. Servlet三大核心组件
| 组件 | 作用 | 典型场景 |
|---|---|---|
| Servlet | 处理具体业务请求 | 用户登录、订单查询 |
| Filter | 请求/响应预处理和后处理 | 日志记录、编码过滤、权限校验 |
| Listener | 监听Web应用生命周期事件 | 应用启动初始化、Session管理 |
二、通俗讲解(1分钟开心学)
把Servlet想象成餐厅的“服务员”
客户端(浏览器):顾客,坐在桌前要点菜(发请求)。
Servlet:服务员,负责接收顾客的点菜单(请求),去厨房(业务逻辑)拿菜,再把菜端给顾客(响应)。每个服务员只服务一位顾客(一个请求对应一个Servlet实例的service方法调用)。
Filter:餐厅门口的迎宾员。每个顾客进来,先经过迎宾员检查——带口罩了吗?有预约吗?(请求过滤),走的时候迎宾员再说声“欢迎下次光临”(响应后处理)。
Listener:餐厅的传感器。餐厅开门了(应用启动),传感器记录;有顾客进来了(Session创建),传感器记录;餐厅关门了(应用关闭),传感器记录。
Web容器(Tomcat):餐厅的管理系统,负责招聘服务员(创建Servlet实例)、安排工作(调用生命周期方法)、辞退服务员(销毁Servlet)。
一句话总结:Filter管“进出门”,Servlet管“核心服务”,Listener管“全程监控”。
三、Servlet生命周期
Servlet生命周期由Web容器(Tomcat等)全权管理,开发者不需要手动newServlet对象。
3.1 生命周期四阶段
各阶段详解:
| 阶段 | 方法 | 调用次数 | 触发时机 | 说明 |
|---|---|---|---|---|
| 加载与实例化 | 构造方法 | 1次 | 第一次请求到达(默认)或容器启动时 | 默认懒加载,可通过<load-on-startup>改为容器启动时加载 |
| 初始化 | init() | 1次 | 实例化后立即调用 | 加载数据库连接池、配置文件等资源 |
| 服务 | service() | 每次请求 | 每次HTTP请求到达 | 根据请求方法分发到doGet()/doPost()等 |
| 销毁 | destroy() | 1次 | 容器关闭或应用卸载时 | 释放资源,如关闭数据库连接 |
💡重要理解:Servlet是单例多线程——整个应用只有一个Servlet实例,但每个请求会创建一个新线程调用
service()方法。因此Servlet实例变量不是线程安全的!
3.2 代码示例
importjakarta.servlet.*;importjakarta.servlet.http.*;importjakarta.servlet.annotation.*;importjava.io.*;@WebServlet("/hello")publicclassHelloServletextendsHttpServlet{// ① 初始化(执行一次)@Overridepublicvoidinit()throwsServletException{System.out.println("Servlet初始化,加载资源");}// ② 服务(每次请求执行)@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{resp.setContentType("text/html;charset=UTF-8");PrintWriterout=resp.getWriter();out.println("<h1>Hello, Servlet!</h1>");out.println("<p>当前时间:"+newjava.util.Date()+"</p>");}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// POST请求处理doGet(req,resp);}// ③ 销毁(执行一次)@Overridepublicvoiddestroy(){System.out.println("Servlet销毁,释放资源");}}web.xml配置方式(传统,仍用于Servlet 6.0):
<web-app><servlet><servlet-name>hello</servlet-name><servlet-class>com.example.HelloServlet</servlet-class><load-on-startup>1</load-on-startup><!-- 容器启动时加载 --></servlet><servlet-mapping><servlet-name>hello</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping></web-app>四、核心对象:Request & Response
4.1 HttpServletRequest——请求对象
| 方法 | 作用 |
|---|---|
getParameter(String name) | 获取单个请求参数 |
getParameterMap() | 获取所有请求参数Map |
getHeader(String name) | 获取请求头 |
getSession() | 获取/创建Session |
getRequestDispatcher() | 请求转发 |
setAttribute(String, Object) | 设置请求属性(转发传递数据) |
startAsync() | Servlet 3.0启动异步处理 |
4.2 HttpServletResponse——响应对象
| 方法 | 作用 |
|---|---|
setContentType(String) | 设置响应内容类型(如text/html;charset=UTF-8) |
setStatus(int) | 设置HTTP状态码 |
getWriter() | 获取字符输出流(输出HTML) |
getOutputStream() | 获取字节输出流(输出文件/图片) |
sendRedirect(String) | 重定向(302) |
addCookie(Cookie) | 添加Cookie |
4.3 请求转发 vs 重定向
| 对比 | 请求转发(forward) | 重定向(sendRedirect) |
|---|---|---|
| 浏览器地址栏 | 不变 | 变化 |
| 请求次数 | 1次(服务器内部) | 2次(客户端重新发起) |
| 数据共享 | ✅ 可共享request属性 | ❌ 不可共享 |
| 性能 | 更高(少一次请求) | 较低(多一次请求) |
| 适用场景 | 服务器内部跳转 | 页面跳转、防止表单重复提交 |
五、Servlet 3.0+ 异步处理
5.1 为什么需要异步?
传统Servlet模型中,一个请求占用一个容器线程直到处理完成。如果业务有耗时操作(如调用外部API、大文件处理),容器线程会被长时间占用,导致并发能力下降。
Servlet 3.0引入异步处理:将耗时任务交给新线程,释放容器线程处理其他请求,提升吞吐量。
5.2 异步Servlet实战
importjakarta.servlet.*;importjakarta.servlet.http.*;importjakarta.servlet.annotation.*;importjava.util.concurrent.*;@WebServlet(value="/async",asyncSupported=true)// 关键:启用异步publicclassAsyncServletextendsHttpServlet{privateExecutorServiceexecutor=Executors.newFixedThreadPool(10);@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{// 1. 启动异步上下文AsyncContextctx=req.startAsync();//// 2. 提交耗时任务到线程池executor.submit(()->{try{// 模拟耗时操作(如调用外部API、查询数据库)Thread.sleep(3000);// 3. 获取响应并输出HttpServletResponseresponse=(HttpServletResponse)ctx.getResponse();response.setContentType("text/plain;charset=UTF-8");response.getWriter().println("异步处理完成!");}catch(Exceptione){e.printStackTrace();}finally{// 4. 完成异步处理ctx.complete();}});// 容器线程立即释放,不阻塞!}}💡关键点:
asyncSupported = true启用异步,startAsync()获取AsyncContext,耗时任务在新线程执行,完成后调用complete()。
六、三大组件:Filter & Listener
6.1 Filter——请求拦截器
Filter是Servlet规范的一部分,在请求到达Servlet之前和响应返回客户端之后执行。
典型场景:日志记录、统一编码处理、登录验证、跨域处理、敏感词过滤。
importjakarta.servlet.*;importjakarta.servlet.annotation.*;importjava.io.IOException;@WebFilter("/*")// 拦截所有请求publicclassLogFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigconfig)throwsServletException{System.out.println("Filter初始化");}@OverridepublicvoiddoFilter(ServletRequestreq,ServletResponseresp,FilterChainchain)throwsIOException,ServletException{// 前置处理System.out.println("请求到达:"+System.currentTimeMillis());// 放行到下一个Filter或Servletchain.doFilter(req,resp);// 后置处理System.out.println("响应返回:"+System.currentTimeMillis());}@Overridepublicvoiddestroy(){System.out.println("Filter销毁");}}Filter链执行顺序:多个Filter按@WebFilter的filterName字母顺序或web.xml配置顺序执行,形成责任链。
6.2 Listener——事件监听器
Listener监听Web应用中关键对象的生命周期事件。
三种核心Listener:
| Listener | 监听对象 | 典型场景 |
|---|---|---|
ServletContextListener | 应用上下文(启动/关闭) | 加载配置文件、初始化数据库连接池 |
HttpSessionListener | Session(创建/销毁) | 在线用户统计 |
ServletRequestListener | 请求(创建/销毁) | 请求日志记录 |
importjakarta.servlet.*;publicclassAppListenerimplementsServletContextListener{@OverridepublicvoidcontextInitialized(ServletContextEventsce){System.out.println("应用启动,加载配置...");// 加载数据库连接池、Spring容器等}@OverridepublicvoidcontextDestroyed(ServletContextEventsce){System.out.println("应用关闭,释放资源...");}}七、Servlet vs Spring MVC
| 对比维度 | Servlet | Spring MVC |
|---|---|---|
| 定位 | Java Web底层规范 | 基于Servlet的高级Web框架 |
| 开发方式 | 继承HttpServlet,手动配置映射 | 注解驱动(@Controller、@RequestMapping) |
| 参数绑定 | 手动req.getParameter() | 自动绑定到方法参数 |
| 视图渲染 | 手动拼接HTML或用JSP | 自动渲染,支持多种视图技术 |
| 启动速度 | 快 | 较慢(需加载Spring上下文) |
| 内存占用 | 低 | 高 |
| 适用场景 | 理解底层、轻量级应用 | 绝大部分企业级应用 |
💡核心结论:Spring MVC底层是
DispatcherServlet,它本身就是一个Servlet。掌握了Servlet,就掌握了Spring MVC的“发动机”。
八、避坑要点
| 错误/误区 | 后果 | 正确做法 |
|---|---|---|
| Servlet实例变量非线程安全 | 多线程数据混乱 | 尽量用局部变量,避免在Servlet中定义可变的实例变量 |
忘记asyncSupported = true | 异步功能不生效 | 注解中加上asyncSupported = true |
Filter中不调用chain.doFilter() | 请求被拦截,无法到达目标 | 必须调用chain.doFilter()放行 |
| 请求转发和重定向混淆 | 数据丢失或重复提交 | 转发用forward,重定向用sendRedirect |
忘记设置setContentType | 中文乱码 | 统一设置text/html;charset=UTF-8 |
老项目使用javax.servlet | 不兼容Jakarta EE 9+ | 新项目使用jakarta.servlet |
九、面试高频考点
Q1:Servlet的生命周期有哪些阶段?
四个阶段:①加载与实例化(容器创建Servlet实例);②初始化(调用
init(),执行一次);③服务(调用service(),每次请求执行);④销毁(调用destroy(),执行一次)。
Q2:Servlet是单例还是多例?线程安全吗?
Servlet是单例,整个应用只有一个实例。线程不安全——多个请求共享同一个Servlet实例,实例变量会被多线程共享。因此应避免在Servlet中使用可变的实例变量,尽量使用局部变量。
Q3:Filter和Interceptor的区别?
Filter是Servlet规范的一部分,运行在Web容器中,在请求到达Servlet之前执行。Interceptor是Spring MVC的组件,运行在Spring容器中,在Handler执行前后执行。Filter更底层,Interceptor功能更丰富(可注入Spring Bean)。
Q4:请求转发(forward)和重定向(redirect)的区别?
转发(forward)是服务器内部跳转,地址栏不变,1次请求,可共享request属性。重定向(redirect)是客户端重新发起请求,地址栏变化,2次请求,不可共享request属性。
Q5:javax.servlet和jakarta.servlet有什么区别?
Jakarta EE 9开始,所有
javax包名改为jakarta。新项目使用jakarta.servlet。老项目迁移需修改import和依赖版本。
🎤 面试官追问陷阱(加分题)
追问1:“Servlet 3.0的异步处理中,startAsync()之后容器线程会怎样?”
👉 容器线程立即释放,回到线程池处理其他请求。耗时任务在
AsyncContext关联的新线程中执行。完成后调用complete()通知容器。
追问2:“<load-on-startup>设置为正数和负数有什么区别?”
👉 正数:容器启动时加载Servlet,数值越小优先级越高。负数或未设置:懒加载——第一次请求到达时才创建Servlet实例。
十、练习题
代码题:编写一个Servlet,接收
name参数,返回“Hello, [name]!”的HTML页面。分析题:Filter和Servlet的执行顺序是什么?一个请求经过多个Filter时,顺序如何确定?
场景题:某Web应用需要统计在线用户数,应该使用什么组件?如何实现?
📊 你的学习进度
- 当前:第68篇 / 共108篇 ·进阶篇:JavaWeb核心技术(第68~72篇)
- ✅ 已完成:基础篇44篇 + 第45~68篇
- 📖 正在学:第68篇
- ⏳ 待学习:第69~108篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
👉 下一篇文章预告
🚀下一篇:《第69篇:JSP与EL表达式》
内容简介:JSP原理、九大内置对象、EL表达式语法、JSTL标签库、JSP与Servlet的关系。
👉JavaWeb专题持续深入,拿下JSP与EL!
📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!