WD
Classnote Docs课程课件
08

08 Web综合

学习目标:

  • 掌握 ServletContextListener 的使用,理解其执行时机和生命周期
  • 掌握 Filter 的使用,理解其执行时机和过滤器链机制
  • 能够使用 Filter 解决实际开发中的常见问题(字符编码、登录验证、日志记录等)
  • 理解三大 Web 组件的协作关系

本章重点:

  • Listener 与 Filter 在 Web 请求链路中的职责定位
  • ServletContextListener 的初始化与资源清理场景
  • Filter 的执行顺序、过滤器链机制与拦截/放行逻辑
  • 字符编码、登录验证、日志记录、跨域处理等典型过滤器场景
01 / Section

1. 前置知识准备

  • Servlet的执行流程和生命周期
  • ServletContext的功能和使用
  • HTTP请求/响应处理
02 / Section

2. Web组件概述

JavaEE定义了三大Web组件,它们共同构成了Web应用的核心处理机制:

组件 职责 执行时机
Servlet 处理请求对应的业务逻辑 接收到请求时
Listener 监听Web应用生命周期事件 应用启动/关闭时
Filter 对请求/响应进行过滤处理 Servlet前后

三者的执行顺序:Listener → Filter → Servlet → Filter

03 / Section

3. Listener监听器

3.1 什么是监听器

监听器(Listener)是JavaEE提供的事件监听机制。监听器会监听特定的主体(如ServletContext、HttpSession、ServletRequest等),当主体发生特定事件(初始化、销毁、属性变化等)时,自动触发对应的方法。

💡 核心思想:通过监听机制,在关键生命周期节点自动执行初始化或清理操作。

3.2 ServletContextListener

ServletContextListener是最常用的监听器,用于监听ServletContext的生命周期:

事件 触发时机 对应方法
初始化 应用程序启动时 contextInitialized()
销毁 应用程序关闭时 contextDestroyed()

生命周期说明

text
应用程序启动
    ↓
ServletContext初始化
    ↓
触发contextInitialized()  ← 在这里进行资源初始化
    ↓
应用运行中...
    ↓
应用程序关闭
    ↓
ServletContext销毁
    ↓
触发contextDestroyed()   ← 在这里进行资源释放

3.2.1 执行过程图解

当应用程序启动时,Web组件按以下顺序加载:

  1. 首先加载:ServletContext 和 Listener
  2. 然后加载:loadOnStartup ≥ 0 的Servlet
diagram
应用启动 创建ServletContext 执行ServletContextListener.contextInitialized() 初始化全局资源(如数据库连接池、SqlSessionFactory) 初始化loadOnStartup ≥ 0的Servlet 应用就绪,等待请求
应用启动
   ├── 创建ServletContext
   ├── 执行ServletContextListener.contextInitialized()
   │       └── 初始化全局资源(如数据库连接池、SqlSessionFactory)
   ├── 初始化loadOnStartup ≥ 0的Servlet
   └── 应用就绪,等待请求

3.3 配置方式

方式一:注解配置(推荐)

java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener  // 无需指定value,自动监听
public class CustomServletContextListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // 应用启动时执行
        System.out.println("✅ 应用程序启动,ServletContext初始化完成");
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 应用关闭时执行
        System.out.println("❌ 应用程序关闭,ServletContext销毁");
    }
}

方式二:web.xml配置

xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <!-- 监听器配置 -->
    <listener>
        <listener-class>com.example.listener.CustomServletContextListener</listener-class>
    </listener>
    
</web-app>

⚠️ 勘误说明:两种方式等效,但注解配置更简洁,是Servlet 3.0+的推荐方式。

<!-- [PRACTICE-START] 实战练习内容已提取到 09-web-integration-practice.md -->

3.4 实战案例:MyBatis集成优化

场景描述

之前整合MyBatis时,SqlSessionFactory在Servlet的init()方法中初始化。更好的做法是在应用启动时就完成初始化,并存入ServletContext供全局使用。

完整代码实现

监听器代码

java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.InputStream;

@WebListener
public class MyBatisInitListener implements ServletContextListener {
    
    private static final String CONFIG_FILE = "mybatis-config.xml";
    private static final String CONTEXT_KEY = "SqlSessionFactory";
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        
        try (InputStream is = Resources.getResourceAsStream(CONFIG_FILE)) {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
            
            // 存入ServletContext,全局共享
            context.setAttribute(CONTEXT_KEY, factory);
            
            System.out.println("✅ MyBatis初始化成功,SqlSessionFactory已存入ServletContext");
        } catch (IOException e) {
            System.err.println("❌ MyBatis初始化失败: " + e.getMessage());
            throw new RuntimeException("MyBatis初始化失败", e);
        }
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 清理资源
        ServletContext context = sce.getServletContext();
        context.removeAttribute(CONTEXT_KEY);
        System.out.println("✅ MyBatis资源已清理");
    }
}

Servlet中使用

java
import org.apache.ibatis.session.SqlSessionFactory;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/user/list")
public class UserListServlet extends HttpServlet {
    
    private SqlSessionFactory sqlSessionFactory;
    
    @Override
    public void init() throws ServletException {
        // 从ServletContext获取已初始化的工厂
        sqlSessionFactory = (SqlSessionFactory) getServletContext()
                .getAttribute("SqlSessionFactory");
        
        if (sqlSessionFactory == null) {
            throw new ServletException("SqlSessionFactory未初始化");
        }
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 使用sqlSessionFactory执行业务操作
        // ...
    }
}

<!-- [PRACTICE-END] 练习一:MyBatis集成优化 结束 -->

3.5 其他常用监听器

监听器接口 监听对象 用途
HttpSessionListener Session 统计在线人数、Session超时处理
ServletRequestListener Request 请求日志记录、性能监控
HttpSessionAttributeListener Session属性 监听登录/登出状态变化
04 / Section

4. Filter过滤器

4.1 什么是过滤器

Filter(过滤器)是运行在Servlet之前的组件,可以对请求进行预处理,对响应进行后处理。

执行位置

text
客户端请求
    ↓
Filter预处理(请求过滤)
    ↓
Servlet业务处理
    ↓
Filter后处理(响应过滤)
    ↓
返回客户端

4.2 Filter和Servlet的映射关系

对比项 Servlet Filter
URL映射 1个URL → 1个Servlet 1个URL → 多个Filter
执行顺序 按URL匹配 按配置顺序
终止性 处理请求 可选择放行或拦截

📌 重要:一个请求可以经过多个Filter,形成过滤器链(FilterChain)

4.3 过滤器链执行流程

单个Filter执行流程

text
请求 → Filter.doFilter()前半部分 
          ↓
      chain.doFilter()  ← 放行
          ↓
      Servlet处理
          ↓
      Filter.doFilter()后半部分 → 响应

多个Filter执行流程

假设配置了FilterA、FilterB、FilterC三个过滤器:

text
请求 → FilterA → FilterB → FilterC → Servlet
                                      ↓
响应 ← FilterA ← FilterB ← FilterC ← 处理完成

执行顺序遵循先进后出(类似栈结构):

text
FilterA开始
  FilterB开始
    FilterC开始
      Servlet处理
    FilterC结束
  FilterB结束
FilterA结束

关键方法说明

java
// 放行方法:必须调用才能继续后续流程
filterChain.doFilter(request, response);

⚠️ 勘误说明:如果不调用chain.doFilter(),请求将被拦截,不会到达Servlet。

4.4 Filter生命周期

阶段 方法 执行次数 用途
初始化 init(FilterConfig) 1次 读取配置参数
过滤 doFilter() 每次请求 执行过滤逻辑
销毁 destroy() 1次 释放资源

4.5 配置方式

方式一:注解配置(推荐)

java
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")  // 过滤所有请求
public class LoggingFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作
        String encoding = filterConfig.getInitParameter("encoding");
        System.out.println("Filter初始化,编码参数: " + encoding);
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        // 前置处理
        System.out.println("请求到达Filter");
        
        // 放行
        chain.doFilter(request, response);
        
        // 后置处理
        System.out.println("响应离开Filter");
    }
    
    @Override
    public void destroy() {
        // 销毁操作
        System.out.println("Filter销毁");
    }
}

常用URL模式

模式 含义 示例
/* 所有请求 全局字符编码
/user/* 以/user/开头的请求 登录验证
/admin/* 以/admin/开头的请求 权限控制
*.jsp 所有JSP页面 页面权限

方式二:web.xml配置

xml
<web-app>
    <!-- Filter定义 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>com.example.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    
    <!-- Filter映射 -->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

⚠️ 勘误说明:多个Filter时,web.xml中的配置顺序决定了执行顺序;注解配置时,Filter名称的字典序影响顺序(建议使用web.xml精确控制)。

<!-- [PRACTICE-START] 实战练习内容已提取到 09-web-integration-practice.md -->

4.6 实战案例

案例一:字符编码过滤器(最常用)

问题描述:POST请求中文乱码、响应中文乱码。

解决方案

java
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 字符编码过滤器
 * 统一设置请求和响应的字符编码为UTF-8
 */
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
    
    private String encoding = "UTF-8";
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 读取初始化参数
        String configEncoding = filterConfig.getInitParameter("encoding");
        if (configEncoding != null && !configEncoding.isEmpty()) {
            this.encoding = configEncoding;
        }
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 设置请求编码(解决POST中文乱码)
        httpRequest.setCharacterEncoding(encoding);
        
        // 设置响应编码(解决响应中文乱码)
        httpResponse.setContentType("application/json;charset=" + encoding);
        // 或者 HTML: text/html;charset=UTF-8
        
        // 放行
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
        // 清理资源
    }
}

案例二:登录验证过滤器

需求:访问/user/*、/order/*等路径时,必须已登录。

java
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * 登录验证过滤器
 * 拦截需要登录才能访问的资源
 */
@WebFilter({"/user/*", "/order/*", "/cart/*"})
public class LoginCheckFilter implements Filter {
    
    // 白名单:不需要登录就能访问的路径
    private static final List<String> WHITE_LIST = Arrays.asList(
        "/user/login",
        "/user/register",
        "/user/logout"
    );
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        String requestURI = httpRequest.getRequestURI();
        String contextPath = httpRequest.getContextPath();
        String path = requestURI.substring(contextPath.length());
        
        // 1. 检查是否在白名单中
        if (isWhiteListed(path)) {
            chain.doFilter(request, response);
            return;
        }
        
        // 2. 检查是否已登录
        HttpSession session = httpRequest.getSession(false);
        Object user = (session != null) ? session.getAttribute("user") : null;
        
        if (user == null) {
            // 未登录,返回401或重定向到登录页
            // AJAX请求
            if (isAjaxRequest(httpRequest)) {
                httpResponse.setStatus(401);
                httpResponse.setContentType("application/json;charset=UTF-8");
                httpResponse.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}");
            } else {
                // 普通请求,重定向到登录页
                httpResponse.sendRedirect(contextPath + "/login.html");
            }
            return;  // 不放行,中断请求
        }
        
        // 已登录,放行
        chain.doFilter(request, response);
    }
    
    /**
     * 检查路径是否在白名单中
     */
    private boolean isWhiteListed(String path) {
        return WHITE_LIST.stream().anyMatch(path::startsWith);
    }
    
    /**
     * 判断是否为AJAX请求
     */
    private boolean isAjaxRequest(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void destroy() {}
}

案例三:请求日志记录过滤器

java
import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;/** * 请求日志过滤器 * 记录每个请求的访问信息 */@WebFilter("/*")public class RequestLoggingFilter implements Filter { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // 记录请求开始时间 long startTime = System.currentTimeMillis(); // 获取请求信息 String clientIP = getClientIP(httpRequest); String method = httpRequest.getMethod(); String uri = httpRequest.getRequestURI(); String userAgent = httpRequest.getHeader("User-Agent"); System.out.println(""); System.out.println(" 【请求时间】" + LocalDateTime.now().format(FORMATTER)); System.out.println(" 【客户端IP】" + clientIP); System.out.println(" 【请求方式】" + method); System.out.println(" 【请求路径】" + uri); System.out.println(" 【浏览器】" + (userAgent != null ? userAgent.substring(0, Math.min(userAgent.length(), 50)) : "Unknown")); try { // 放行 chain.doFilter(request, response); } finally { // 记录响应时间 long duration = System.currentTimeMillis() - startTime; System.out.println(" 【处理耗时】" + duration + "ms"); System.out.println(""); } } /** * 获取客户端真实IP(考虑反向代理) */ private String getClientIP(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty()) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.isEmpty()) { ip = request.getRemoteAddr(); } // 多个IP时取第一个 if (ip != null && ip.contains(",")) { ip = ip.split(",")[0].trim(); } return ip; } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {}}
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 请求日志过滤器
 * 记录每个请求的访问信息
 */
@WebFilter("/*")
public class RequestLoggingFilter implements Filter {
    
    private static final DateTimeFormatter FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        
        // 获取请求信息
        String clientIP = getClientIP(httpRequest);
        String method = httpRequest.getMethod();
        String uri = httpRequest.getRequestURI();
        String userAgent = httpRequest.getHeader("User-Agent");
        
        System.out.println("┌──────────────────────────────────────────");
        System.out.println("│ 【请求时间】" + LocalDateTime.now().format(FORMATTER));
        System.out.println("│ 【客户端IP】" + clientIP);
        System.out.println("│ 【请求方式】" + method);
        System.out.println("│ 【请求路径】" + uri);
        System.out.println("│ 【浏览器】" + (userAgent != null ? userAgent.substring(0, 
            Math.min(userAgent.length(), 50)) : "Unknown"));
        
        try {
            // 放行
            chain.doFilter(request, response);
        } finally {
            // 记录响应时间
            long duration = System.currentTimeMillis() - startTime;
            System.out.println("│ 【处理耗时】" + duration + "ms");
            System.out.println("└──────────────────────────────────────────");
        }
    }
    
    /**
     * 获取客户端真实IP(考虑反向代理)
     */
    private String getClientIP(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        // 多个IP时取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void destroy() {}
}

案例四:CORS跨域过滤器

java
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 跨域请求处理过滤器
 * 解决前后端分离项目的跨域问题
 */
@WebFilter("/*")
public class CORSFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 允许的源(生产环境应配置具体域名)
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许的请求方法
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        // 允许的请求头
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
        // 预检请求缓存时间
        httpResponse.setHeader("Access-Control-Max-Age", "3600");
        // 允许携带Cookie
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        
        // 预检请求直接返回
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        
        chain.doFilter(request, response);
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void destroy() {}
}

<!-- [PRACTICE-END] 练习二至练习五(Filter实战案例)结束 -->

05 / Section

5. 常见问题与解决方案

5.1 Filter相关问题

Q1: Filter不生效怎么办?

排查步骤

  1. 检查URL模式:确认请求的URL匹配Filter的url-pattern
  2. 检查配置方式:注解和web.xml不要重复配置,可能冲突
  3. 检查是否放行:确认调用了chain.doFilter()
  4. 检查执行顺序:可能被前面的Filter拦截了

Q2: 多个Filter的执行顺序怎么控制?

解决方案

配置方式 顺序控制方法
web.xml <filter-mapping>的配置顺序
注解 无法控制(按类名字典序),建议使用web.xml

推荐在web.xml中集中配置Filter顺序:

xml
<!-- 先执行字符编码 -->
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 再执行登录验证 -->
<filter-mapping>
    <filter-name>LoginCheckFilter</filter-name>
    <url-pattern>/user/*</url-pattern>
</filter-mapping>

Q3: Filter中如何获取Spring容器中的Bean?

java
// 通过WebApplicationContext获取
WebApplicationContext context = WebApplicationContextUtils
    .getRequiredWebApplicationContext(request.getServletContext());
UserService userService = context.getBean(UserService.class);

5.2 Listener相关问题

Q1: contextInitialized执行两次?

原因:Tomcat配置了多个Context,或IDEA热部署导致。

解决:在方法中添加判断,避免重复初始化:

java
@Override
public void contextInitialized(ServletContextEvent sce) {
    ServletContext context = sce.getServletContext();
    // 检查是否已初始化
    if (context.getAttribute("SqlSessionFactory") != null) {
        return;  // 已存在,跳过
    }
    // 初始化代码...
}

Q2: Listener中获取不到数据库连接?

原因:数据库驱动在Listener初始化时还未加载。

解决:确保数据库驱动JAR在WEB-INF/lib中,或使用延迟加载。

5.3 其他常见问题

Q3: 如何设置Filter的初始化参数?

注解方式

java
@WebFilter(
    value = "/*",
    initParams = {
        @WebInitParam(name = "encoding", value = "UTF-8"),
        @WebInitParam(name = "forceEncoding", value = "true")
    }
)
public class CharacterEncodingFilter implements Filter {
    private String encoding;
    
    @Override
    public void init(FilterConfig config) throws ServletException {
        this.encoding = config.getInitParameter("encoding");
    }
}

Q4: 如何排除特定路径不进行过滤?

java
@Override
public void doFilter(ServletRequest request, ServletResponse response, 
                     FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String path = httpRequest.getRequestURI();
    
    // 排除静态资源
    if (path.startsWith("/static/") || path.endsWith(".js") || path.endsWith(".css")) {
        chain.doFilter(request, response);
        return;
    }
    
    // 继续过滤逻辑...
}
06 / Section

6. 本章总复盘

6.1 三大组件协作图

diagram
应用程序生命周期 应用启动 ServletContext初始化 Listener.contextInitialized() 初始化全局资源 等待请求... 请求到达 Filter1.doFilter()前半部分 Filter2.doFilter()前半部分 chain.doFilter() Servlet.service() Filter2.doFilter()后半部分 Filter1.doFilter()后半部分 响应返回 应用关闭 Listener.contextDestroyed() 清理资源
┌─────────────────────────────────────────────────────────┐
│                    应用程序生命周期                        │
├─────────────────────────────────────────────────────────┤
│  应用启动                                                │
│     ↓                                                   │
│  ServletContext初始化                                    │
│     ↓                                                   │
│  Listener.contextInitialized()  ← 初始化全局资源          │
│     ↓                                                   │
│  等待请求...                                              │
│     ↓                                                   │
│  请求到达 ───────────────────────────────────────┐       │
│     ↓                                            │       │
│  Filter1.doFilter()前半部分                       │       │
│     ↓                                            │       │
│  Filter2.doFilter()前半部分                       │       │
│     ↓                                            │       │
│  chain.doFilter() ───────────────────────────────┤───────┤
│     ↓                                            │       │
│  Servlet.service()                                │       │
│     ↓                                            │       │
│  Filter2.doFilter()后半部分                       │       │
│     ↓                                            │       │
│  Filter1.doFilter()后半部分 ←────────────────────┘       │
│     ↓                                                   │
│  响应返回                                                │
│     ↓                                                   │
│  应用关闭                                                │
│     ↓                                                   │
│  Listener.contextDestroyed()  ← 清理资源                 │
└─────────────────────────────────────────────────────────┘

6.2 知识点回顾

组件 核心接口 关键方法 主要用途
Listener ServletContextListener contextInitialized() contextDestroyed() 应用启动/关闭时执行初始化和清理
Filter Filter init() doFilter() destroy() 请求预处理、响应后处理

6.3 最佳实践

  1. 资源初始化:使用Listener在应用启动时初始化数据库连接池、缓存等
  2. 通用处理:使用Filter统一处理字符编码、登录验证、日志记录
  3. 顺序控制:多个Filter时使用web.xml精确控制执行顺序
  4. 异常处理:Filter中注意异常处理,避免影响正常请求
  5. 白名单机制:登录验证等场景使用白名单放行特定路径

<!-- [PRACTICE-START] 课后练习题已提取到 09-web-integration-practice.md -->