中间件(Interceptor / Filter)
Laravel 的中间件有「全局中间件」和「路由中间件」两层,还有
before/after时机。Spring 对应有两套机制:Filter(Servlet 层) 和 HandlerInterceptor(Spring MVC 层)。先记住区别,就不会再混淆。
Filter 与 Interceptor 的区别(最重要的认知)
| 维度 | Filter | HandlerInterceptor |
|---|---|---|
| 所属层次 | Servlet 容器层 | Spring MVC 层 |
| 能否拿到 Controller 方法信息 | 不能 | 能 |
| 作用范围 | 所有请求 | 可配置拦截路径 |
| 相当于 Laravel | 全局中间件 | 路由中间件 |
| 常见用途 | 编码、CORS、日志、Security 过滤链 | 登录校验、权限校验、性能监控 |
判断标准:需要「按 URL 配置 + 拿 Controller 信息」用 Interceptor;需要「拦所有请求、最底层」用 Filter。
最直观的理解
Filter 在 DispatcherServlet 之前跑(还没进 Spring MVC);Interceptor 在 DispatcherServlet 找到控制器之后、执行前后跑。Laravel 里所有中间件都在 Kernel 之后按路由分发,而 Spring 把「拦全局」和「拦路由」分成了两层。
HandlerInterceptor:对应路由中间件
实现拦截器
package com.example.demo.common;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isBlank()) {
response.setStatus(401);
return false; // 拦截,请求终止
}
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
// 控制器执行后、视图渲染前(类似中间件 after)
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 请求完成后(可用于记录耗时)
}
}三个方法的时机对照:
| 方法 | 时机 | 类比 Laravel |
|---|---|---|
preHandle | 进入控制器前 | 中间件 before |
postHandle | 控制器返回后、视图渲染前 | 中间件 after |
afterCompletion | 整个请求结束后 | terminate |
注册拦截器(关键:要显式注册才生效)
package com.example.demo.config;
import com.example.demo.common.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 拦这些路径
.excludePathPatterns("/api/login"); // 放行这些
}
}和 Laravel 的差异
Laravel 中间件在 Kernel 里注册、可分配给路由;Spring 拦截器注册在配置类里,按 URL 模式匹配。不能像 Laravel 那样给某个控制器方法单独挂中间件,只能按路径前缀拦(配合 @RequestMapping 的组织方式)。
Filter:对应全局中间件
实现 jakarta.servlet.Filter(注意是 3.x 的 jakarta 命名空间):
package com.example.demo.config;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("每个请求都经过这里: " + req.getRequestURI());
chain.doFilter(request, response); // 放行
}
}@Component 注册的 Filter 默认对所有 URL 生效,相当于 Laravel 里放进 $middleware 数组的全局中间件。
也可以注册到指定 URL:
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
FilterRegistrationBean<CorsFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new CorsFilter());
reg.addUrlPatterns("/api/*");
reg.setOrder(1); // 顺序
return reg;
}执行顺序
Filter 1 → Filter 2 → DispatcherServlet → Interceptor.preHandle
→ 控制器 → Interceptor.postHandle → Filter 后置逻辑- Filter 按
@Order/ 注册顺序执行 - Interceptor 按注册顺序执行,
preHandle顺序执行,postHandle逆序执行
注意
CORS 一定要用 Filter(拦截器可能被 Spring Security 拦在后面);登录/权限校验用 Interceptor。Spring Security 自己有独立的过滤器链(见 Spring Security 深入),和 Interceptor 是两条路。
典型场景选型
| 需求 | 选 Filter | 选 Interceptor |
|---|---|---|
| CORS 跨域 | ✅ | |
| 请求日志 / 耗时 | ✅ | |
| 字符编码 | ✅ | |
| 登录校验 | ✅ | |
| 权限校验(拿用户角色) | ✅ | |
| 请求参数篡改校验 | ✅ | |
| 限流 | ✅ |
拦截器里如何传递数据给控制器
Laravel 中间件可以往 $request 里塞数据;Spring 拦截器可以用 request.setAttribute:
// preHandle 里
request.setAttribute("currentUser", user);
// 控制器里
@GetMapping("/me")
public User me(@RequestAttribute("currentUser") User user) {
return user;
}TIP
@RequestAttribute 专门读取 request.setAttribute 塞进去的数据。登录信息从拦截器传到控制器,这是标准做法。
与 Laravel 中间件对比总结
| 维度 | Laravel | Spring |
|---|---|---|
| 全局拦截 | Kernel $middleware | Filter |
| 按路由拦截 | 中间件赋给路由 | Interceptor + 路径匹配 |
| 注册方式 | Kernel 数组 / 路由方法 | WebMvcConfigurer.addInterceptors |
| 放行 | return $next($request) | return true(pre)或 chain.doFilter(...) |
| 终止 | 返回响应 | return false + 手动写状态码 |
| 拿控制器信息 | 拿不到 | handler 参数里有方法对象 |
下一节,拦截器的兄弟 —— 参数校验:数据校验。