第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等)全权管理,开发者不需要手动new Servlet对象。

3.1 生命周期四阶段

在这里插入图片描述

各阶段详解

阶段 方法 调用次数 触发时机 说明
加载与实例化 构造方法 1次 第一次请求到达(默认)或容器启动时 默认懒加载,可通过<load-on-startup>改为容器启动时加载
初始化 init() 1次 实例化后立即调用 加载数据库连接池、配置文件等资源
服务 service() 每次请求 每次HTTP请求到达 根据请求方法分发到doGet()/doPost()
销毁 destroy() 1次 容器关闭或应用卸载时 释放资源,如关闭数据库连接

💡 重要理解:Servlet是单例多线程——整个应用只有一个Servlet实例,但每个请求会创建一个新线程调用service()方法。因此Servlet实例变量不是线程安全的

3.2 代码示例
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.io.*;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    
    // ① 初始化(执行一次)
    @Override
    public void init() throws ServletException {
        System.out.println("Servlet初始化,加载资源");
    }
    
    // ② 服务(每次请求执行)
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<h1>Hello, Servlet!</h1>");
        out.println("<p>当前时间:" + new java.util.Date() + "</p>");
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // POST请求处理
        doGet(req, resp);
    }
    
    // ③ 销毁(执行一次)
    @Override
    public void destroy() {
        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实战
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import java.util.concurrent.*;

@WebServlet(value = "/async", asyncSupported = true)  // 关键:启用异步
public class AsyncServlet extends HttpServlet {
    
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 启动异步上下文
        AsyncContext ctx = req.startAsync();  // 
        
        // 2. 提交耗时任务到线程池
        executor.submit(() -> {
            try {
                // 模拟耗时操作(如调用外部API、查询数据库)
                Thread.sleep(3000);
                
                // 3. 获取响应并输出
                HttpServletResponse response = 
                    (HttpServletResponse) ctx.getResponse();
                response.setContentType("text/plain;charset=UTF-8");
                response.getWriter().println("异步处理完成!");
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 4. 完成异步处理
                ctx.complete();
            }
        });
        
        // 容器线程立即释放,不阻塞!
    }
}

💡 关键点asyncSupported = true启用异步,startAsync()获取AsyncContext,耗时任务在新线程执行,完成后调用complete()

六、三大组件:Filter & Listener

6.1 Filter——请求拦截器

Filter是Servlet规范的一部分,在请求到达Servlet之前和响应返回客户端之后执行。

典型场景:日志记录、统一编码处理、登录验证、跨域处理、敏感词过滤。

import jakarta.servlet.*;
import jakarta.servlet.annotation.*;
import java.io.IOException;

@WebFilter("/*")  // 拦截所有请求
public class LogFilter implements Filter {
    
    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("Filter初始化");
    }
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, 
                         FilterChain chain) throws IOException, ServletException {
        // 前置处理
        System.out.println("请求到达:" + System.currentTimeMillis());
        
        // 放行到下一个Filter或Servlet
        chain.doFilter(req, resp);
        
        // 后置处理
        System.out.println("响应返回:" + System.currentTimeMillis());
    }
    
    @Override
    public void destroy() {
        System.out.println("Filter销毁");
    }
}

Filter链执行顺序:多个Filter按@WebFilterfilterName字母顺序或web.xml配置顺序执行,形成责任链

6.2 Listener——事件监听器

Listener监听Web应用中关键对象的生命周期事件。

三种核心Listener

Listener 监听对象 典型场景
ServletContextListener 应用上下文(启动/关闭) 加载配置文件、初始化数据库连接池
HttpSessionListener Session(创建/销毁) 在线用户统计
ServletRequestListener 请求(创建/销毁) 请求日志记录
import jakarta.servlet.*;

public class AppListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("应用启动,加载配置...");
        // 加载数据库连接池、Spring容器等
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        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.servletjakarta.servlet有什么区别?

Jakarta EE 9开始,所有javax包名改为jakarta。新项目使用jakarta.servlet。老项目迁移需修改import和依赖版本。

🎤 面试官追问陷阱(加分题)

追问1:“Servlet 3.0的异步处理中,startAsync()之后容器线程会怎样?”

👉 容器线程立即释放,回到线程池处理其他请求。耗时任务在AsyncContext关联的新线程中执行。完成后调用complete()通知容器。

追问2:“<load-on-startup>设置为正数和负数有什么区别?”

👉 正数:容器启动时加载Servlet,数值越小优先级越高。负数或未设置:懒加载——第一次请求到达时才创建Servlet实例。

十、练习题

  1. 代码题:编写一个Servlet,接收name参数,返回“Hello, [name]!”的HTML页面。

  2. 分析题:Filter和Servlet的执行顺序是什么?一个请求经过多个Filter时,顺序如何确定?

  3. 场景题:某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!
👉 点击关注我,更新后第一时间收到推送!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐