在 Java Web 开发中,过滤器 (Filter) 和 拦截器 (Interceptor) 都是常用的 AOP(面向切面编程)实现方式,但它们所属的框架层级和执行时机有很大不同。
我们可以把它们想象成进入大楼的保安(过滤器)和进入办公室前的秘书(拦截器)。
1. 过滤器 (Filter)
过滤器属于 Servlet 规范,它位于所有 Servlet 之前。它可以对几乎所有的请求(HTML、图片、JS、路径等)进行预处理。
实现方案一:通过 @WebFilter 注解(Servlet 原生)
这是最简单、最直接的方式,不需要额外的配置类。
-
实现步骤:
-
实现
jakarta.servlet.Filter接口。 -
重写
doFilter方法。 -
在类上标注
@WebFilter(urlPatterns = "/*")。 -
在启动类上加
@ServletComponentScan。
-
实现方案二:通过 FilterRegistrationBean(Spring Boot统一管理生命周期 推荐)
如果你需要更精细地控制过滤器的执行顺序(Order),或者你的过滤器是第三方库提供的,这种方案最合适。
-
实现步骤:
-
编写一个普通的类实现
Filter接口。 -
创建一个配置类(标注
@Configuration)。 -
定义一个
Bean返回FilterRegistrationBean实例。
@Bean
public FilterRegistrationBean<MyFilter> registrationBean() {
FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter()); // 设置过滤器
bean.addUrlPatterns("/api/*"); // 设置拦截路径
bean.setOrder(1); // 设置优先级,数字越小越靠前
return bean;
} -
2. 拦截器 (Interceptor)
拦截器属于 Spring MVC 框架 。它只能拦截发送到 Controller 的请求。因为它在 Spring 容器中,所以它可以直接使用 Spring 注入的对象(比如 Service)。
需要继承/实现的类
-
实现接口 :
org.springframework.web.servlet.HandlerInterceptor。 -
主要方法:
-
preHandle:在 Controller 执行前调用(常用作权限校验)。 -
postHandle:Controller 执行后,视图渲染前调用。 -
afterCompletion:整个请求结束(视图渲染后)调用,常用于清理资源。
-
注册拦截器
实现拦截器后,必须将其注册到 Spring MVC 的配置中:
-
继承
WebMvcConfigurer接口。 -
重写
addInterceptors方法。
3. 核心区别对比表
| 特性 | 过滤器 (Filter) | 拦截器 (Interceptor) |
|---|---|---|
| 所属框架 | Servlet 容器 (如 Tomcat) | Spring MVC 框架 |
| 实现接口 | jakarta.servlet.Filter |
HandlerInterceptor |
| 拦截范围 | 几乎所有请求 (URL 匹配即可) | 仅针对 Controller 的请求 |
| 注入能力 | 较难直接注入 Spring Bean | 完美支持注入 Bean |
| 执行时机 | 在 Servlet 之前执行 | 在 Servlet 之后,Controller 之前执行 |
| 主要用途 | 编码设置、敏感词过滤、全局压缩 | 权限检查、日志记录、参数预处理 |
简单总结:
-
过滤器:适合做底层的、通用的操作(如:字符编码、请求跨域、全局加密)。
-
拦截器:适合做业务相关的操作(如:检查是否登录、接口权限控制、记录操作日志)。
为什么跨域请求要通过过滤器实现?
其实跨域(CORS)既可以通过**过滤器(Filter)实现,也可以通过 拦截器(Interceptor)**或者 Spring 提供的 @CrossOrigin 注解实现。
但在实际生产中,绝大多数架构师会首选过滤器 。这主要源于跨域请求的一个特殊机制:预检请求(Preflight Request)。
1. 预检请求(OPTIONS)的拦路虎
当浏览器发现你在发起一个"复杂请求"(比如带了自定义 Header 或 Content-Type 为 application/json)时,它会先自动发送一个 OPTIONS 方法的探测请求。
-
过滤器的优势 :由于过滤器在 Servlet 容器 的最外层,它能在请求到达 Spring MVC 的
DispatcherServlet之前,就识别出这是一个 OPTIONS 请求,并直接返回允许跨域的响应头(200 OK)。 -
拦截器的劣势 :拦截器工作在 Spring MVC 内部。如果你的 OPTIONS 请求因为某些安全配置或权限校验没能通过
DispatcherServlet的映射,拦截器可能根本不会执行。这会导致预检请求失败,浏览器直接拦截掉后续的正式请求。
2. 为什么选择过滤器的三大理由
① 执行时机最早(最核心原因)
跨域配置本质上是给 HTTP 响应头加上 Access-Control-Allow-Origin 等字段。
- 过滤器在整个过滤链的最前端。如果请求跨域失败,直接在过滤器层级就"挡回去"或"处理掉"了,不会浪费后续的业务逻辑资源(如数据库连接、权限检查)。
② 解决权限框架(Security/Shiro)的冲突
如果你的项目中使用了 Spring Security,它本身就是通过一系列过滤器实现的。
-
如果你把跨域放在拦截器里,由于拦截器的执行晚于 Spring Security 的过滤器,Spring Security 会在拦截器生效前发现请求没带 Token 或不符合安全规则,直接返回 403。
-
此时浏览器因为没收到跨域响应头,会报一个"CORS error",而隐藏了真正的"403 Forbidden"错误,导致前端无法准确定位问题。
③ 覆盖范围广
过滤器是针对 URL 模式拦截的。如果你的项目里既有 Spring MVC 的 Controller,又有一些原生的 Servlet,或者静态资源,过滤器可以一把抓,统一处理所有资源的跨域问题。
3. 实现代码对比
过滤器方案(推荐)
这种方式利用 Spring 提供的 CorsFilter,通常配合 FilterRegistrationBean 使用,优先级设为最高。
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*"); // 允许所有来源
config.addAllowedHeader("*"); // 允许所有Header
config.addAllowedMethod("*"); // 允许所有方法
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 确保在最前端执行
return bean;
}
总结
跨域选择过滤器,是因为它能在业务逻辑和安全框架之前处理 OPTIONS 请求 。就像在大楼的**正门口(过滤器)贴上"欢迎光临"的告示,比等客人进了办公室(拦截器)**才说欢迎要高效且稳妥得多。