文章目录
- 前言
- 一、核心功能
- 二、实现方式
-
- [2.1 @WebFilter + @ServletComponentScan](#2.1 @WebFilter + @ServletComponentScan)
- [2.2 @Component修饰自定义过滤器类](#2.2 @Component修饰自定义过滤器类)
- 三、过滤器执行的顺序
-
- [3.1 现象讨论](#3.1 现象讨论)
- [3.2 自定义顺序](#3.2 自定义顺序)
前言
过滤器是Java EE标准中Servlet规范提供的功能,也是传统JavaWeb的三大组件之一(Servlet,Filter和Listener)。通过过滤器可以把所有进入Servlet容器的请求都拦截住,无论静态资源还是Controller,从而实现一些通用的操作。
提示:以下是本篇文章正文内容,下面案例可供参考
一、核心功能
自定义过滤器的核心能力来自Filter这个接口。这个接口分别有init(),doFilter()和destroy()方法。init和destroy的生命周期依托于Servlet容器,容器初始化的时候会执行init()方法,容器销毁的时候会执行destroy()方法。
Filter 接口
java
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
其中最常用的是doFilter方法,在每一次请求管道中,都会执行我们重写的这个方法。doFilter有一核心的FilterChain参数,它的核心作用是将请求传递给下一个 Filter或最终的控制器。缺少这个调用时,请求会卡在当前 Filter,后续所有处理环节都不会执行。
比如下面的doFilter方法中包含了对filterChain的调用,这样请求才会传递到后面,否则就会中断。
java
filterChain.doFilter(servletRequest, servletResponse);
另外doFilter方法中还有ServletRequest和ServletResponse这两个参数,这两个分别对应请求和响应。我们可以通过这两个参数来查看或设置请求/响应
二、实现方式
过滤器本身是Servlet规范提供的功能,在Spring Boot中实现一个过滤器主要通过两种方法,接下来通过两种方式实现一个字符格式化过滤器和日志打印过滤器。
以下两种写法其中本质上还是Spring Boot默认使用FilterRegistrationBean来注册自定义Filter到容器中,两者仅仅是写法上不同
2.1 @WebFilter + @ServletComponentScan
首先我们定义一个CharsetFilter类来实现Filter类,Filter功能由jakarta.servlet这个包提供提供。
注意在自定义类上要使用@WebFilter()注解修饰,里面的参数则是URI路径匹配通配符,这里设置为"/*",表示匹配所有。
CharsetFilter类主要是拦截住请求,该Filter接口里最为核心的方法是doFilter。我们在doFilter的重载里写上自定义的逻辑,比如这里是将客户端到服务器和服务器到客户端的数据流都设置为UTF-8的编码格式。
值得注意的是@WebFilter需要和@ServletComponentScan搭配使用。需要在启动类上通过@ServletComponentScan来控制Spring Boot启动的时候扫描到这个被@WebFilter修饰的自定义过滤器。
通过@WebFilter + @ServletComponentScan这种实现本质上是Spring引导 Servlet 容器扫描@WebFilter最终由Servlet 容器直接调用ServletContext.addFilter()完成过滤器的注册。
java
package org.araby.blognovelink.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j
@WebFilter("/*")
public class CharsetFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("初始化统一字符编码过滤器");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("统一字符编码过滤器开始执行");
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("销毁统一字符编码过滤器");
Filter.super.destroy();
}
}
java
@SpringBootApplication
@ServletComponentScan
public class BlogNovelinkApplication {
public static void main(String[] args) {
SpringApplication.run(BlogNovelinkApplication.class, args);
}
}
执行结果里包含

2.2 @Component修饰自定义过滤器类
第二种通过在自定义过滤器类添加@Component会更加的简单,这样就不需要在启动类上添加@ServletComponentScan。这种方式基于Spring的IOC容器,直接将过滤器作为Bean对象注册到容器中,最后在请求管道里注入自定义过滤器。
但仅用@Component注解标记Filter类时,Spring Boot 会自动注册该过滤器,默认拦截规则为所有请求。如果要手动控制匹配顺序,需要手动实现一个配置类来实现FilterRegistrationBean的注册,这里后面再详细介绍。
这里我们定义一个自定义的日志过滤器,这次只有在类上加一个@Component注解。其底层还是依赖 Spring的FilterRegistrationBean完成注册。
java
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j
@Component
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("初始化日志过滤器");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("日志过滤器开始执行");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String uri = request.getRequestURI();
String method = request.getMethod();
String ip = request.getRemoteAddr();
long startTime = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
// 记录响应耗时
long costTime = System.currentTimeMillis() - startTime;
log.info("Response - URL: {}, Cost Time: {}ms", uri, costTime);
}
@Override
public void destroy() {
log.info("销毁日志过滤器");
Filter.super.destroy();
}
}
三、过滤器执行的顺序
3.1 现象讨论
假想一下有三个过滤器在请求管道里,分别是过滤器a,过滤器b和过滤器c。它们都是通过@WebFilter + @ServletComponentScan的方式初始化。
观察执行结果发现了一个神奇的现象,初始化和销毁顺序 不等于执行顺序。初始化/销毁顺序是A-C-B,而 执行顺序是A-B-C。这是因为@WebFilter + @ServletComponentScan默认情况下自定义过滤器其初始化顺序由Servlet容器加载Servlet组件的规则决定,而执行顺序由Servlet容器构建FilterChain时的排序规则,像Tomcat默认会对所有@WebFilter按类名排序。
执行结果
bash
[INFO ] [2025-12-02 22:14:18.365] [main] [] o.araby.blognovelink.filter.AFilter - 初始化A过滤器
[INFO ] [2025-12-02 22:14:18.365] [main] [] o.araby.blognovelink.filter.CFilter - 初始化C过滤器
[INFO ] [2025-12-02 22:14:18.365] [main] [] o.araby.blognovelink.filter.BFilter - 初始化B过滤器
[INFO ] [2025-12-02 22:14:36.764] [http-nio-9090-exec-2] [] o.araby.blognovelink.filter.AFilter - A过滤器开始执行
[INFO ] [2025-12-02 22:14:36.765] [http-nio-9090-exec-2] [] o.araby.blognovelink.filter.BFilter - B过滤器开始执行
[INFO ] [2025-12-02 22:14:36.765] [http-nio-9090-exec-2] [] o.araby.blognovelink.filter.CFilter - C过滤器开始执行
[INFO ] [2025-12-02 22:14:42.262] [SpringApplicationShutdownHook] [] o.araby.blognovelink.filter.AFilter - 销毁A过滤器
[INFO ] [2025-12-02 22:14:42.262] [SpringApplicationShutdownHook] [] o.araby.blognovelink.filter.CFilter - 销毁C过滤器
[INFO ] [2025-12-02 22:14:42.262] [SpringApplicationShutdownHook] [] o.araby.blognovelink.filter.BFilter - 销毁B过滤器
3.2 自定义顺序
既然都已经是在Spring Boot中使用过滤器,那么我们就应该用Spring的方式管理Filter,而不是依赖 Servlet容器的隐式行为。这里其实是采用FilterRegistrationBean来手动定义注册的顺序,所以过滤器类可以不用@WebFilter + @ServletComponentScan或者@Component修饰。启动类上也不需要@ServletComponentScan。
这样注册过滤器的的过程由Spring Boot通过FilterRegistrationBean统一管理,而非直接依赖Servlet 容器的注解扫描。
这里我们定义一个配置类WebConfig ,用@Configuration注解修饰。里面手动实现FilterRegistrationBean来管理定义的过滤器。
- setFilter:添加过滤器类
- addUrlPatterns:添加匹配路径
- setOrder:指定顺序,数值越小,过滤器doFilter越先执行
java
import org.araby.blognovelink.filter.AFilter;
import org.araby.blognovelink.filter.BFilter;
import org.araby.blognovelink.filter.CFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<AFilter> aFilter() {
FilterRegistrationBean<AFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new AFilter());
bean.addUrlPatterns("/*");
bean.setOrder(1); // 最先执行
return bean;
}
@Bean
public FilterRegistrationBean<BFilter> bFilter() {
FilterRegistrationBean<BFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new BFilter());
bean.addUrlPatterns("/*");
bean.setOrder(2);
return bean;
}
@Bean
public FilterRegistrationBean<CFilter> cFilter() {
FilterRegistrationBean<CFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new CFilter());
bean.addUrlPatterns("/*");
bean.setOrder(3); // 最后执行
return bean;
}
}