Servlet 技术详解:从入门到实战 🦞
Servlet 是 Java Web 开发的基础,是所有 Java Web 框架(如 Spring MVC)的底层原理。理解和掌握 Servlet 的工作机制,对于每一个 Java 后端开发者来说都是必备技能。本文将带你从零开始,系统掌握 Servlet 的核心概念、工作原理、生命周期以及实战开发。💪
📚 目录导航
一、Servlet 概述:什么是 Servlet? 1.1 Servlet 的定义 Servlet 是 Sun 公司制定的 Java Servlet API 规范,它是一种运行在服务器端的 Java 程序,用于处理客户端(浏览器)的请求并返回响应。
flowchart LR
A["🌐 浏览器"] -->|"HTTP 请求"| B["☕️ Servlet 容器"]
B -->|"调用"| C["📦 Servlet"]
C -->|"处理业务"| D["📋 业务逻辑"]
D -->|"返回响应"| C
C -->|"HTTP 响应"| A
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
style D fill:#f8bbd0
1.2 Servlet 与 JSP 的关系
对比维度
Servlet
JSP
本质
Java 类
HTML + Java 代码混合
擅长
业务逻辑、数据处理
页面展示、动态内容
编译
手动编译
首次访问时自动编译成 Servlet
书写
纯 Java 代码
HTML 中嵌入 Java 代码
运行
必须部署到 Servlet 容器
运行在 Servlet 容器中
适用场景
后端接口、数据处理
动态页面、模板渲染
💡 推荐 :Servlet 负责后端逻辑,JSP 负责页面展示,分工明确。现代开发更推荐前后端分离,使用 JSON 作为数据交换格式。
1.3 Servlet 容器的作用 常见的 Servlet 容器有:Tomcat、Jetty、Undertow、WebLogic、WebSphere 等。
flowchart TD
A["⚙️ Servlet 容器职责"] --> B["🔌 网络通信"]
A --> C["📦 对象生命周期管理"]
A --> D["🛡️ 安全控制"]
A --> E["📝 URL 映射"]
A --> F["🔄 JSP 支持"]
B --> B1["处理 TCP/IP\n协议栈"]
C --> C1["加载 Servlet\n调用构造方法\n管理线程"]
D --> D1["角色权限\n数据安全"]
E --> E1["URL 路径匹配\n请求分发"]
F --> F1["编译 JSP\n转换为 Servlet"]
style A fill:#fff3e0
1.4 Servlet 版本演进
版本
发布年份
主要特性
Servlet 2.0
1997
最早的 Servlet 规范
Servlet 2.3/2.4
2001/2003
过滤器的引入
Servlet 2.5
2005
注解支持增强
Servlet 3.0
2009
注解驱动 、异步支持、模块化
Servlet 3.1
2013
非阻塞 I/O
Servlet 4.0
2017
HTTP/2 支持
Servlet 5.0+
2020+
Jakarta EE 迁移
二、Tomcat 服务器安装与配置 2.1 Tomcat 简介 Apache Tomcat 是 Apache 软件基金会开发的开源 Servlet 容器,是目前最流行的 Java Web 服务器之一。
flowchart TD
A["🐱 Tomcat 架构"] --> B["🖥️ Server"]
B --> C["🌐 Service"]
C --> D["🔌 Connector"]
C --> E["🗂️ Engine"]
E --> F["🛤️ Host"]
F --> G["📁 Context\n(Web 应用)"]
G --> H["🔀 Wrapper\n(Servlet)"]
style A fill:#fff3e0
style G fill:#c8e6c9
2.2 Tomcat 安装(Windows) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
2.3 Tomcat 目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 apache-tomcat-10.1.0/ ├── bin/ │ ├── startup.bat │ ├── startup.sh │ ├── shutdown.bat │ └── version.sh │ ├── conf/ │ ├── server.xml │ ├── web.xml │ ├── context.xml │ └── tomcat-users.xml │ ├── lib/ │ ├── logs/ │ ├── catalina.out │ └── localhost.log │ ├── webapps/ │ ├── docs/ │ ├── examples/ │ └── manager/ │ └── work/ └── Catalina/
2.4 启动 Tomcat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cd D:\apache-tomcat-10.1.0\binstartup.bat ./bin/startup.sh shutdown.bat ./bin/shutdown.sh
2.5 修改端口号 1 2 3 4 5 6 7 8 9 10 <Server port ="8005" shutdown ="SHUTDOWN" > <Service name ="Catalina" > <Connector port ="8080" protocol ="HTTP/1.1" connectionTimeout ="20000" redirectPort ="8443" /> </Service > </Server >
三、第一个 Servlet 程序 3.1 项目结构设计
flowchart TD
A["📁 WebApp 项目结构"] --> B["📁 src/main/java"]
A --> C["📁 src/main/webapp"]
A --> D["📁 src/main/resources"]
B --> E["📁 com.example.servlet"]
C --> F["📄 index.jsp"]
C --> G["📄 WEB-INF/"]
G --> H["📄 web.xml"]
G --> I["📄 lib/"]
style A fill:#e3f2fd
style B fill:#c8e6c9
3.2 Maven 项目配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.example</groupId > <artifactId > servlet-demo</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > war</packaging > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > jakarta.servlet</groupId > <artifactId > jakarta.servlet-api</artifactId > <version > 6.0.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > jakarta.servlet.jsp</groupId > <artifactId > jakarta.servlet.jsp-api</artifactId > <version > 3.1.0</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.glassfish.web</groupId > <artifactId > jakarta.servlet.jsp.jstl</artifactId > <version > 3.0.0</version > </dependency > </dependencies > <build > <finalName > servlet-demo</finalName > <plugins > <plugin > <groupId > org.apache.tomcat.maven</groupId > <artifactId > tomcat-maven-plugin</artifactId > <version > 3.0-r1655215</version > <configuration > <port > 8080</port > <path > /</path > </configuration > </plugin > </plugins > </build > </project >
3.3 创建 Servlet 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.example.servlet;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.time.LocalDateTime;@WebServlet("/first-servlet") public class FirstServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8" ); response.setCharacterEncoding("UTF-8" ); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>" ); out.println("<html>" ); out.println("<head>" ); out.println("<meta charset='UTF-8'>" ); out.println("<title>第一个 Servlet</title>" ); out.println("</head>" ); out.println("<body>" ); out.println("<h1>🎉 Hello, Servlet! 🦞</h1>" ); out.println("<p>当前时间:" + LocalDateTime.now() + "</p>" ); out.println("<p>当前会话 ID:" + request.getSession().getId() + "</p>" ); out.println("<p>Servlet 版本:jakarta.servlet-api 6.0</p>" ); out.println("</body>" ); out.println("</html>" ); } }
3.4 传统 XML 配置方式 如果使用 Servlet 3.0 之前的版本,或者项目不使用注解,需要在 web.xml 中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version ="6.0" > <display-name > Servlet Demo Application</display-name > <servlet > <servlet-name > HelloServlet</servlet-name > <servlet-class > com.example.servlet.HelloServlet</servlet-class > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > HelloServlet</servlet-name > <url-pattern > /hello</url-pattern > </servlet-mapping > <welcome-file-list > <welcome-file > index.jsp</welcome-file > </welcome-file-list > </web-app >
3.5 运行项目 1 2 3 4 5 6 7 8 9 10 11 mvn tomcat:run mvn package
四、Servlet 生命周期 4.1 生命周期四个阶段
flowchart TD
A["🔄 Servlet 生命周期"] --> B["1️⃣ 实例化"]
B --> C["2️⃣ 初始化 init()"]
C --> D["3️⃣ 服务 doGet/doPost"]
D --> E["4️⃣ 销毁 destroy()"]
B -.->|"首次请求时"| B
C -.->|"只执行一次"| C
D -.->|"每次请求都执行"| D
E -.->|"应用卸载或重启时"| E
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#e3f2fd
style E fill:#f8bbd0
阶段
执行时机
执行次数
说明
实例化
首次请求时(或容器启动时
1 次
Servlet 容器创建 Servlet 实例
初始化
实例化后
1 次
调用 init() 方法,可获取初始化参数
服务
每次请求
多次
调用 service() → 分发到 doGet/doPost
销毁
应用卸载/容器关闭
1 次
调用 destroy(),释放资源
4.2 生命周期代码演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.example.servlet;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.HttpServlet;@WebServlet("/lifecycle") public class LifecycleServlet extends HttpServlet { private int requestCount = 0 ; public LifecycleServlet () { super (); System.out.println("📦 1. LifecycleServlet 构造方法被调用" ); } @Override public void init () throws ServletException { super .init(); System.out.println("🔧 2. init() 方法被调用,Servlet 初始化完成" ); } @Override protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { requestCount++; System.out.println("🔄 3. service() 方法被调用,第 " + requestCount + " 次请求" ); super .service(request, response); } @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("📝 4. doGet() 方法被调用" ); response.getWriter().write( "<h1>Servlet 生命周期演示</h1>" + "<p>请求次数:" + requestCount + "</p>" + "<p>当前会话 ID:" + request.getSession().getId() + "</p>" ); } @Override public void destroy () { super .destroy(); System.out.println("🗑️ 5. destroy() 方法被调用,Servlet 即将被销毁" ); System.out.println("总共处理了 " + requestCount + " 次请求" ); } }
4.3 init() 方法详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override public void init () throws ServletException { String encoding = getServletConfig().getInitParameter("encoding" ); System.out.println("编码格式:" + encoding); } @Override public void init (ServletConfig config) throws ServletException { super .init(config); } import jakarta.annotation.PostConstruct;@PostConstruct public void initMethod () { System.out.println("初始化方法被调用" ); }
五、HttpServletRequest 与 HttpServletResponse 5.1 HttpServletRequest 对象 HttpServletRequest 封装了客户端的 HTTP 请求信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @WebServlet("/request-demo") public class RequestDemoServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method = request.getMethod(); StringBuffer url = request.getRequestURL(); String uri = request.getRequestURI(); String username = request.getParameter("username" ); String password = request.getParameter("password" ); String[] hobbies = request.getParameterValues("hobby" ); String userAgent = request.getHeader("User-Agent" ); String contentType = request.getContentType(); int contentLength = request.getContentLength(); javax.servlet.http.HttpSession session = request.getSession(); Cookie[] cookies = request.getCookies(); request.setCharacterEncoding("UTF-8" ); BufferedReader reader = request.getReader(); String line; StringBuilder body = new StringBuilder (); while ((line = reader.readLine()) != null ) { body.append(line); } request.setAttribute("user" , new User ("张三" , 25 )); User user = (User) request.getAttribute("user" ); RequestDispatcher dispatcher = request.getRequestDispatcher("/other" ); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write("请求方法:" + method + "<br/>" ); response.getWriter().write("请求 URL:" + url); } }
5.2 HttpServletResponse 对象 HttpServletResponse 用于构建 HTTP 响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @WebServlet("/response-demo") public class ResponseDemoServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8" ); response.setStatus(200 ); response.setHeader("Cache-Control" , "no-cache" ); response.setHeader("X-Custom-Header" , "custom-value" ); PrintWriter writer = response.getWriter(); writer.write("Hello, World!" ); response.sendRedirect(request.getContextPath() + "/target" ); Cookie cookie = new Cookie ("username" , "zhangsan" ); cookie.setMaxAge(3600 ); cookie.setPath("/" ); cookie.setHttpOnly(true ); response.addCookie(cookie); } }
5.3 请求参数中文乱码解决 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @WebServlet("/encoding-demo") public class EncodingDemoServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("name" ); name = new String (name.getBytes("ISO-8859-1" ), "UTF-8" ); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write("GET 参数:" + name); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8" ); String username = request.getParameter("username" ); String password = request.getParameter("password" ); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write("POST 参数:username=" + username + ", password=" + password); } }
六、会话管理:Session 与 Cookie 6.1 会话管理概念
flowchart LR
A["👤 用户浏览器"] -->|"第一次请求\n无 Cookie"| B["🖥️ 服务器"]
B -->|"创建 Session\n返回 JSESSIONID"| C["📦 Session 对象"]
C -->|"Cookie: JSESSIONID"| A
A -->|"第二次请求\n携带 Cookie"| B
B -->|"查找 Session"| C
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
6.2 Cookie 使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @WebServlet("/cookie-demo") public class CookieDemoServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie usernameCookie = new Cookie ("username" , "zhangsan" ); usernameCookie.setMaxAge(3600 ); usernameCookie.setPath("/" ); usernameCookie.setHttpOnly(true ); response.addCookie(usernameCookie); Cookie[] cookies = request.getCookies(); String username = null ; if (cookies != null ) { for (Cookie cookie : cookies) { if ("username" .equals(cookie.getName())) { username = cookie.getValue(); break ; } } } Cookie deleteCookie = new Cookie ("username" , "" ); deleteCookie.setMaxAge(0 ); deleteCookie.setPath("/" ); response.addCookie(deleteCookie); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write( "<h1>Cookie 演示 �饼干</h1>" + "<p>用户名:" + (username != null ? username : "未设置" ) + "</p>" ); } }
6.3 Session 使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @WebServlet("/session-demo") public class SessionDemoServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { javax.servlet.http.HttpSession session = request.getSession(); session.setAttribute("userId" , 1001L ); session.setAttribute("username" , "zhangsan" ); Long userId = (Long) session.getAttribute("userId" ); String username = (String) session.getAttribute("username" ); session.removeAttribute("userId" ); session.setMaxInactiveInterval(1800 ); session.getMaxInactiveInterval(); String sessionId = session.getId(); long creationTime = session.getCreationTime(); long lastAccessedTime = session.getLastAccessedTime(); boolean isNew = session.isNew(); response.setContentType("text/html;charset=UTF-8" ); response.getWriter().write( "<h1>Session 演示 🗃️</h1>" + "<p>Session ID:" + sessionId + "</p>" + "<p>用户名:" + username + "</p>" + "<p>是否新 Session:" + isNew + "</p>" ); } }
6.4 Session 失效的几种情况
flowchart TD
A["🔚 Session 失效情况"] --> B["⏰ 超时过期"]
A --> C["👤 用户关闭浏览器"]
A --> D["🗑️ 手动 invalidate()"]
A --> E["🛑 服务器停止"]
B --> B1["默认 30 分钟\n无活动"]
C --> C1["Cookie 被清除\nJSESSIONID 丢失"]
D --> D1["主动注销登录\n退出操作"]
E --> E1["应用卸载\n容器重启"]
style A fill:#fff3e0
6.5 Cookie vs Session 对比
对比维度
Cookie
Session
存储位置
浏览器本地
服务器内存
存储容量
每个 Cookie 最大 4KB
理论上无限制
安全性
较低(可被禁用/伪造)
较高(数据在服务器端)
性能
不占用服务器资源
占用服务器内存
跨会话
可设置跨会话持久化
仅当前会话有效
适用场景
自动登录、记住我
登录状态、购物车
七、过滤器与监听器 7.1 过滤器(Filter)概述 过滤器是 Servlet 2.3 规范引入的组件,用于在请求到达 Servlet 之前或响应返回之前对请求/响应进行预处理。
flowchart TD
A["🔍 过滤器链"] --> B["请求"]
B --> C["Filter 1"]
C --> D["Filter 2"]
D --> E["Servlet"]
E --> F["业务逻辑"]
F --> G["Servlet"]
G --> H["Filter 2"]
H --> I["Filter 1"]
I --> J["响应"]
style C fill:#c8e6c9
style D fill:#c8e6c9
style E fill:#e3f2fd
style F fill:#fff3e0
7.2 过滤器示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.example.filter;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter("/*") public class EncodingFilter implements Filter { private String encoding = "UTF-8" ; @Override public void init (FilterConfig filterConfig) throws ServletException { String initEncoding = filterConfig.getInitParameter("encoding" ); if (initEncoding != null && !initEncoding.isEmpty()) { encoding = initEncoding; } System.out.println("✅ EncodingFilter 初始化完成,编码:" + encoding); } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(encoding); response.setCharacterEncoding(encoding); response.setContentType("text/html;charset=" + encoding); System.out.println("🔍 请求被 EncodingFilter 拦截" ); chain.doFilter(request, response); System.out.println("🔍 响应被 EncodingFilter 处理完成" ); } @Override public void destroy () { System.out.println("🗑️ EncodingFilter 被销毁" ); } }
7.3 用户登录过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @WebFilter(urlPatterns = {"/admin/*", "/api/*"}) public class LoginFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; javax.servlet.http.HttpSession session = httpRequest.getSession(false ); Object user = session != null ? session.getAttribute("user" ) : null ; if (user != null ) { chain.doFilter(request, response); } else { httpResponse.sendRedirect(httpRequest.getContextPath() + "/login" ); } } }
7.4 监听器(Listener)概述
监听器类型
接口
触发时机
ServletContext 监听器
ServletContextListener
应用启动/停止
Session 监听器
HttpSessionListener
Session 创建/销毁
请求监听器
ServletRequestListener
请求创建/销毁
属性监听器
ServletContextAttributeListener 等
属性添加/删除/替换
7.5 监听器示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.example.listener;import jakarta.servlet.*;import jakarta.servlet.annotation.WebListener;@WebListener public class ApplicationListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { System.out.println("🚀 应用启动:Spring Boot 初始化中..." ); ServletContext context = sce.getServletContext(); context.setAttribute("appName" , "Servlet Demo" ); String adminEmail = context.getInitParameter("adminEmail" ); System.out.println("管理员邮箱:" + adminEmail); } @Override public void contextDestroyed (ServletContextEvent sce) { System.out.println("🛑 应用停止:资源释放中..." ); } } @WebListener public class SessionListener implements HttpSessionListener { private static int onlineCount = 0 ; @Override public void sessionCreated (HttpSessionEvent se) { onlineCount++; System.out.println("👤 Session 创建,当前在线人数:" + onlineCount); } @Override public void sessionDestroyed (HttpSessionEvent se) { onlineCount--; System.out.println("👋 Session 销毁,当前在线人数:" + onlineCount); } public static int getOnlineCount () { return onlineCount; } }
八、Servlet 注解开发 8.1 常用注解一览
注解
作用
示例
@WebServlet
配置 Servlet 的 URL 映射
@WebServlet("/user")
@WebFilter
配置过滤器
@WebFilter("/*")
@WebListener
配置监听器
@WebListener
@WebInitParam
配置初始化参数
@WebInitParam(name="encoding", value="UTF-8")
@MultipartConfig
配置文件上传
@MultipartConfig
8.2 完整注解示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 package com.example.servlet;import jakarta.servlet.ServletException;import jakarta.servlet.annotation.MultipartConfig;import jakarta.servlet.annotation.WebInitParam;import jakarta.servlet.annotation.WebServlet;import jakarta.servlet.http.*;import java.io.IOException;import java.io.PrintWriter;import java.util.Arrays;@WebServlet( name = "UserServlet", // Servlet 名称 urlPatterns = {"/api/user", "/api/users"}, // 多个 URL 映射 initParams = { // 初始化参数 @WebInitParam(name = "encoding", value = "UTF-8"), @WebInitParam(name = "pageSize", value = "20") }, loadOnStartup = 1, // 启动时加载 description = "用户管理 Servlet" // 描述 ) @MultipartConfig( // 文件上传配置 fileSizeThreshold = 1024 * 1024, // 超过 1MB 则写入磁盘 maxFileSize = 10 * 1024 * 1024, // 最大文件大小 10MB maxRequestSize = 50 * 1024 * 1024 // 最大请求大小 50MB ) public class UserServlet extends HttpServlet { private String encoding; private int pageSize; @Override public void init () throws ServletException { encoding = getServletConfig().getInitParameter("encoding" ); pageSize = Integer.parseInt(getServletConfig().getInitParameter("pageSize" )); System.out.println("✅ UserServlet 初始化:encoding=" + encoding + ", pageSize=" + pageSize); } @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action" ); if ("list" .equals(action)) { listUsers(request, response); } else if ("detail" .equals(action)) { getUserDetail(request, response); } else { listUsers(request, response); } } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(encoding); String action = request.getParameter("action" ); if ("create" .equals(action)) { createUser(request, response); } else if ("update" .equals(action)) { updateUser(request, response); } else if ("delete" .equals(action)) { deleteUser(request, response); } else if ("upload" .equals(action)) { uploadAvatar(request, response); } } private void listUsers (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"users\":[{\"id\":1,\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}]}" ); } private void getUserDetail (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String id = request.getParameter("id" ); response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"id\":" + id + ",\"name\":\"张三\",\"email\":\"zhangsan@example.com\"}" ); } private void createUser (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username" ); String email = request.getParameter("email" ); response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"success\":true,\"message\":\"用户创建成功\"}" ); } private void updateUser (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String id = request.getParameter("id" ); String email = request.getParameter("email" ); response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"success\":true,\"message\":\"用户更新成功\"}" ); } private void deleteUser (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String id = request.getParameter("id" ); response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"success\":true,\"message\":\"用户删除成功\"}" ); } private void uploadAvatar (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Part filePart = request.getPart("avatar" ); String fileName = filePart.getSubmittedFileName(); long fileSize = filePart.getSize(); String uploadPath = getServletContext().getRealPath("/uploads/" ); java.io.File uploadDir = new java .io.File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdirs(); } filePart.write(uploadPath + java.io.File.separator + fileName); response.setContentType("application/json;charset=" + encoding); PrintWriter out = response.getWriter(); out.println("{\"success\":true,\"fileName\":\"" + fileName + "\",\"size\":" + fileSize + "}" ); } }
九、MVC 架构模式 9.1 MVC 模式概述
flowchart TD
A["🎯 MVC 架构模式"] --> B["👤 View\n(视图层)"]
A --> C["📋 Controller\n(控制器层)"]
A --> D["📊 Model\n(模型层)"]
B -->|"用户交互"| C
C -->|"调用"| D
D -->|"数据"| C
C -->|"返回视图"| B
B --> B1["JSP\nThymeleaf\nHTML+Ajax"]
C --> C1["处理请求\n参数校验\n调用 Service"]
D --> D1["业务逻辑\n数据访问\nEntity/DTO"]
style A fill:#fff3e0
style B fill:#c8e6c9
style C fill:#e3f2fd
style D fill:#fff3e0
9.2 MVC 各层职责
层级
职责
组成部分
Model
业务逻辑和数据处理
Service、DAO、Entity
View
页面展示和用户交互
JSP、HTML、Thymeleaf
Controller
请求处理和流程控制
Servlet
9.3 简单 MVC 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 public class User { private Long id; private String username; private String email; private Integer status; } public class UserDao { private static final Map<Long, User> userMap = new ConcurrentHashMap <>(); static { userMap.put(1L , new User (1L , "zhangsan" , "zhangsan@example.com" , 1 )); userMap.put(2L , new User (2L , "lisi" , "lisi@example.com" , 1 )); } public List<User> findAll () { return new ArrayList <>(userMap.values()); } public User findById (Long id) { return userMap.get(id); } public User save (User user) { if (user.getId() == null ) { user.setId((long ) (userMap.size() + 1 )); } userMap.put(user.getId(), user); return user; } public boolean deleteById (Long id) { return userMap.remove(id) != null ; } } public class UserService { private UserDao userDao = new UserDao (); public List<User> getAllUsers () { return userDao.findAll(); } public User getUserById (Long id) { if (id == null || id <= 0 ) { throw new IllegalArgumentException ("ID 不合法" ); } return userDao.findById(id); } public User createUser (User user) { if (user.getUsername() == null || user.getUsername().trim().isEmpty()) { throw new IllegalArgumentException ("用户名不能为空" ); } return userDao.save(user); } } @WebServlet("/mvc/users/*") public class UserController extends HttpServlet { private UserService userService = new UserService (); private Gson gson = new Gson (); @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo = request.getPathInfo(); if (pathInfo == null || pathInfo.equals("/" )) { listUsers(request, response); } else if (pathInfo.matches("/\\d+" )) { Long id = Long.parseLong(pathInfo.substring(1 )); getUser(request, response, id); } else { response.sendError(404 , "Not Found" ); } } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8" ); String pathInfo = request.getPathInfo(); if ("/" .equals(pathInfo)) { createUser(request, response); } else { response.sendError(404 , "Not Found" ); } } private void listUsers (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<User> users = userService.getAllUsers(); sendJson(response, users); } private void getUser (HttpServletRequest request, HttpServletResponse response, Long id) throws ServletException, IOException { try { User user = userService.getUserById(id); sendJson(response, user); } catch (IllegalArgumentException e) { response.sendError(404 , e.getMessage()); } } private void createUser (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { BufferedReader reader = request.getReader(); String body = reader.lines().collect(Collectors.joining()); User user = gson.fromJson(body, User.class); try { User created = userService.createUser(user); sendJson(response, created); } catch (IllegalArgumentException e) { response.sendError(400 , e.getMessage()); } } private void sendJson (HttpServletResponse response, Object data) throws IOException { response.setContentType("application/json;charset=UTF-8" ); response.getWriter().write(gson.toJson(data)); } }
十、常见问题与最佳实践 10.1 常见问题与解决方案
flowchart TD
A["❓ 常见问题"] --> B["⚠️ 404 找不到"]
A --> C["⚠️ 405 方法不支持"]
A --> D["⚠️ 中文乱码"]
A --> E["⚠️ Session 失效"]
B --> B1["检查 URL 映射\n检查 web.xml 配置"]
C --> C1["未重写 doGet/doPost\n或调用了父类 service()"]
D --> D1["设置请求/响应编码\nTomcat URIEncoding"]
E --> E1["Cookie 被禁用\n超时时间太短"]
style A fill:#fff3e0
style B fill:#ffcdd2
style C fill:#fff3e0
style D fill:#e3f2fd
style E fill:#f8bbd0
10.2 最佳实践清单
实践
说明
推荐程度
使用注解配置
减少 XML 配置,代码更简洁
✅✅✅
统一字符编码
过滤器中统一设置 UTF-8
✅✅✅
使用 Session 存储敏感数据
重要数据放服务器端
✅✅
合理使用转发与重定向
保持页面状态用转发,减少请求用重定向
✅✅
线程安全
Servlet 是单实例多线程,注意成员变量
✅✅✅
资源释放
关闭流、数据库连接等资源
✅✅✅
日志记录
使用日志框架记录关键操作
✅✅
10.3 线程安全注意事项 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @WebServlet("/unsafe") public class UnsafeServlet extends HttpServlet { private int count = 0 ; @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) { count++; response.getWriter().write("访问次数:" + count); } } @WebServlet("/safe") public class SafeServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) { int localCount = 0 ; localCount++; response.getWriter().write("访问次数:" + localCount); } } @WebServlet("/safe2") public class SafeServlet2 extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) { request.setAttribute("count" , 1 ); Integer count = (Integer) request.getAttribute("count" ); response.getWriter().write("访问次数:" + count); } } @WebServlet("/synced") public class SyncedServlet extends HttpServlet { private int count = 0 ; @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) { synchronized (this ) { count++; response.getWriter().write("访问次数:" + count); } } }
十一、总结 11.1 核心知识点回顾
mindmap
root((Servlet 核心技术))
基础概念
Servlet 定义
Servlet 容器
Tomcat 服务器
生命周期
实例化
初始化 init
服务 service
销毁 destroy
请求响应
HttpServletRequest
HttpServletResponse
参数获取
中文乱码
会话管理
Cookie
Session
区别对比
过滤器监听器
Filter 过滤器
FilterChain
Listener 监听器
注解开发
@WebServlet
@WebFilter
@WebListener
MVC 架构
Model 层
View 层
Controller 层
11.2 学习路线建议
flowchart LR
A["学习建议"] --> B["📖 理解原理"]
A --> C["💻 多敲代码"]
A --> D["🔍 调试跟踪"]
A --> E["📚 阅读源码"]
B --> B1["生命周期\n请求分发\n工作原理"]
C --> C1["手写 Servlet\n实现 CRUD\n过滤器拦截器"]
D --> D1["断点调试\n查看 Tomcat 日志"]
E --> E1["Spring MVC\nTomcat 源码"]
style A fill:#fff3e0
11.3 下一步推荐学习
📖 Spring MVC :Servlet 的高级封装,Java Web 开发主流框架
📖 Spring Boot :简化 Spring 开发,快速构建 Web 应用
📖 Tomcat 源码 :深入理解 Servlet 容器的工作原理
📖 **Servlet 4.0+**:HTTP/2、Server Push 等新特性
💡 写给读者的话 :Servlet 是 Java Web 开发的基石,虽然现在很少有人直接手写 Servlet(大多数使用 Spring MVC 等框架),但理解 Servlet 的原理对于深入学习 Spring 框架和排查问题非常有帮助。”知其然,更知其所以然”,共勉!🚀
📅 本文首次发布于 2026 年 5 月 24 日