乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

HandlerInterceptor

本章聚焦 Spring MVC 的请求拦截层。DispatcherServlet 把请求分发给 Controller 之前,以及 Controller 处理完毕之后,往往需要插入一些横切逻辑——如登录检查、权限校验、日志记录、性能监控等。HandlerInterceptor 正是为此而生的标准扩展接口。

本章与全局的关系:前面章节讲解了请求如何被映射到 Controller、参数如何被解析、返回值如何被序列化。本章讲解请求在 Controller 前后如何被拦截器处理。后续章节将讲解如何通过 WebMvcConfigurer 注册拦截器,以及跨域处理。


定义与作用

HandlerInterceptor 是 Spring MVC 提供的处理器拦截器接口,允许开发者在请求处理链路的三个关键节点插入自定义逻辑:

  1. preHandle:Controller 方法执行之前调用。常用于登录检查、权限校验、防重复提交等。返回 true 放行,返回 false 中断请求。
  2. postHandle:Controller 方法执行之后、视图渲染之前调用。常用于修改模型数据、统一包装响应等。
  3. afterCompletion:整个请求处理完成之后(包括视图渲染)调用。常用于资源清理、异常日志记录、性能统计等。

生活类比:公司大楼的安保系统

想象飞翔科技的办公大楼:

  • preHandle(门禁刷卡):你进大楼前,保安检查你的工牌。没工牌?直接拦在门外(返回 false)。有工牌?放行(返回 true)。
  • postHandle(电梯间指引):你办完事后准备离开,电梯间广播提示"请带好随身物品"——此时你的事务已办完,但还没真正出门。
  • afterCompletion(出门登记):你真正走出大楼后,门卫在登记簿上记录"某某于几点离开"——无论你在楼里有没有吵架(抛异常),出门登记一定会做。

关键认知:拦截器只拦截被 DispatcherServlet 处理的请求,不拦截静态资源(除非特别配置)。


核心原理

拦截器执行顺序时序图

当多个拦截器同时存在时,Spring MVC 按照注册顺序组织成拦截器链,执行顺序遵循"先进后出"原则:

执行顺序规律:

阶段执行顺序记忆口诀
preHandleI1 → I2 → Controller正序进入
postHandleController → I2 → I1逆序离开
afterCompletionI2 → I1逆序收尾

preHandle 返回 false 时的流程中断

当某个拦截器的 preHandle 返回 false 时,请求处理链立即中断:

重要细节:

  • I2 的 preHandle 返回 false 后,I2 的 postHandle 和 afterCompletion 不会执行
  • I1 的 afterCompletion 仍然会执行(因为它已经放行了)
  • 如果 I1 的 preHandle 就返回 false,没有任何 afterCompletion 会执行

适用位置与常用属性

HandlerInterceptor 是一个接口,开发者通过实现该接口来创建拦截器:

方法触发时机返回值典型用途
preHandle(HttpServletRequest, HttpServletResponse, Object handler)Controller 方法执行前boolean登录检查、权限校验、IP 黑名单
postHandle(HttpServletRequest, HttpServletResponse, Object handler, ModelAndView modelAndView)Controller 方法执行后,视图渲染前void统一添加模型数据、修改视图名
afterCompletion(HttpServletRequest, HttpServletResponse, Object handler, Exception ex)请求完全结束后void日志记录、资源释放、性能统计

便捷基类:HandlerInterceptorAdapter

Spring 提供了 HandlerInterceptorAdapter 抽象类,实现了 HandlerInterceptor 接口的所有方法(默认空实现)。开发者只需重写需要的方法:

public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 只关心 preHandle,其他方法继承空实现
        return request.getSession().getAttribute("user") != null;
    }
}

Spring 5.3+ 更新:HandlerInterceptorAdapter 已被标记为 @Deprecated,推荐直接实现 HandlerInterceptor 接口(Java 8 默认方法支持)。


完整示例

场景

飞翔科技员工管理系统需要三层防护:

  1. 登录检查:未登录用户无法访问任何业务接口
  2. 权限校验:只有管理员(admin 角色)能访问 /admin/** 路径
  3. 操作日志:记录所有请求的耗时和结果

项目结构

employee-web/
├── src/main/java/
│   └── com/feixiang/web/
│       ├── controller/
│       │   └── AdminController.java
│       └── interceptor/
│           ├── LoginInterceptor.java
│           ├── AdminAuthInterceptor.java
│           └── LogInterceptor.java

拦截器实现

// LoginInterceptor.java
package com.feixiang.web.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        if (session.getAttribute("user") == null) {
            response.setStatus(401);
            response.getWriter().write("{\"error\":\"未登录,请先登录\"}");
            return false; // 拦截
        }
        return true; // 放行
    }
}
// AdminAuthInterceptor.java
package com.feixiang.web.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class AdminAuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        String role = (String) session.getAttribute("role");
        if (!"admin".equals(role)) {
            response.setStatus(403);
            response.getWriter().write("{\"error\":\"权限不足,需要管理员角色\"}");
            return false;
        }
        return true;
    }
}
// LogInterceptor.java
package com.feixiang.web.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LogInterceptor implements HandlerInterceptor {

    private static final String START_TIME_KEY = "startTime";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        request.setAttribute(START_TIME_KEY, System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 可在此修改模型数据
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        Long startTime = (Long) request.getAttribute(START_TIME_KEY);
        long duration = System.currentTimeMillis() - startTime;
        String uri = request.getRequestURI();
        System.out.println("[日志] URI=" + uri + ", 耗时=" + duration + "ms, 异常=" + ex);
    }
}

Controller

// AdminController.java
package com.feixiang.web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/users")
    public String listUsers() {
        return "管理员用户列表";
    }
}

HTTP 请求示例 1:未登录访问被拦截

$ curl -X GET http://localhost:8080/admin/users

响应:

{"error":"未登录,请先登录"}

状态码:401 Unauthorized

流程解析:

  1. 请求到达 DispatcherServlet
  2. LoginInterceptor.preHandle 检查 session,发现无 user 属性
  3. 返回 false,写入 401 响应体
  4. 请求中断,AdminAuthInterceptor 和 AdminController 都不执行
  5. LoginInterceptor.afterCompletion 执行(记录日志)

HTTP 请求示例 2:已登录但无权限

先模拟登录(设置 session):

$ curl -X POST http://localhost:8080/login \
  -c cookies.txt \
  -d "username=xiaocui&password=123456"

再用普通用户访问管理员接口:

$ curl -X GET http://localhost:8080/admin/users \
  -b cookies.txt

响应:

{"error":"权限不足,需要管理员角色"}

状态码:403 Forbidden

流程解析:

  1. LoginInterceptor.preHandle 返回 true(已登录)
  2. AdminAuthInterceptor.preHandle 检查 role,发现不是 admin
  3. 返回 false,写入 403 响应体
  4. 请求中断,AdminController 不执行
  5. LoginInterceptor.afterCompletion 执行,LogInterceptor 的 afterCompletion 不执行(因为 LogInterceptor 的 preHandle 虽然返回 true,但后续拦截器返回 false 时,已执行 preHandle 的拦截器仍会执行 afterCompletion)

HTTP 请求示例 3:管理员正常访问

$ curl -X GET http://localhost:8080/admin/users \
  -b cookies.txt

响应:

管理员用户列表

控制台输出:

[日志] URI=/admin/users, 耗时=23ms, 异常=null

易错场景与面试考点

误区一:拦截器可以拦截静态资源

错误认知:"我配置了 /** 拦截所有请求,所以 /static/logo.png 也会被拦截。"

纠正:默认情况下,Spring MVC 的拦截器不拦截静态资源。静态资源由 ResourceHttpRequestHandler 处理,不走 HandlerExecutionChain 的拦截器逻辑。如果需要拦截静态资源,需要特殊配置(如把静态资源也映射到 Controller,或使用 Filter)。

误区二:preHandle 返回 false 后什么都不执行

错误认知:"某个拦截器返回 false,所有后续逻辑都不执行。"

纠正:已执行过 preHandle 且返回 true 的拦截器,其 afterCompletion 仍然会执行。这是 Spring 设计的资源清理保障机制。只有当前返回 false 的那个拦截器,以及它之后的所有拦截器,不会执行任何后续方法。

误区三:postHandle 能修改 @ResponseBody 的返回值

错误认知:"我在 postHandle 里修改 modelAndView,就能改变 JSON 响应内容。"

纠正:对于 @ResponseBody / @RestController 的响应,消息转换器在 Controller 方法返回后立即写入响应流,postHandle 执行时响应已经提交。此时修改 ModelAndView 对 JSON 响应无效。如果需要统一包装 REST 响应,应使用 ResponseBodyAdvice(本教程不展开)。

面试高频:Filter 和 HandlerInterceptor 的区别

对比维度Filter(Servlet 规范)HandlerInterceptor(Spring MVC)
所属规范Servlet APISpring MVC
执行时机Servlet 容器层面,DispatcherServlet 之前Spring MVC 框架层面,DispatcherServlet 内部
能否获取 Controller 信息不能能(通过 handler 参数)
能否获取 Spring 容器不能直接获取能(通过 ApplicationContext)
配置方式web.xml / @WebFilterWebMvcConfigurer
拦截范围所有请求(包括静态资源)仅 DispatcherServlet 处理的请求

标准回答:Filter 是 Servlet 容器层面的通用过滤器,HandlerInterceptor 是 Spring MVC 框架层面的专用拦截器。Filter 在 DispatcherServlet 之前执行,HandlerInterceptor 在 DispatcherServlet 内部执行,因此 HandlerInterceptor 能获取到 Spring 容器和 Controller 的上下文信息。


小结

HandlerInterceptor 是 Spring MVC 的请求拦截接口,通过 preHandle、postHandle、afterCompletion 三个回调,在 Controller 前后插入横切逻辑。preHandle 返回 true 放行、false 拦截,是登录检查和权限校验的核心机制。多个拦截器组成拦截器链,执行顺序遵循"正序进入、逆序离开"的规律。

本章与全局的关系:本章讲解了如何编写拦截器逻辑。下一章"WebMvcConfigurer"将讲解如何把拦截器注册到 Spring MVC 中,以及如何配置拦截路径和排除路径。

上一页
本章导读:拦截器、过滤器与跨域
下一页
WebMvcConfigurer