本文收录于「Java 学习日记:从入门到架构师」专栏,聚焦 Java Web 核心组件 Servlet,从底层原理到实战落地,帮你彻底搞懂 Servlet 的工作方式~
一、为什么要学 Servlet?
在上一篇 Tomcat 的学习中,我们知道 Tomcat 是 Web 服务器,但它只是 "接收请求、返回响应" 的容器 ------ 真正处理业务逻辑(比如接收前端参数、操作数据库、返回数据)的核心组件,就是Servlet。
简单来说:
- Servlet 是 Java 编写的服务器端程序,是 Java Web 的 "基石";
- SpringMVC 的 Controller 本质上是对 Servlet 的封装,懂 Servlet 才能理解 SpringMVC 的底层原理;
- 所有 Java Web 项目的请求处理,最终都会落到 Servlet 层面。
今天这篇日记,我们从 Servlet 的核心概念、生命周期、Request/Response 对象实战入手,手把手带你写第一个 Servlet 程序。
二、Servlet 核心认知:什么是 Servlet?
Servlet(Server Applet)是运行在服务器端的 Java 小程序,遵循 Java Servlet 规范,核心作用是:
- 接收客户端的 HTTP 请求(Request);
- 处理请求(如参数解析、业务逻辑处理);
- 生成 HTTP 响应(Response)返回给客户端。
Servlet 的核心特点:
- 基于 Java 语言,跨平台;
- 与 HTTP 协议深度绑定,能直接操作请求 / 响应的所有细节;
- 由 Tomcat 等 Servlet 容器管理,无需手动创建和销毁。
三、Servlet 生命周期:从创建到销毁的全过程
Servlet 的生命周期完全由 Tomcat(Servlet 容器)管理,核心分为 4 个阶段,记住这个流程就能理解 Servlet 的运行原理:
1. 生命周期四阶段(核心)

(1)加载与实例化
- 时机:默认情况下,Tomcat 第一次接收到访问该 Servlet 的请求时,加载 Servlet 类并创建唯一实例(Servlet 是单例的!);
- 特点:整个应用生命周期内,一个 Servlet 类只创建一个实例,所有请求共享这个实例(注意线程安全问题)。
(2)初始化(init ())
- 时机:实例化后立即执行,只执行一次;
- 作用:初始化 Servlet 的资源(如加载配置文件、创建数据库连接池);
- 重写方式:
public void init() throws ServletException {}。
(3)处理请求(service ())
- 时机:每次客户端发送请求时,Tomcat 会启动一个新线程调用 service () 方法;
- 核心逻辑:service () 方法会根据请求方式(GET/POST),自动调用 doGet () 或 doPost () 方法;
- 特点:每次请求都执行,是处理业务逻辑的核心。
(4)销毁(destroy ())
- 时机:Tomcat 关闭、Servlet 被卸载时执行,只执行一次;
- 作用:释放 Servlet 占用的资源(如关闭数据库连接、释放文件句柄);
- 重写方式:
public void destroy() {}。
2. 关键注意点
- 单例多线程:Servlet 是单例的,多个请求共用一个实例,因此不要在 Servlet 中定义成员变量(会有线程安全问题);
- 提前初始化 :若想让 Servlet 在 Tomcat 启动时就初始化(而非第一次请求时),可在 web.xml 中配置
<load-on-startup>1</load-on-startup>(数字越小优先级越高)。
四、第一个 Servlet 程序:实战步骤
1. 开发环境准备
- JDK 8+ + Tomcat 9 + IDEA(Eclipse 也可);
- 新建 Java Web 项目(IDEA 中选择 "Java Enterprise",勾选 "Web Application")。
2. 编写 Servlet 类
创建FirstServlet.java,继承HttpServlet(核心父类),重写 doGet () 和 doPost () 方法:
java
运行
java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
// 注解配置Servlet路径(替代web.xml),访问路径:http://localhost:8080/demo/first
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
// 初始化方法(可选重写)
@Override
public void init() throws ServletException {
super.init();
System.out.println("FirstServlet 初始化了!");
}
// 处理GET请求
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应编码(解决中文乱码)
response.setContentType("text/html;charset=UTF-8");
// 2. 获取PrintWriter,用于向客户端输出内容
PrintWriter writer = response.getWriter();
// 3. 输出响应内容
writer.write("<h1>Hello Servlet!</h1>");
writer.write("<p>Java学习日记 - Day33</p>");
// 打印请求信息
System.out.println("请求方法:" + request.getMethod());
System.out.println("请求路径:" + request.getRequestURI());
}
// 处理POST请求(复用GET逻辑)
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
// 销毁方法(可选重写)
@Override
public void destroy() {
super.destroy();
System.out.println("FirstServlet 销毁了!");
}
}
3. 配置与运行(两种方式)
方式 1:注解配置(推荐,Servlet 3.0 + 支持)
上面的代码中,@WebServlet("/first") 就是注解配置,直接指定 Servlet 的访问路径,无需修改 web.xml。
方式 2:web.xml 配置(传统方式)
若不用注解,可在WEB-INF/web.xml中添加配置:
xml
XML
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置Servlet -->
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>com.example.FirstServlet</servlet-class>
<!-- 可选:启动时初始化,数字越小优先级越高 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置Servlet映射路径 -->
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/first</url-pattern>
</servlet-mapping>
</web-app>
4. 部署与访问
- 将项目部署到 Tomcat(复制到 webapps 目录或 IDEA 中配置 Tomcat 运行);
- 启动 Tomcat;
- 访问
http://localhost:8080/demo/first(demo 是项目名),能看到页面输出 "Hello Servlet!" 即成功; - 查看 Tomcat 控制台,能看到 "FirstServlet 初始化了!"(第一次访问时输出),每次刷新页面都会打印请求信息。
五、Request/Response 对象实战:核心 API
Servlet 的核心能力是处理 Request(请求)和 Response(响应),这两个对象由 Tomcat 创建并传入 doGet/doPost 方法,下面是高频使用的 API:
1. HttpServletRequest(请求对象)
作用:获取客户端的所有请求信息(参数、请求头、Cookie 等)。
高频 API:
java
运行
java
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置请求编码(解决POST参数中文乱码)
request.setCharacterEncoding("UTF-8");
// 2. 获取请求参数(GET/POST通用)
String username = request.getParameter("username"); // 单个参数
String[] hobbies = request.getParameterValues("hobby"); // 多个同名参数(如复选框)
// 3. 获取请求头信息
String userAgent = request.getHeader("User-Agent"); // 客户端类型
String host = request.getHeader("Host"); // 主机名
// 4. 获取请求路径相关信息
String requestURI = request.getRequestURI(); // /demo/first
String contextPath = request.getContextPath(); // /demo(项目名)
// 5. 打印参数
System.out.println("用户名:" + username);
System.out.println("客户端类型:" + userAgent);
// 响应内容
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("用户名:" + username + "<br>");
writer.write("请求路径:" + requestURI);
}
访问测试:http://localhost:8080/demo/first?username=Java日记&hobby=编程&hobby=学习,控制台和页面会显示对应的参数。
2. HttpServletResponse(响应对象)
作用:向客户端发送响应(页面、JSON、重定向等)。
高频 API:
java
运行
java
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应编码和数据格式
response.setContentType("text/html;charset=UTF-8");
// 或返回JSON:response.setContentType("application/json;charset=UTF-8");
// 2. 向客户端输出内容
PrintWriter writer = response.getWriter();
writer.write("{\"code\":200,\"msg\":\"请求成功\"}"); // 输出JSON
// 3. 重定向(跳转到其他页面)
// response.sendRedirect("/demo/index.html");
// 4. 设置响应状态码
// response.setStatus(404); // 404未找到
// response.sendError(500, "服务器内部错误"); // 500错误并提示信息
}
3. 解决中文乱码问题(必看)
新手最常踩的坑就是中文乱码,核心解决方案:
-
请求乱码 :POST 请求需设置
request.setCharacterEncoding("UTF-8");GET 请求乱码需修改 Tomcat 的conf/server.xml,添加URIEncoding="UTF-8":xml
XML<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> -
响应乱码 :必须先设置
response.setContentType("text/html;charset=UTF-8"),再获取 PrintWriter。
六、Servlet 线程安全问题(重点避坑)
Servlet 是单例的,多个请求会共用同一个 Servlet 实例,因此不要在 Servlet 中定义成员变量,否则会出现线程安全问题:
❌ 错误示例(有线程安全问题):
java
运行
java
@WebServlet("/unsafe")
public class UnsafeServlet extends HttpServlet {
// 成员变量:多个线程共享,会被覆盖
private String username;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
username = request.getParameter("username");
// 模拟业务处理延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getWriter().write("用户名:" + username);
}
}
✅ 正确示例(无线程安全问题):
java
运行
java
@WebServlet("/safe")
public class SafeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 局部变量:每个线程独立,无安全问题
String username = request.getParameter("username");
response.getWriter().write("用户名:" + username);
}
}
七、今日实战小任务
- 编写一个 Servlet,接收前端传入的 "姓名" 和 "年龄" 参数,在页面上输出 "你好,XXX!你的年龄是 XX 岁";
- 测试 GET 和 POST 请求的参数接收,解决中文乱码问题;
- 尝试用 response 实现重定向功能(跳转到百度首页)。
总结
- Servlet 是 Java Web 的核心组件,生命周期分为实例化、初始化(init)、处理请求(service/doGet/doPost)、销毁(destroy),其中 service 方法每次请求都会执行;
- HttpServletRequest 用于获取请求参数、请求头等信息,HttpServletResponse 用于向客户端返回响应,需注意设置编码解决中文乱码;
- Servlet 是单例多线程的,不要定义成员变量,避免线程安全问题,推荐使用注解
@WebServlet配置访问路径。
下一篇【Day34】预告:Servlet 进阶:关注专栏持续解锁 Java Web 核心知识点~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!