Appearance
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方法是必须实现的,有初始化操作或销毁操作的,可以重写init和destroy方法。- 在
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容器管理,包括以下阶段:
- 初始化阶段:Web容器启动时调用
init(FilterConfig)方法初始化过滤器 - 过滤阶段:每次请求经过过滤器时调用
doFilter(ServletRequest, ServletResponse, FilterChain)方法 - 销毁阶段:Web容器关闭时调用
destroy()方法销毁过滤器
15.3 过滤器的过滤范围
过滤器(Filter)的 “过滤范围” 指的是 哪些请求会被过滤器拦截或处理。过滤器的覆盖范围非常灵活,可以通过多种方式配置,包括 URL 路径、Servlet、资源类型、Dispatcher 类型等。
过滤范围由以下几部分决定:
- URL 匹配规则(最常见)
- 是否匹配静态资源
- 是否匹配特定 Servlet
- DispatcherType
- 过滤器在容器中的执行位置
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>内容未完......