Skip to content

过滤器与拦截器 ⚠️

对应 PHP 中的 HTTP 中间件(Middleware),但 Spring Boot 把"请求前后处理"拆成了两种机制。


两种机制对比

维度FilterHandlerInterceptor
所属层级Servlet 容器级(Tomcat)Spring MVC 级
是否能处理静态资源否(只拦截 Controller 请求)
是否能修改请求/响应对象
是否知道 Controller 信息否(只看到 URL)是(可以知道调用了哪个方法)
和 Laravel Middleware 的对应接近 $next($request)更精确的控制器前后处理

HandlerInterceptor(推荐,功能更强)

定义

java
@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 类比 PHP 中间件的 handle($request, $next) 之前
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(401);
            response.getWriter().write("{\"error\":\"unauthorized\"}");
            return false;   // 终止请求,不继续
        }
        return true;        // 继续执行控制器
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        // 类比 PHP 中间件的 handle($request, $next) 之后,视图渲染之前
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        // 请求完全结束后(视图渲染完成),类比 PHP 的 terminate 中间件
    }
}

注册

java
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Autowired
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor)
                .addPathPatterns("/**");               // 拦截所有路径

        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")             // 只拦截 /api/*
                .excludePathPatterns("/api/login");     // 排除登录接口
    }
}

Filter(更低级,通常用不到)

java
@Component
@Order(1)  // 排序,数字越小越先执行
public class CorsFilter implements Filter {

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

        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setHeader("Access-Control-Allow-Origin", "*");

        chain.doFilter(request, response);  // 类比 return $next($request)
    }
}

⚠️ 和 PHP 中间件的关键差异

1. 不能像 Laravel 那样直接 return response

php
// PHP 中间件
public function handle($request, Closure $next) {
    if (!$request->has('token')) {
        return response()->json(['error' => 'unauthorized'], 401);
    }
    return $next($request);
}
java
// Java 拦截器:不能 return 响应,必须手动写入 response 对象
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {
    if (request.getHeader("Authorization") == null) {
        response.setStatus(401);
        response.getWriter().write("{\"error\":\"unauthorized\"}");
        return false;  // 返回 false 表示"拦截,不要再继续"
    }
    return true;
}

原因:Servlet 规范在设计上,Filter/Interceptor 是管道模式,没有返回值作为响应体的机制。你需要直接操作 HttpServletResponse。 这是 Java Servlet 的历史设计决定的,Spring 也无法改变。

2. 多个拦截器的顺序需要手动注册

php
// PHP:在 Kernel 中定义数组顺序
protected $middlewarePriority = [
    \App\Http\Middleware\Authenticate::class,
    \App\Http\Middleware\LogRequests::class,
];
java
// Java:通过 @Order 或 Ordered 接口控制
@Order(1) public class LogInterceptor implements HandlerInterceptor { ... }
@Order(2) public class AuthInterceptor implements HandlerInterceptor { ... }

3. 没有 after() 方法的简易语法

php
// PHP 中可以在中间件中写前后逻辑
$response = $next($request);
// 这里是 after 逻辑
return $response;
java
// Java 中 before 和 after 是分开的方法
// before → preHandle(return true 才继续)
// after  → postHandle(执行完控制器后)
// finally → afterCompletion(无论如何都会执行)

4. ⭐ 修改请求体很麻烦

在 Filter 中读了一次请求 Body 后,后面的 Controller 就读取不到了,因为 ServletInputStream 只能读一次。 需要 ContentCachingRequestWrapper 包装,这是 Java Servlet 规范的限制。

面向 PHP 开发者的 Spring Boot 文档