Skip to content

SpringMVC教程 - 15 过滤器

过滤器(Filter)是 Servlet 规范中的组件,由 Web 容器(如 Tomcat)在请求到达 Servlet 之前、响应离开容器之后执行,它本质上不属于 SpringMVC 体系。

通过下面的图,可以看到过滤器和拦截器的位置区别:

请求到达服务器,先会由 Filter 拦截处理,然后交由 Servlet 处理,然后到达拦截器,经过层层拦截器,才执行 controller 中的方法,在 Controller 执行之后,在视图渲染之前,拦截器(postHandle()方法)可以执行一些后处理操作,在请求处理完毕之后(包括视图渲染),拦截器(afterCompletion()方法)还可以执行一些清理工作,最终又回到了过滤器的调用。

所以拦截器和过滤器主要有如下区别:

  • 过滤器是 Servlet 层的组件,拦截器是 Spring MVC 层的组件。
  • 过滤器可以处理任何请求(包括静态资源),而拦截器只能拦截Controller方法的调用。
  • 过滤器在请求到达 Servlet 之前和响应返回客户端之前执行,拦截器在控制器方法调用之前、之后以及请求处理完成后执行。
  • 过滤器只能对 request 和 response 进行操作,而拦截器可以对 request、response、handler、modelAndView、exception进行操作。

在没有 MVC 框架的年代,过滤器经常被用来完成“请求预处理”和“响应后处理”的工作,包括:

  • 统一字符编码(如 CharacterEncodingFilter
  • 请求日志记录
  • 认证逻辑
  • 跨域(CORS)处理
  • 请求/响应包装

从功能上讲,过滤器和拦截器是有一些相似的。现在有了 SpringMVC 后,一般与业务相关的检查(如登录状态、权限验证)更适合使用拦截器,而过滤器则用于处理与业务无关的全局性工作,例如统一编码、日志记录、跨域处理(CORS)等。

15.1 过滤器的使用

以前 Tomcat 请求和响应的字符编码不是 UTF-8 的时候,请求和响应就会出现乱码,所以一般使用过滤器来设置请求和响应的字符编码。

下面我们就实现一个过滤器,设置请求头和响应头的字符编码。

1 创建过滤器

要创建一个过滤器,我们需要实现 jakarta.servlet.Filter 接口:

java
package com.foooor.hellospringmvc.filter;

import jakarta.servlet.*;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;

@Slf4j
public class CharacterEncodingFilter implements Filter {

    private String encoding;

    /**
     * 过滤器初始化方法
     * 有需要可以重写
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 从配置中获取编码,如果没有设置则使用UTF-8
        encoding = filterConfig.getInitParameter("encoding");
        log.info("CharacterEncodingFilter 初始化,编码参数为:{}", encoding);

        if (encoding == null) {
            encoding = "UTF-8";
        }
    }

    /**
     * 过滤器核心方法,用于处理请求和响应
     * 必须实现的方法
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 设置请求编码
        request.setCharacterEncoding(encoding);
        // 设置响应编码
        response.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset=" + encoding);

        // 调用过滤器链中的下一个过滤器
        chain.doFilter(request, response);

        // 请求处理完成后,可以在这里处理响应
        log.info("CharacterEncodingFilter 处理响应完成");
    }

    /**
     * 过滤器销毁方法
     * 可以重写
     */
    @Override
    public void destroy() {
        log.info("CharacterEncodingFilter 被销毁");
        // 释放资源
        encoding = null;
    }
}
  • doFilter 方法是必须实现的,有初始化操作或销毁操作的,可以重写 initdestroy 方法。
  • doFilter 方法中, chain.doFilter(request, response); 表示继续执行后面的流程。

2 配置过滤器

需要在 web.xml 文件中配置过滤器:

xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <!-- 配置字符编码过滤器 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>com.foooor.hellospringmvc.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <!-- 设置参数 -->
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <!-- 配置过滤器拦截所有请求 -->
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 其他配置,略... -->
    
</web-app>
  • 过滤器是可以创建和配置多个的,配置在前面的过滤器优先执行,而设置字符编码的过滤器都是放在最前面,以免已经处理了数据再设置编码,导致不生效。

3 测试

此时在请求服务器的相关接口,就可以看到过滤器的中的日志被打印了。

我们上面写的过滤器只是介绍过滤器的使用,如果要真的配置字符编码过滤器,SpringMVC 为我们提供了现成的过滤器,直接配置就可以了:

xml
<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

    <!-- 设置编码为 UTF-8 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>

    <!-- forceEncoding=true 表示强制覆盖 request 和 response 的编码 -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<!-- 匹配所有请求 -->
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

15.2 过滤器的工作原理

过滤器的工作原理基于责任链模式(Chain of Responsibility Pattern)。当一个请求到达Web容器时,容器会创建一个过滤器链(Filter Chain),然后按照配置的顺序依次调用过滤器链中的每个过滤器。

每个过滤器可以:

  • 对请求进行处理
  • 调用chain.doFilter(request, response)方法将请求传递给下一个过滤器
  • 如果是最后一个过滤器,则请求会传递给目标Servlet
  • 在请求返回时,可以对响应进行处理

过滤器的生命周期由Web容器管理,包括以下阶段:

  1. 初始化阶段:Web容器启动时调用init(FilterConfig)方法初始化过滤器
  2. 过滤阶段:每次请求经过过滤器时调用doFilter(ServletRequest, ServletResponse, FilterChain)方法
  3. 销毁阶段:Web容器关闭时调用destroy()方法销毁过滤器

15.3 过滤器的过滤范围

过滤器(Filter)的 “过滤范围” 指的是 哪些请求会被过滤器拦截或处理。过滤器的覆盖范围非常灵活,可以通过多种方式配置,包括 URL 路径、Servlet、资源类型、Dispatcher 类型等。

过滤范围由以下几部分决定:

  1. URL 匹配规则(最常见)
  2. 是否匹配静态资源
  3. 是否匹配特定 Servlet
  4. DispatcherType
  5. 过滤器在容器中的执行位置

1 URL 过滤范围(最常见)

过滤器最常见的是基于 URL 模式进行过滤,使用 <url-pattern> 指定。

常见 URL 模式:

匹配模式说明示例
/*匹配所有请求过滤所有前端访问
/api/*匹配某个目录下的所有请求/api/user/api/list
*.jsp匹配所有 .jsp 资源拦截所有 JSP 访问
/login精确匹配某个路径/login
/让当前 Servlet 处理所有“找不到其他 Servlet 的请求”/ 并不是拦截所有请求

举个栗子:(web.xml中的配置):

xml
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/api/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>
  • 一个 Filter 可以对应多个 url-pattern

2 是否过滤静态资源

过滤器的 URL 匹配是基于 Servlet 容器级别的,因此/* 会拦截静态资源(CSS/JS/PNG),不像拦截器可以自动避开静态资源。

如果想过滤器只处理动态请求,应手动排除静态资源:

java
String uri = request.getRequestURI();
if (uri.endsWith(".css") || uri.endsWith(".js") || uri.contains("/static/")) {
    chain.doFilter(request, response);
    return;
}

或者配置更精确的 URL(如 /api/*)。

3 Servlet名称映射范围

除了 URL 模式外,还可以针对某个 Servlet 进行过滤:

xml
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>

这表示只过滤 DispatcherServlet 的请求,而不会处理 JSP 或静态资源。

15.4 常用过滤器示例

1 日志记录过滤器

记录所有请求的请求路径、耗时时间、方法等信息。

java
package com.foooor.hellospringmvc.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 记录请求开始时间
        long startTime = System.currentTimeMillis();
        String requestUri = httpRequest.getRequestURI();
        String method = httpRequest.getMethod();
        String clientIp = httpRequest.getRemoteAddr();

        log.info("请求开始: [{}] {} - 客户端IP: {}", method, requestUri, clientIp);

        try {
            // 调用下一个过滤器
            chain.doFilter(request, response);
        } finally {
            // 记录请求结束时间
            long executeTime = System.currentTimeMillis() - startTime;
            int statusCode = httpResponse.getStatus();

            log.info("请求结束: [{}] {} - 状态码: {} - 执行时间: {}ms",
                    method, requestUri, statusCode, executeTime);
        }
    }
}
  • 需要在 web.xml 中配置过滤的URL。

2 跨域请求过滤器

什么是跨域?

CORS 的作用就是告诉浏览器,我可以被跨域请求。

java
package com.foooor.hellospringmvc.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CorsFilter implements Filter {

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

        // 设置允许的源
        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");
        // 设置是否允许发送Cookie
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        // 设置预检请求的缓存时间(秒)
        httpResponse.setHeader("Access-Control-Max-Age", "3600");

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 处理预检请求
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }
}

但是 SpringMVC 提供了现成的 CorsFilter,直接使用就可以了:

在 web.xml 中配置拦截所有请求就可以了:

xml
<!-- 配置 CORS 过滤器 -->
<filter>
    <filter-name>corsFilter</filter-name>
    <filter-class>com.foooor.hellospringmvc.filter.CorsFilter</filter-class>
</filter>

<!-- 配置过滤器拦截所有请求 -->
<filter-mapping>
    <filter-name>corsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
内容未完......