欢迎光临
我们一直在努力

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

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


目录

  • Servlet 概述
    • 1.1 什么是 Servlet?
    • 1.2 为什么需要 Servlet?
    • 1.3 Servlet 与 CGI 的比较
    • 1.4 Servlet 在 Web 应用中的位置
  • Servlet API 核心
    • 2.1 javax.servlet 包 (jakarta.servlet)
    • 2.2 javax.servlet.http 包 (jakarta.servlet.http)
  • Servlet 生命周期
    • 3.1 init(ServletConfig config)
    • 3.2 service(ServletRequest req, ServletResponse res)
    • 3.3 destroy()
    • 3.4 生命周期图示与线程模型
  • HttpServlet 详解
    • 4.1 继承结构
    • 4.2 核心方法:doGet, doPost, doPut, doDelete, doHead, service
    • 4.3 处理 HTTP 请求参数
  • HttpServletRequest
    • 5.1 获取请求信息 (方法、URI、协议、头信息)
    • 5.2 获取请求参数 (getParameter, getParameterValues, getParameterMap)
    • 5.3 获取请求体 (InputStream/Reader)
    • 5.4 请求属性 (setAttribute, getAttribute)
    • 5.5 请求分发 (RequestDispatcher)
  • HttpServletResponse
    • 6.1 设置状态码 (setStatus, sendError)
    • 6.2 设置响应头 (setHeader, addHeader, setContentType, setCharacterEncoding)
    • 6.3 获取输出流 (getOutputStream, getWriter)
    • 6.4 重定向 (sendRedirect)
    • 6.5 响应内容类型与编码
  • 会话管理 (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())
  • Servlet 配置
    • 8.1 ServletConfig
    • 8.2 web.xml 部署描述符配置
      • 声明与映射 Servlet (<servlet>, <servlet-mapping>)
      • 初始化参数 (<init-param>)
      • 加载顺序 (<load-on-startup>)
    • 8.3 基于注解的配置 (@WebServlet)
  • Servlet 上下文 (ServletContext)
    • 9.1 应用范围共享数据 (setAttribute, getAttribute)
    • 9.2 获取应用初始化参数 (getInitParameter)
    • 9.3 获取资源路径 (getRealPath)
    • 9.4 日志记录 (log)
  • Servlet 过滤器 (Filter)
    • 10.1 过滤器的概念与作用 (日志、安全、编码转换、压缩等)
    • 10.2 过滤器生命周期 (init, doFilter, destroy)
    • 10.3 配置过滤器 (web.xml 或 @WebFilter)
    • 10.4 过滤器链 (FilterChain)
  • Servlet 监听器 (Listener)
    • 11.1 监听器的概念与作用
    • 11.2 主要监听器接口 (ServletContextListener, HttpSessionListener, ServletRequestListener 等)
    • 11.3 配置监听器 (web.xml 或 @WebListener)
  • 异步 Servlet
    • 12.1 传统 Servlet 的阻塞模型限制
    • 12.2 异步处理支持 (@WebServlet(asyncSupported = true))
    • 12.3 AsyncContext 接口 (startAsync, complete, dispatch)
    • 12.4 使用场景 (长轮询、服务器推送、长时间计算)
  • 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)
  • Servlet 最佳实践
    • 14.1 线程安全注意事项
    • 14.2 资源管理与关闭流
    • 14.3 异常处理
    • 14.4 性能考虑
    • 14.5 安全考虑 (XSS, CSRF, SQL 注入)
  • 实战案例
    • 案例一:基础用户登录与会话管理
    • 案例二:文件上传 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 的比较

    特性CGIServlet
    进程模型 每个请求创建一个新进程 多线程处理请求(轻量级)
    性能 启动慢,资源消耗大 启动快,资源消耗小
    平台 依赖语言和平台 (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)

    • 调用时机: 每次客户端请求到达时,由容器调用。
    • 目的: 处理客户端的请求并生成响应。
    • 参数: ServletRequest 和 ServletResponse 对象,分别封装了请求和响应信息。在 HttpServlet 中,通常由更具体的 doGet、doPost 等方法处理。
    • 线程模型: 容器通常会为每个请求创建一个新线程来调用 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 | |
    +———————+ |

    • 关键点: init 和 destroy 各调用一次,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 方法(最常见的是 doGet 和 doPost)。

    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)。注意: getInputStream 和 getReader 不能同时调用。

    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):

      RequestDispatcher dispatcher = request.getRequestDispatcher("/path/to/next");
      dispatcher.forward(request, response);

      • 将请求完全交给目标资源处理,当前 Servlet 不再生成响应。响应由目标资源生成。
      • 浏览器地址栏 URL 不会改变。
      • 只能转发到同一应用内的资源。
    • 包含 (include):

      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): 发送错误状态码,并可选择包含错误消息。

      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)的字符输出流。
    • 注意: getOutputStream 和 getWriter 不能同时调用。选择哪个取决于要发送的内容类型。

    6.4 重定向 (sendRedirect)

    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

    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):

      <session-config>
      <session-timeout>30</session-timeout> <!– 超时时间(分钟),30分钟 –>
      </session-config>

    • Session 级别 (编程):

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

    7.5 使 Session 失效 (invalidate())

    • 当用户注销或会话需要结束时调用。
    • 移除该 Session 对象及其所有属性。

    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>):

    <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>):

    <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 注解:

    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> 中定义:

    <context-param>
    <param-name>jdbcUrl</param-name>
    <param-value>jdbc:mysql://localhost:3306/mydb</param-value>
    </context-param>

    在 Servlet 中获取:

    String jdbcUrl = getServletContext().getInitParameter("jdbcUrl");

    9.3 获取资源路径

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

      String configPath = getServletContext().getRealPath("/WEB-INF/config.properties");

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

      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 配置:

    <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):

    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 之后 对响应进行处理(如添加头、压缩)。

      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 配置:

    <listener>
    <listener-class>com.example.MyContextListener</listener-class>
    </listener>

    注解配置 (@WebListener):

    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): 服务器主动向客户端推送数据。
    • 长时间计算: 将耗时计算交给后台线程,主线程立即返回。

    基本模式:

    @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

  • 在主应用类上添加 @ServletComponentScan:

    @SpringBootApplication
    @ServletComponentScan // 扫描 @WebServlet, @WebFilter, @WebListener
    public class MyApplication {
    public static void main(String[] args) {
    SpringApplication.run(MyApplication.class, args);
    }
    }

  • 在 Servlet/Filter/Listener 类上使用相应的注解:

    @WebServlet(urlPatterns = "/custom")
    public class CustomServlet extends HttpServlet { … }

    @WebFilter(urlPatterns = "/*")
    public class CustomFilter implements Filter { … }

    @WebListener
    public class CustomListener implements ServletContextListener { … }

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

    @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 作为方法参数注入:

    @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();
    }
    }

    也可以通过 ServletRequestAttributes 和 RequestContextHolder 在非控制器类中获取:

    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+) 自动关闭资源:

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

    14.3 异常处理

    • 使用 try-catch 捕获并处理预期异常。
    • 对于未捕获的异常,容器通常会生成一个包含堆栈跟踪的错误页面(不友好)。
    • 可以配置错误页面 (web.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 记录登录状态,访问受保护页面需要登录。

    // 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>");
    }
    }
    }

    // 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>");
    }
    }

    // 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

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

    @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):

    <!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 秒后返回响应。

    @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、处理耗时。

    // 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):

    @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 应用的权威参考。


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

    赞(0)
    未经允许不得转载:171主机测评 » Web基石:Java Servlet 全面指南:从基础原理到 Spring Boot 实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址