Web基石:Java Servlet 全面指南:从基础原理到 Spring Boot 实战

这是一份非常详细、实用、通俗易懂、权威且全面的 Java Servlet 指南,涵盖了其方方面面,包括在 Spring Boot 中的应用,并提供了可直接在 IDE 中运行的最佳实践代码和完整案例。


目录

  1. Servlet 概述
    • 1.1 什么是 Servlet?
    • 1.2 为什么需要 Servlet?
    • 1.3 Servlet 与 CGI 的比较
    • 1.4 Servlet 在 Web 应用中的位置
  2. Servlet API 核心
    • 2.1 javax.servlet 包 (jakarta.servlet)
    • 2.2 javax.servlet.http 包 (jakarta.servlet.http)
  3. Servlet 生命周期
    • 3.1 init(ServletConfig config)
    • 3.2 service(ServletRequest req, ServletResponse res)
    • 3.3 destroy()
    • 3.4 生命周期图示与线程模型
  4. HttpServlet 详解
    • 4.1 继承结构
    • 4.2 核心方法:doGet, doPost, doPut, doDelete, doHead, service
    • 4.3 处理 HTTP 请求参数
  5. HttpServletRequest
    • 5.1 获取请求信息 (方法、URI、协议、头信息)
    • 5.2 获取请求参数 (getParameter, getParameterValues, getParameterMap)
    • 5.3 获取请求体 (InputStream/Reader)
    • 5.4 请求属性 (setAttribute, getAttribute)
    • 5.5 请求分发 (RequestDispatcher)
  6. HttpServletResponse
    • 6.1 设置状态码 (setStatus, sendError)
    • 6.2 设置响应头 (setHeader, addHeader, setContentType, setCharacterEncoding)
    • 6.3 获取输出流 (getOutputStream, getWriter)
    • 6.4 重定向 (sendRedirect)
    • 6.5 响应内容类型与编码
  7. 会话管理 (HttpSession)
    • 7.1 会话的概念 (Cookie vs URL Rewriting vs Session)
    • 7.2 获取或创建 Session (request.getSession())
    • 7.3 在 Session 中存储/获取/移除数据 (setAttribute, getAttribute, removeAttribute)
    • 7.4 会话超时配置 (session-timeout)
    • 7.5 使 Session 失效 (invalidate())
  8. Servlet 配置
    • 8.1 ServletConfig
    • 8.2 web.xml 部署描述符配置
      • 声明与映射 Servlet (<servlet>, <servlet-mapping>)
      • 初始化参数 (<init-param>)
      • 加载顺序 (<load-on-startup>)
    • 8.3 基于注解的配置 (@WebServlet)
  9. Servlet 上下文 (ServletContext)
    • 9.1 应用范围共享数据 (setAttribute, getAttribute)
    • 9.2 获取应用初始化参数 (getInitParameter)
    • 9.3 获取资源路径 (getRealPath)
    • 9.4 日志记录 (log)
  10. Servlet 过滤器 (Filter)
    • 10.1 过滤器的概念与作用 (日志、安全、编码转换、压缩等)
    • 10.2 过滤器生命周期 (init, doFilter, destroy)
    • 10.3 配置过滤器 (web.xml@WebFilter)
    • 10.4 过滤器链 (FilterChain)
  11. Servlet 监听器 (Listener)
    • 11.1 监听器的概念与作用
    • 11.2 主要监听器接口 (ServletContextListener, HttpSessionListener, ServletRequestListener 等)
    • 11.3 配置监听器 (web.xml@WebListener)
  12. 异步 Servlet
    • 12.1 传统 Servlet 的阻塞模型限制
    • 12.2 异步处理支持 (@WebServlet(asyncSupported = true))
    • 12.3 AsyncContext 接口 (startAsync, complete, dispatch)
    • 12.4 使用场景 (长轮询、服务器推送、长时间计算)
  13. Servlet 与 Spring Boot
    • 13.1 Spring Boot 对 Servlet 的支持 (嵌入式容器:Tomcat, Jetty, Undertow)
    • 13.2 自动配置 Servlet 组件 (ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean)
    • 13.3 在 Spring Boot 中定义 Servlet/Filter/Listener
      • 使用 @ServletComponentScan + @WebServlet/@WebFilter/@WebListener
      • 使用 @Bean + ServletRegistrationBean/FilterRegistrationBean/ServletListenerRegistrationBean
    • 13.4 Spring MVC DispatcherServlet 与 Servlet 的关系
    • 13.5 在 Spring Boot 中访问 Servlet API (HttpServletRequest, HttpServletResponse, HttpSession)
  14. Servlet 最佳实践
    • 14.1 线程安全注意事项
    • 14.2 资源管理与关闭流
    • 14.3 异常处理
    • 14.4 性能考虑
    • 14.5 安全考虑 (XSS, CSRF, SQL 注入)
  15. 实战案例
    • 案例一:基础用户登录与会话管理
    • 案例二:文件上传 Servlet
    • 案例三:异步 Servlet 实现简单消息推送
    • 案例四:Spring Boot 中自定义 Filter 实现请求日志与耗时统计

1. Servlet 概述

1.1 什么是 Servlet?

Servlet 是运行在 Web 服务器或应用服务器上的 Java 程序 。它充当了客户端(通常是 Web 浏览器)请求服务器上应用程序(数据库、Java 类等) 之间的中间层 。简单来说,Servlet 是用来扩展服务器功能处理 HTTP 请求生成 HTTP 响应的 Java 类。

1.2 为什么需要 Servlet?

  • 动态内容生成: 相比静态 HTML,Servlet 可以根据请求参数、数据库查询结果等动态生成 HTML、JSON、XML 等内容。
  • 高效: Servlet 在服务器端加载后驻留内存,处理后续请求速度快(相比传统的 CGI 为每个请求创建新进程)。
  • 平台独立: Java 的 "Write Once, Run Anywhere" 特性使得 Servlet 可以在任何支持 Java 的服务器上运行。
  • 功能强大: 提供丰富的 API 处理请求、响应、会话、上下文、过滤器等。
  • 可扩展性: 是构建 Java Web 应用(如 JSP、JSF、Spring MVC)的基础。

1.3 Servlet 与 CGI 的比较

特性 CGI Servlet
进程模型 每个请求创建一个新进程 多线程处理请求(轻量级)
性能 启动慢,资源消耗大 启动快,资源消耗小
平台 依赖语言和平台 (Perl, C, Python) 基于 Java,平台独立
持久性 驻留内存,可保持状态
安全性 较低 较高(Java 安全机制)

1.4 Servlet 在 Web 应用中的位置

复制代码
 浏览器 (HTTP Request) -> Web Server (Tomcat, Jetty) -> Servlet Container -> Servlet
                                                                              |
 Servlet (HTTP Response) <- Servlet Container <- Web Server <- 浏览器 (HTTP Response)
  • Web 服务器: 处理网络连接(TCP/IP, HTTP)。
  • Servlet 容器 (Servlet Engine): 管理 Servlet 生命周期,处理请求/响应。常见容器:Tomcat, Jetty, Undertow。
  • Servlet: 业务逻辑处理单元。

2. Servlet API 核心

Servlet API 主要由两个包组成:

2.1 javax.servlet 包 (jakarta.servlet)

  • 包含与协议无关的核心接口和类。
  • 核心接口:
    • Servlet: 定义 Servlet 生命周期方法 (init, service, destroy)。
    • ServletConfig: 提供 Servlet 初始化参数和 ServletContext
    • ServletContext: 代表整个 Web 应用,用于应用级别的共享和资源访问。
    • ServletRequest, ServletResponse: 表示请求和响应的通用接口。
    • RequestDispatcher: 用于请求转发或包含。
    • Filter: 定义过滤器接口。
    • FilterChain: 表示过滤器链。
  • 注意: Jakarta EE 9 及以后,包名从 javax.servlet 迁移到 jakarta.servlet。使用 Tomcat 10+ 或 Jakarta EE 9+ 项目时需注意。

2.2 javax.servlet.http 包 (jakarta.servlet.http)

  • 提供特定于 HTTP 协议的接口和类。
  • 核心接口和类:
    • HttpServlet: 继承自 GenericServlet (实现了 Servlet),提供处理 HTTP 方法 (doGet, doPost 等) 的骨架。
    • HttpServletRequest: 扩展 ServletRequest,提供 HTTP 特有的请求信息(方法、URI、头、Cookie、Session)。
    • HttpServletResponse: 扩展 ServletResponse,提供 HTTP 特有的响应功能(状态码、重定向、头、Cookie)。
    • HttpSession: 用于维护用户会话状态。
    • Cookie: 表示 HTTP Cookie。

3. Servlet 生命周期

Servlet 的生命周期由 Servlet 容器管理,包含三个关键方法:

3.1 init(ServletConfig config)

  • 调用时机: Servlet 实例被创建后,在第一次处理请求之前 ,由容器调用一次
  • 目的: 执行一次性初始化 工作。
    • 读取配置参数 (ServletConfig.getInitParameter())
    • 建立数据库连接池(通常不推荐在此处直接创建,建议使用连接池管理)
    • 加载资源
  • 参数: ServletConfig 对象,包含 Servlet 的配置信息(如初始化参数)和 ServletContext 引用。
  • 注意: 可以覆盖无参数的 init() 方法,但通常使用带 ServletConfig 参数的版本。

3.2 service(ServletRequest req, ServletResponse res)

  • 调用时机: 每次客户端请求到达时,由容器调用。
  • 目的: 处理客户端的请求并生成响应。
  • 参数: ServletRequestServletResponse 对象,分别封装了请求和响应信息。在 HttpServlet 中,通常由更具体的 doGetdoPost 等方法处理。
  • 线程模型: 容器通常会为每个请求创建一个新线程 来调用 service 方法。因此,Servlet 必须是线程安全的!避免使用实例变量存储请求相关的状态。

3.3 destroy()

  • 调用时机: Servlet 实例即将被销毁时(例如应用卸载或服务器关闭),由容器调用一次
  • 目的: 执行清理 工作。
    • 关闭数据库连接(如果在此处打开了)
    • 释放其他长期持有的资源
    • 保存状态到持久存储(如果需要)

3.4 生命周期图示与线程模型

复制代码
              +-------------------+       +-------------------+
              | Servlet Container |       |      Threads       |
              +-------------------+       +-------------------+
                      |                           |
                      | (First Request)           |
                      v                           |
          +---------------------+                 |
          | Instantiate Servlet |                 |
          +---------------------+                 |
                      |                           |
                      | init(config)              |
                      v                           |
          +---------------------+                 |
          | Servlet Ready       |                 |
          +---------------------+                 |
                      |                           |
          /---------- | ----------\               |
         /            |            \              |
+-------v----+  +-----v------+  +---v-------+     |
| Request 1  |  | Request 2  |  | Request n | ... |
+------------+  +------------+  +-----------+     |
        |             |              |            |
        | service(req, res)          |            | (Each in own thread)
        v             v              v            |
+-------------------------------+                 |
| Process Request & Generate Response |           |
+-------------------------------+                 |
        |             |              |            |
        v             v              v            |
+------------+  +------------+  +-----------+     |
| Response 1 |  | Response 2 |  | Response n| ... |
+------------+  +------------+  +-----------+     |
                      |                           |
                      | (App Unload/Server Stop)  |
                      v                           |
          +---------------------+                 |
          | destroy()           |                 |
          +---------------------+                 |
                      |                           |
                      v                           |
          +---------------------+                 |
          | Servlet Destroyed   |                 |
          +---------------------+                 |
  • 关键点: initdestroy 各调用一次,service 为每个请求调用一次。多个请求的 service 方法可能并发执行。

4. HttpServlet 详解

HttpServlet 是开发 HTTP Servlet 最常用的基类。它继承自 GenericServlet

4.1 继承结构

复制代码
java.lang.Object
  |
  +-- GenericServlet (implements Servlet, ServletConfig, Serializable)
        |
        +-- HttpServlet

4.2 核心方法

  • service(HttpServletRequest req, HttpServletResponse resp): 覆盖了 GenericServlet.service(ServletRequest, ServletResponse)。它根据 HTTP 请求方法 (GET, POST, PUT, DELETE, HEAD 等) 调用相应的 doXxx 方法。
  • doGet(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP GET 请求。通常用于安全、幂等的操作,如查询数据。默认实现返回 HTTP 405 (Method Not Allowed) 错误。
  • doPost(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP POST 请求。通常用于提交数据 或执行非幂等操作(如创建资源)。默认实现返回 HTTP 405 错误。
  • doPut(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP PUT 请求。通常用于更新整个资源。默认实现返回 HTTP 405 错误。
  • doDelete(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP DELETE 请求。通常用于删除资源。默认实现返回 HTTP 405 错误。
  • doHead(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP HEAD 请求。与 GET 类似,但只返回头信息,不返回响应体。默认实现调用 doGet 并忽略响应体输出。
  • doOptions(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP OPTIONS 请求,返回服务器支持的 HTTP 方法。默认实现返回允许的方法列表。
  • doTrace(HttpServletRequest req, HttpServletResponse resp): 处理 HTTP TRACE 请求(用于诊断)。默认实现返回客户端发送的请求信息。通常禁用。

最佳实践: 创建自定义 Servlet 时,继承 HttpServlet 并覆盖需要处理的 HTTP 方法对应的 doXxx 方法(最常见的是 doGetdoPost)。

4.3 处理 HTTP 请求参数

请求参数可以通过 URL 查询字符串 (GET) 或请求体 (POST, PUT) 传递。在 HttpServletRequest 中统一处理:

  • String getParameter(String name): 获取单个参数值。如果参数不存在返回 null
  • String[] getParameterValues(String name): 获取多值参数(如复选框)的所有值。如果参数不存在返回 null
  • Map<String, String[]> getParameterMap(): 获取所有请求参数的 Map,键为参数名,值为字符串数组。
  • Enumeration<String> getParameterNames(): 获取所有请求参数名的枚举。

注意: 处理参数时需注意字符编码,通常需要设置请求的编码(如 request.setCharacterEncoding("UTF-8"))以正确解析非 ASCII 字符。但 setCharacterEncoding 必须在 getParameter 之前调用才有效


5. HttpServletRequest

HttpServletRequest 对象封装了客户端发送的所有 HTTP 请求信息。

5.1 获取请求信息

  • HTTP 方法: String getMethod()
  • 请求 URI: String getRequestURI() (如 /myapp/servlet/Hello)
  • 上下文路径 (Context Path): String getContextPath() (如 /myapp)
  • Servlet 路径 (Servlet Path): String getServletPath() (如 /servlet/Hello)
  • 路径信息 (Path Info): String getPathInfo() (URI 中位于 Servlet 路径之后、查询字符串之前的部分,可为 null)
  • 查询字符串 (Query String): String getQueryString() (如 name=John&age=30)
  • 协议: String getProtocol()
  • 远程地址: String getRemoteAddr(), String getRemoteHost()
  • 请求头: String getHeader(String name), Enumeration<String> getHeaderNames(), int getIntHeader(String name), long getDateHeader(String name)
  • Cookie: Cookie[] getCookies()

5.2 获取请求参数

如前所述 (getParameter, getParameterValues, getParameterMap)。

5.3 获取请求体

  • ServletInputStream getInputStream(): 获取二进制输入流(用于文件上传、非文本数据)。
  • BufferedReader getReader(): 获取字符输入流(用于文本数据,如表单 POST、JSON)。注意: getInputStreamgetReader 不能同时调用

5.4 请求属性 (setAttribute, getAttribute)

  • 属性存储在 request 对象中,仅在当前请求范围内有效(从请求开始到生成响应结束)。
  • 常用于在 Servlet 之间或 Servlet 与 JSP 之间传递数据(通过 RequestDispatcher)。
  • void setAttribute(String name, Object object)
  • Object getAttribute(String name)
  • void removeAttribute(String name)
  • Enumeration<String> getAttributeNames()

5.5 请求分发 (RequestDispatcher)

用于将请求转发 (forward)包含 (include) 到另一个 Web 组件(Servlet、JSP 或 HTML)。

  • 获取 RequestDispatcher:

    • RequestDispatcher getRequestDispatcher(String path): path 必须是相对路径(相对于当前上下文根),可以包含其他 Servlet 或 JSP。
    • RequestDispatcher getNamedDispatcher(String name): 通过 Servlet 名称获取。
  • 转发 (forward):

    java 复制代码
    RequestDispatcher dispatcher = request.getRequestDispatcher("/path/to/next");
    dispatcher.forward(request, response);
    • 将请求完全交给目标资源处理,当前 Servlet 不再生成响应。响应由目标资源生成。
    • 浏览器地址栏 URL 不会改变。
    • 只能转发到同一应用内的资源。
  • 包含 (include):

    java 复制代码
    RequestDispatcher dispatcher = request.getRequestDispatcher("/path/to/include");
    dispatcher.include(request, response);
    • 目标资源的输出被包含在当前 Servlet 的响应中。当前 Servlet 可以继续生成响应内容。
    • 常用于模板页面的公共部分(如页眉、页脚)。

6. HttpServletResponse

HttpServletResponse 对象用于生成发送回客户端的 HTTP 响应。

6.1 设置状态码

  • void setStatus(int sc): 设置响应状态码 (如 SC_OK 200, SC_NOT_FOUND 404)。常量定义在 HttpServletResponse 接口中。

  • void sendError(int sc): 发送错误状态码,并可选择包含错误消息。

    java 复制代码
    response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
  • void sendError(int sc, String msg): 同上,并指定错误消息。

6.2 设置响应头

  • void setHeader(String name, String value): 设置一个响应头。如果该头已存在,则覆盖。
  • void addHeader(String name, String value): 添加一个响应头。如果该头允许多个值,则追加。
  • void setContentType(String type): 设置响应内容类型 (text/html, application/json 等)。非常重要,它告诉浏览器如何解释响应体。
  • void setCharacterEncoding(String charset): 设置响应体的字符编码 (如 UTF-8)。通常结合 setContentType 使用:response.setContentType("text/html;charset=UTF-8");
  • void setDateHeader(String name, long date): 设置包含日期值的响应头。
  • void setIntHeader(String name, int value): 设置包含整数值的响应头。
  • 常用头示例:
    • response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); (禁用缓存)
    • response.setHeader("Pragma", "no-cache"); (兼容旧版 HTTP/1.0)
    • response.setHeader("Expires", "0");
    • response.setHeader("Location", "http://www.example.com/newpage"); (用于重定向)
    • response.addCookie(Cookie cookie) (设置 Cookie,内部处理 Set-Cookie 头)

6.3 获取输出流

  • ServletOutputStream getOutputStream(): 获取用于写入二进制数据(如图片、文件)的输出流。
  • PrintWriter getWriter(): 获取用于写入文本数据(如 HTML, JSON)的字符输出流。
  • 注意: getOutputStreamgetWriter 不能同时调用。选择哪个取决于要发送的内容类型。

6.4 重定向 (sendRedirect)

java 复制代码
response.sendRedirect("http://www.example.com/newpage"); // 绝对URL
response.sendRedirect("/myapp/newpath"); // 相对于上下文根的URL
  • 告诉浏览器去请求一个新的 URL。浏览器地址栏会改变。
  • 相当于设置状态码为 302 Found (或 SC_MOVED_TEMPORARILY) 和 Location 头。
  • 转发 (forward) 不同,重定向是客户端的二次请求。

6.5 响应内容类型与编码

  • 务必在获取输出流 之前 设置内容类型 (setContentType) 和字符编码 (setCharacterEncoding 或包含在 setContentType 中)。
  • 常见 MIME 类型:
    • text/html: HTML 文档
    • text/plain: 纯文本
    • application/json: JSON 数据
    • application/xml: XML 数据
    • image/jpeg, image/png, image/gif: 图片
    • application/pdf: PDF 文档
    • application/octet-stream: 二进制流 (文件下载)

7. 会话管理 (HttpSession)

HTTP 是无状态协议。会话管理用于在多个 HTTP 请求间跟踪用户状态

7.1 会话的概念

  • Session: 服务器端为每个用户创建的一个会话对象,用于存储用户相关的数据。
  • Session ID: 唯一标识一个会话的字符串。通常通过 Cookie (JSESSIONID) 或 URL 重写 在客户端和服务器间传递。
    • Cookie (默认方式): 容器自动创建 JSESSIONID Cookie。浏览器每次请求都会带上它。
    • URL 重写: 在 URL 后附加 ;jsessionid=...。用于浏览器禁用 Cookie 的情况。可通过 response.encodeURL(String url)response.encodeRedirectURL(String url) 自动添加。

7.2 获取或创建 Session

java 复制代码
HttpSession session = request.getSession(); // 获取现有session,如果没有则创建一个新的。
HttpSession session = request.getSession(false); // 获取现有session,如果没有则返回null。

7.3 在 Session 中存储/获取/移除数据

  • 存储数据: void setAttribute(String name, Object value)
  • 获取数据: Object getAttribute(String name) (不存在返回 null)
  • 移除数据: void removeAttribute(String name)
  • 获取所有属性名: Enumeration<String> getAttributeNames()

7.4 会话超时配置

  • 容器级别 (web.xml):

    XML 复制代码
    <session-config>
        <session-timeout>30</session-timeout> <!-- 超时时间(分钟),30分钟 -->
    </session-config>
  • Session 级别 (编程):

    java 复制代码
    session.setMaxInactiveInterval(60 * 30); // 设置超时时间(秒),30分钟
    int interval = session.getMaxInactiveInterval(); // 获取当前超时时间(秒)

7.5 使 Session 失效 (invalidate())

  • 当用户注销或会话需要结束时调用。
  • 移除该 Session 对象及其所有属性。
java 复制代码
session.invalidate();

8. Servlet 配置

Servlet 可以通过传统的 web.xml 文件或注解进行配置。

8.1 ServletConfig

  • 每个 Servlet 实例都有自己唯一的 ServletConfig 对象。
  • init(ServletConfig config) 方法中由容器传入。
  • 主要方法:
    • String getServletName(): 获取 Servlet 名称。
    • ServletContext getServletContext(): 获取 ServletContext
    • String getInitParameter(String name): 获取 Servlet 初始化参数。
    • Enumeration<String> getInitParameterNames(): 获取所有初始化参数名。

8.2 web.xml 部署描述符配置

声明 Servlet (<servlet>):

XML 复制代码
<servlet>
    <servlet-name>HelloServlet</servlet-name> <!-- 唯一标识名 -->
    <servlet-class>com.example.HelloServlet</servlet-class> <!-- 完整类名 -->
    <init-param> <!-- 可选:初始化参数 -->
        <param-name>message</param-name>
        <param-value>Welcome!</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup> <!-- 可选:启动时加载顺序 (数字越小优先级越高) -->
</servlet>

映射 Servlet (<servlet-mapping>):

XML 复制代码
<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name> <!-- 指向上面定义的servlet-name -->
    <url-pattern>/hello</url-pattern> <!-- 匹配的URL模式,如 /hello, /greet/*, *.do -->
</servlet-mapping>

8.3 基于注解的配置 (@WebServlet)

  • Servlet 3.0+ 引入了注解,简化配置。
  • 在 Servlet 类上使用 @WebServlet 注解:
java 复制代码
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

@WebServlet(
    name = "AnnotationServlet", // 可选
    urlPatterns = {"/annotated", "/anno/*"}, // 必需:URL 模式数组
    initParams = { // 可选:初始化参数
        @WebInitParam(name = "param1", value = "value1"),
        @WebInitParam(name = "param2", value = "value2")
    },
    loadOnStartup = 1 // 可选:加载顺序
)
public class AnnotationServlet extends HttpServlet {
    // ... doGet, doPost 等方法
}
  • 优点: 简洁,Servlet 定义和映射在一起。
  • 注意: 容器需要支持 Servlet 3.0+。在 web.xml 中设置 <metadata-complete>true</metadata-complete> 会禁用注解扫描。

9. Servlet 上下文 (ServletContext)

ServletContext 代表整个 Web 应用程序。每个 Web 应用只有一个 ServletContext 对象 。它在应用启动时创建,应用卸载时销毁。所有 Servlet、Filter、Listener 共享同一个 ServletContext

9.1 应用范围共享数据

  • 存储数据: void setAttribute(String name, Object object)
  • 获取数据: Object getAttribute(String name) (不存在返回 null)
  • 移除数据: void removeAttribute(String name)
  • 获取所有属性名: Enumeration<String> getAttributeNames()

9.2 获取应用初始化参数

web.xml<context-param> 中定义:

XML 复制代码
<context-param>
    <param-name>jdbcUrl</param-name>
    <param-value>jdbc:mysql://localhost:3306/mydb</param-value>
</context-param>

在 Servlet 中获取:

java 复制代码
String jdbcUrl = getServletContext().getInitParameter("jdbcUrl");

9.3 获取资源路径

  • String getRealPath(String path): 将相对于 Web 应用根目录的虚拟路径转换为文件系统的绝对路径。常用于访问 WEB-INF 目录下的文件。

    java 复制代码
    String configPath = getServletContext().getRealPath("/WEB-INF/config.properties");
  • URL getResource(String path): 获取资源的 URL。

  • InputStream getResourceAsStream(String path): 获取资源的输入流。常用于读取类路径或 WEB-INF 下的文件。

    java 复制代码
    InputStream is = getServletContext().getResourceAsStream("/WEB-INF/config.properties");

9.4 日志记录 (log)

  • void log(String msg): 记录一般信息。
  • void log(String message, Throwable throwable): 记录错误信息和异常堆栈。
  • 日志输出位置由容器配置(通常是服务器的日志文件)。

10. Servlet 过滤器 (Filter)

过滤器在请求到达 Servlet (或其他资源) 之前,或响应发送给客户端之前,拦截请求和响应 。用于执行横切关注点 (Cross-cutting Concerns)

10.1 过滤器的概念与作用

  • 日志记录: 记录请求、响应信息,统计耗时。
  • 安全: 身份验证、授权检查。
  • 数据转换: 请求/响应字符编码设置、数据压缩。
  • 审计: 记录用户操作。
  • 调试: 调试信息注入。

10.2 过滤器生命周期

类似于 Servlet:

  • init(FilterConfig filterConfig): 初始化时调用一次。
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain): 每次请求/响应时调用。必须 调用 chain.doFilter(request, response) 将请求传递给链中的下一个组件(可能是另一个过滤器或目标资源)。
  • destroy(): 销毁时调用一次。

10.3 配置过滤器

web.xml 配置:

XML 复制代码
<filter>
    <filter-name>LoggingFilter</filter-name>
    <filter-class>com.example.LoggingFilter</filter-class>
    <init-param>
        <param-name>logLevel</param-name>
        <param-value>INFO</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- 过滤所有请求 -->
    <!-- 也可用 <servlet-name> 指定特定 Servlet -->
</filter-mapping>

注解配置 (@WebFilter):

java 复制代码
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.Filter;

@WebFilter(
    filterName = "AnnotatedFilter",
    urlPatterns = {"/*"}, // 过滤所有请求
    initParams = {
        @WebInitParam(name = "encoding", value = "UTF-8")
    }
)
public class EncodingFilter implements Filter {
    // ... init, doFilter, destroy 方法
}

10.4 过滤器链 (FilterChain)

  • 多个过滤器可以串联成一个链。

  • doFilter 方法中,调用 chain.doFilter(request, response) 将控制权交给链中的下一个过滤器或目标资源。

  • 目标资源处理完请求后,响应会逆序 经过过滤器链返回给客户端。因此可以在 chain.doFilter 之后 对响应进行处理(如添加头、压缩)。

    java 复制代码
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 前置处理 (请求到达目标资源前)
        chain.doFilter(request, response); // 传递给下一个过滤器或目标资源
        // 后置处理 (目标资源生成响应后)
    }

11. Servlet 监听器 (Listener)

监听器用于监听 Web 应用中发生的事件(如 ServletContext 创建/销毁、Session 创建/销毁/属性更改、Request 创建/销毁)。

11.1 监听器的概念与作用

  • 初始化/清理: 在应用启动/关闭时加载/释放全局资源(如数据库连接池)。
  • 会话跟踪: 统计在线用户数、监听会话创建销毁。
  • 请求监控: 记录请求生命周期。
  • 属性变更: 监听上下文、会话、请求中属性的变化。

11.2 主要监听器接口

  • ServletContextListener: 监听 ServletContext 的创建和销毁事件。
    • void contextInitialized(ServletContextEvent sce): 应用启动,ServletContext 创建后调用。
    • void contextDestroyed(ServletContextEvent sce): 应用关闭,ServletContext 销毁前调用。
  • HttpSessionListener: 监听 HttpSession 的创建和销毁事件。
    • void sessionCreated(HttpSessionEvent se): Session 创建后调用。
    • void sessionDestroyed(HttpSessionEvent se): Session 销毁前调用。
  • ServletRequestListener: 监听 ServletRequest 的创建和销毁事件。
    • void requestInitialized(ServletRequestEvent sre): 请求开始时调用。
    • void requestDestroyed(ServletRequestEvent sre): 请求结束时调用。
  • ServletContextAttributeListener: 监听 ServletContext 中属性的添加、移除、替换事件。
  • HttpSessionAttributeListener: 监听 HttpSession 中属性的添加、移除、替换事件。
  • ServletRequestAttributeListener: 监听 ServletRequest 中属性的添加、移除、替换事件。

11.3 配置监听器

web.xml 配置:

XML 复制代码
<listener>
    <listener-class>com.example.MyContextListener</listener-class>
</listener>

注解配置 (@WebListener):

java 复制代码
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.ServletContextListener;

@WebListener
public class MyContextListener implements ServletContextListener {
    // ... contextInitialized, contextDestroyed 方法
}

12. 异步 Servlet

传统 Servlet 模型是阻塞的:一个线程处理一个请求直到响应完成。对于耗时操作(如长查询、外部服务调用),线程会被长时间占用,影响服务器吞吐量。

12.1 传统 Servlet 的阻塞模型限制

  • 线程池中的线程可能被耗尽。
  • 高并发下性能下降。

12.2 异步处理支持

Servlet 3.0+ 引入了异步处理。通过注解或 web.xml 声明 Servlet 支持异步:

  • 注解: @WebServlet(asyncSupported = true)
  • web.xml: <servlet><async-supported>true</async-supported></servlet>

12.3 AsyncContext 接口

核心对象是 AsyncContext,它代表一个异步操作上下文。

  • 启动异步:doXxx 方法中调用 AsyncContext startAsync(ServletRequest request, ServletResponse response)。这告诉容器当前线程可以释放,异步操作将在另一个线程中完成。
  • 完成异步: 在异步操作完成后,调用 asyncContext.complete() 通知容器请求处理完成。
  • 分发: 也可以调用 asyncContext.dispatch(String path) 将请求分发到另一个资源(Servlet/JSP)去生成响应,类似于 RequestDispatcher,但发生在异步线程中。

12.4 使用场景

  • 长轮询 (Long Polling): 客户端发起请求,服务器在有新数据时才响应。
  • 服务器推送 (Server Push): 服务器主动向客户端推送数据。
  • 长时间计算: 将耗时计算交给后台线程,主线程立即返回。

基本模式:

java 复制代码
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 1. 启动异步上下文
        AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(30000); // 设置超时时间(毫秒)

        // 2. 将耗时任务提交给另一个线程池或Executor
        executor.submit(() -> {
            try {
                // 模拟耗时操作
                Thread.sleep(5000);

                // 3. 在异步线程中获取响应对象并写入响应
                HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
                asyncResponse.setContentType("text/plain");
                asyncResponse.getWriter().write("Async Response after 5 seconds");
            } catch (Exception e) {
                // 处理异常
            } finally {
                // 4. 必须调用 complete() 通知容器请求完成
                asyncContext.complete();
            }
        });
        // doGet 方法立即返回,原始线程释放
    }
}

13. Servlet 与 Spring Boot

Spring Boot 极大地简化了基于 Servlet 的 Web 应用的开发,但它底层仍然运行在 Servlet 容器(如 Tomcat)之上。

13.1 Spring Boot 对 Servlet 的支持

  • 嵌入式容器: Spring Boot 默认将 Servlet 容器(Tomcat, Jetty, Undertow)作为应用的一部分嵌入,无需单独安装和部署 WAR 文件。spring-boot-starter-web 依赖包含了 Tomcat 和 Spring MVC。
  • 自动配置: Spring Boot 自动配置 Servlet 容器、DispatcherServlet、字符编码过滤器等。

13.2 自动配置 Servlet 组件

Spring Boot 提供了 RegistrationBean 来方便地注册 Servlet、Filter、Listener:

  • ServletRegistrationBean: 注册自定义 Servlet。
  • FilterRegistrationBean: 注册自定义 Filter。
  • ServletListenerRegistrationBean: 注册自定义 Listener。

13.3 在 Spring Boot 中定义 Servlet/Filter/Listener

方法一:使用 @ServletComponentScan + @WebServlet/@WebFilter/@WebListener

  1. 在主应用类上添加 @ServletComponentScan

    java 复制代码
    @SpringBootApplication
    @ServletComponentScan // 扫描 @WebServlet, @WebFilter, @WebListener
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
  2. 在 Servlet/Filter/Listener 类上使用相应的注解:

    java 复制代码
    @WebServlet(urlPatterns = "/custom")
    public class CustomServlet extends HttpServlet { ... }
    
    @WebFilter(urlPatterns = "/*")
    public class CustomFilter implements Filter { ... }
    
    @WebListener
    public class CustomListener implements ServletContextListener { ... }

方法二:使用 @Bean + RegistrationBean (更灵活,可控制顺序)

java 复制代码
@Configuration
public class ServletConfig {

    @Bean
    public ServletRegistrationBean<CustomServlet> customServletRegistration() {
        ServletRegistrationBean<CustomServlet> registrationBean = new ServletRegistrationBean<>();
        registrationBean.setServlet(new CustomServlet());
        registrationBean.addUrlMappings("/custom");
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<CustomFilter> customFilterRegistration() {
        FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CustomFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置过滤器顺序
        return registrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean<CustomListener> customListenerRegistration() {
        return new ServletListenerRegistrationBean<>(new CustomListener());
    }
}

13.4 Spring MVC DispatcherServlet 与 Servlet 的关系

  • Spring MVC 的核心是 DispatcherServlet,它是一个标准的 HttpServlet
  • 它充当前端控制器 (Front Controller),接收所有匹配其映射的请求。
  • 它负责将请求路由到合适的控制器 (@Controller)、处理器 (@RestController),处理视图解析、异常处理等。
  • 在 Spring Boot 中,DispatcherServlet 被自动注册,映射路径默认为 / (根路径)。可以通过 spring.mvc.servlet.path 属性修改。

13.5 在 Spring Boot 中访问 Servlet API

在 Spring MVC 的控制器 (@Controller, @RestController) 中,可以直接将 HttpServletRequest, HttpServletResponse, HttpSession 作为方法参数注入:

java 复制代码
@RestController
public class MyController {

    @GetMapping("/info")
    public String getRequestInfo(HttpServletRequest request, HttpServletResponse response) {
        String userAgent = request.getHeader("User-Agent");
        // ... 使用 request, response
        return "Info: " + userAgent;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpSession session) {
        // ... 验证逻辑
        session.setAttribute("user", authenticatedUser);
        return ResponseEntity.ok().build();
    }
}

也可以通过 ServletRequestAttributesRequestContextHolder 在非控制器类中获取:

java 复制代码
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();

14. Servlet 最佳实践

14.1 线程安全注意事项

  • 避免使用实例变量: Servlet 实例通常单例 ,多个请求并发访问 service/doXxx 方法。实例变量会被所有请求共享,导致数据错乱。不要在 Servlet 中使用实例变量存储请求特定状态!
  • 使用局部变量: 请求特定的数据应存储在方法局部变量或请求对象 (request, session) 中。
  • 同步谨慎: 如果必须使用共享资源(如计数器),使用同步机制 (synchronized),但会影响性能。

14.2 资源管理与关闭流

  • finally 块中关闭 InputStream, OutputStream, Reader, Writer, 数据库 Connection, Statement, ResultSet 等资源。

  • 使用 try-with-resources (Java 7+) 自动关闭资源:

    java 复制代码
    try (PrintWriter out = response.getWriter()) {
        out.println("Hello World");
    } // 自动关闭 out

14.3 异常处理

  • 使用 try-catch 捕获并处理预期异常。

  • 对于未捕获的异常,容器通常会生成一个包含堆栈跟踪的错误页面(不友好)。

  • 可以配置错误页面 (web.xml):

    XML 复制代码
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error.jsp</location>
    </error-page>
    <error-page>
        <error-code>404</error-code>
        <location>/notfound.html</location>
    </error-page>
  • 在 Servlet 中,使用 response.sendError() 发送特定错误状态码和消息。

14.4 性能考虑

  • 避免同步: 如非必要,避免使用 synchronized
  • 使用连接池: 数据库连接、HTTP 客户端连接等使用连接池管理。
  • 缓存: 对频繁访问且变化不频繁的数据使用缓存。
  • 异步处理: 对于耗时操作,考虑使用异步 Servlet。
  • 优化数据库查询: 使用索引,避免 N+1 查询。

14.5 安全考虑

  • 防止跨站脚本攻击 (XSS): 对用户输入进行输出编码,避免直接将用户输入输出到 HTML/JS 中。使用库如 OWASP Java Encoder。
  • 防止跨站请求伪造 (CSRF): 使用框架提供的 CSRF 保护机制(如 Spring Security),或生成并验证 Token。
  • 防止 SQL 注入: 使用 PreparedStatement 进行数据库操作,永远不要拼接 SQL 字符串。
  • 输入验证: 验证所有用户输入(类型、长度、格式、范围)。
  • 敏感数据处理: 密码等敏感数据应加盐哈希存储,避免明文。
  • HTTPS: 使用 HTTPS 加密传输数据。
  • 最小权限原则: 应用程序连接数据库等资源时使用最小必要权限的账户。
  • 安全配置: 确保服务器、框架的安全配置正确(如禁用不安全的 HTTP 方法)。

15. 实战案例

案例一:基础用户登录与会话管理

功能: 用户通过表单提交用户名和密码,验证成功后创建 Session 记录登录状态,访问受保护页面需要登录。

java 复制代码
// LoginServlet.java
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    // 简单的内存用户存储 (实际应用中应从数据库获取)
    private Map<String, String> validUsers = new HashMap<>();
    {
        validUsers.put("admin", "admin123"); // 弱密码仅作示例,实际应用需加盐哈希
        validUsers.put("user", "userpass");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 显示登录表单
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("<h2>用户登录</h2>");
        out.println("<form method='POST'>");
        out.println("用户名: <input type='text' name='username'><br>");
        out.println("密码: <input type='password' name='password'><br>");
        out.println("<input type='submit' value='登录'>");
        out.println("</form>");
        out.println("</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 验证用户名和密码
        if (validUsers.containsKey(username) && validUsers.get(username).equals(password)) {
            // 登录成功,创建 Session
            HttpSession session = request.getSession();
            session.setAttribute("user", username);
            // 重定向到欢迎页面
            response.sendRedirect("welcome");
        } else {
            // 登录失败
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<html><body>");
            out.println("<h2>登录失败!用户名或密码错误。</h2>");
            out.println("<a href='login'>返回登录</a>");
            out.println("</body></html>");
        }
    }
}
java 复制代码
// WelcomeServlet.java (受保护资源)
@WebServlet("/welcome")
public class WelcomeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 检查用户是否登录 (Session 中是否有 user)
        HttpSession session = request.getSession(false); // 不创建新session
        if (session == null || session.getAttribute("user") == null) {
            // 未登录,重定向到登录页
            response.sendRedirect("login");
            return;
        }

        // 用户已登录,显示欢迎信息
        String username = (String) session.getAttribute("user");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("<h2>欢迎, " + username + "!</h2>");
        out.println("<p>这是您的个人主页。</p>");
        out.println("<a href='logout'>注销</a>");
        out.println("</body></html>");
    }
}
java 复制代码
// LogoutServlet.java
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取 Session (如果存在)
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 使 Session 失效
            session.invalidate();
        }
        // 重定向到登录页
        response.sendRedirect("login");
    }
}

案例二:文件上传 Servlet

功能: 允许用户上传文件到服务器。

java 复制代码
@WebServlet("/upload")
@MultipartConfig // 必须的注解,用于处理 multipart/form-data 请求
public class FileUploadServlet extends HttpServlet {

    private static final String UPLOAD_DIR = "uploads"; // 上传目录 (相对于应用根目录)

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 确保上传目录存在
        String appPath = request.getServletContext().getRealPath("");
        String uploadPath = appPath + File.separator + UPLOAD_DIR;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        try {
            // 获取上传的文件部分
            Part filePart = request.getPart("file"); // "file" 是表单中文件字段的 name
            String fileName = getSubmittedFileName(filePart); // 获取原始文件名

            // 防止路径遍历攻击 (确保只有文件名,没有路径)
            fileName = new File(fileName).getName();

            // 写入文件
            String filePath = uploadPath + File.separator + fileName;
            filePart.write(filePath);

            out.println("<html><body>");
            out.println("<h2>文件上传成功!</h2>");
            out.println("<p>文件名: " + fileName + "</p>");
            out.println("<p>保存路径: " + filePath + "</p>");
            out.println("<a href='upload.html'>继续上传</a>"); // 假设有一个上传表单页面
            out.println("</body></html>");

        } catch (Exception e) {
            out.println("<html><body>");
            out.println("<h2>上传失败!</h2>");
            out.println("<p>错误: " + e.getMessage() + "</p>");
            out.println("</body></html>");
            e.printStackTrace();
        }
    }

    // 辅助方法:从 Part 中提取原始文件名 (不同容器实现可能不同)
    private String getSubmittedFileName(Part part) {
        for (String content : part.getHeader("content-disposition").split(";")) {
            if (content.trim().startsWith("filename")) {
                return content.substring(content.indexOf('=') + 1).trim().replace("\"", "");
            }
        }
        return null;
    }
}

对应的上传表单 (upload.html):

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    <h2>上传文件</h2>
    <form action="upload" method="POST" enctype="multipart/form-data">
        选择文件: <input type="file" name="file"><br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

案例三:异步 Servlet 实现简单消息推送

功能: 模拟一个长轮询接口,客户端请求后,服务器在 5 秒后返回响应。

java 复制代码
@WebServlet(urlPatterns = "/longpoll", asyncSupported = true)
public class LongPollingServlet extends HttpServlet {

    private ExecutorService executor = Executors.newCachedThreadPool();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 启动异步上下文
        AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(30000); // 设置超时时间 30秒

        // 提交一个延迟任务到线程池
        executor.submit(() -> {
            try {
                // 模拟耗时操作 (等待 5 秒)
                Thread.sleep(5000);

                // 获取异步响应对象
                HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse();
                asyncResponse.setContentType("text/plain");
                asyncResponse.getWriter().write("Server response after 5 seconds delay.");
            } catch (Exception e) {
                // 处理异常
                asyncContext.getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } finally {
                // 完成异步操作
                asyncContext.complete();
            }
        });
        // doGet 方法立即返回
    }
}

案例四:Spring Boot 中自定义 Filter 实现请求日志与耗时统计

功能: 记录每个请求的 URL、方法、客户端 IP、处理耗时。

java 复制代码
// LoggingFilter.java
public class LoggingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        long startTime = System.currentTimeMillis();
        // 记录请求信息
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String uri = httpRequest.getRequestURI();
        String method = httpRequest.getMethod();
        String clientIP = httpRequest.getRemoteAddr();

        // 继续处理请求链
        chain.doFilter(request, response);

        // 计算耗时
        long duration = System.currentTimeMillis() - startTime;
        // 记录响应信息 (状态码需转换类型)
        int status = ((HttpServletResponse) response).getStatus();

        logger.info("{} {} from IP: {}, Status: {}, Duration: {} ms", method, uri, clientIP, status, duration);
    }

    // ... init, destroy 方法 (可选)
}

在 Spring Boot 中注册 Filter (使用 @Bean):

java 复制代码
@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/*"); // 过滤所有请求
        registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE - 1); // 尽量靠后执行,确保能记录最终状态
        return registrationBean;
    }
}

这份指南涵盖了 Java Servlet 的核心概念、API、生命周期、配置、高级特性(过滤器、监听器、异步处理)以及在 Spring Boot 中的集成方式,并提供了可直接运行的实战案例。希望它能成为您学习和开发 Servlet 应用的权威参考。


觉得文章不错,请多多支持,关注我!

相关推荐
爬山算法2 小时前
Hibernate(86)如何在性能测试中使用Hibernate?
java·后端·hibernate
菜鸟小杰子2 小时前
Spring Boot集成asyncTool:复杂任务的优雅编排与高效执行(实战优化版)
java·spring boot·后端
茶本无香2 小时前
Spring 异步执行器(Executor)配置策略与命名实践
java·spring·多线程·异步
弹简特2 小时前
【JavaEE06-后端部分】SpringMVC01-Spring MVC第一大核心URL 路由映射【建立连接】与 Postman 接口测试详解
java·spring boot·测试工具·spring·postman
rannn_1112 小时前
【苍穹外卖|Day3】公共字段自动填充、新增菜品功能、菜品分页查询功能、删除菜品功能、修改菜品功能、起售停售菜品
java·spring boot·后端·学习·项目
无名-CODING2 小时前
SpringMVC处理流程完全指南:从请求到响应的完整旅程
java·后端·spring
瑶山2 小时前
Spring Cloud微服务搭建三、分布式任务调度XXL-JOB
java·spring cloud·微服务·xxljob
Re.不晚2 小时前
深入底层理解HashMap——妙哉妙哉的结构!!
java·哈希算法
Serene_Dream2 小时前
Java 内存区域
java·jvm