通俗易懂理解 Spring MVC 拦截器:概念、流程与简单实现(Spring系列16)

摘要:本文深入讲解Spring MVC中拦截器(Interceptor)的核心概念、工作原理、开发步骤及实战应用。从拦截器的基础定义入手,对比其与过滤器的区别,通过完整的代码示例演示拦截器的环境搭建、开发流程、参数配置与执行流程,同时解析多拦截器执行顺序与生命周期方法的使用场景,帮助开发者全面掌握Spring MVC拦截器的核心技能,适配实际项目中的业务场景开发。

关键词:Spring MVC;拦截器;Interceptor;Java Web;请求拦截


一、拦截器基础认知

1.1 拦截器概念

拦截器(Interceptor)是Spring MVC提供的动态拦截机制,用于在Controller方法执行的前后进行逻辑增强,核心作用是对请求进行预处理、后处理及完成后的收尾操作,且仅对Spring MVC的请求进行拦截(区别于过滤器对所有Web请求的拦截范围)。

请求处理流程

  1. 浏览器发送一个请求会先到Tomcat的web服务器
  2. Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
  3. 如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
  4. 如果是动态资源,就需要交给项目的后台代码进行处理
  5. 在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行
  6. 然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截
  7. 如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
  8. 如果不满足规则,则不进行处理
  9. 这个时候,如果我们需要在每个Controller方法执行的前后添加业务,具体该如何来实现?

1.2 拦截器与过滤器的核心区别

对比维度 过滤器(Filter) 拦截器(Interceptor)
归属范围 属于Servlet规范,是Web基础组件 属于Spring MVC框架,仅Spring MVC容器管理
拦截范围 拦截所有Web请求(静态/动态) 仅拦截Spring MVC的请求(Controller相关)
依赖框架 依赖Servlet容器,不依赖Spring 依赖Spring MVC框架,与Spring容器深度整合
功能场景 通用Web场景(编码处理、跨域、日志) Spring MVC业务场景(权限校验、日志记录、参数预处理)

二、拦截器开发环境搭建

2.1 项目环境准备

创建Maven Web项目,在`pom.xml`中引入核心依赖,涵盖Spring MVC、Servlet API等核心包:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itheima</groupId>
    <artifactId>springmvc_12_interceptor</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <!-- Spring MVC核心 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>
        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- Jackson(JSON处理) -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- Tomcat7插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                </configuration>
            </plugin>
            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.2 项目结构规划

复制代码
springmvc_12_interceptor
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── itheima
│   │   │           ├── config       // 配置类
│   │   │           │   ├── ServletContainersInitConfig.java
│   │   │           │   └── SpringMvcConfig.java
│   │   │           ├── controller   // 控制器
│   │   │           │   └── BookController.java
│   │   │           ├── interceptor  // 拦截器
│   │   │           │   └── ProjectInterceptor.java
│   │   │           └── domain     // 实体类
│   │   │               └── Book.java
│   │   └── webapp
│   │       └── WEB-INF
│   └── pom.xml
└── target

2.3 核心类开发

(1)实体类Book

复制代码
public class Book {
    private String name;
    private double price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

(2)控制器BookController

复制代码
@RestController
@RequestMapping("/books")
public class BookController {

    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

    @PutMapping
    public String update(@RequestBody Book book){
        System.out.println("book update..." + book);
        return "{'module':'book update'}";
    }

    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..." + id);
        return "{'module':'book getById'}";
    }

    @GetMapping
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}

(3)创建对应的配置类

复制代码
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //乱码处理
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
public class SpringMvcConfig{
}

三、拦截器核心开发步骤

3.1 自定义拦截器

实现Spring MVC的`HandlerInterceptor`接口,重写核心方法(`preHandle`、`postHandle`、`afterCompletion`):

复制代码
@Component
public class ProjectInterceptor implements HandlerInterceptor {

    /**
     * 预处理:Controller方法执行前调用
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 处理器对象(封装了目标方法信息)
     * @return true-放行,false-拦截
     * @throws Exception 异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        // 放行:返回true;拦截:返回false
        return true;
    }

    /**
     * 后处理:Controller方法执行后,视图渲染前调用
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 处理器对象
     * @param modelAndView 视图模型对象(可修改视图/数据)
     * @throws Exception 异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    /**
     * 完成后处理:视图渲染完成后调用(仅preHandle返回true时执行)
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 处理器对象
     * @param ex 异常对象(若请求执行无异常则为null)
     * @throws Exception 异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

3.2 注册拦截器

通过WebMvcConfigurationSupport配置类(推荐)

复制代码
@Configuration
public class SpringMvcSupport implements WebMvcConfigurationSupport {

    @Autowired
    private ProjectInterceptor projectInterceptor;

    /**
     * 注册拦截器
     * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(projectInterceptor)
                .addPathPatterns("/books/**"); // 拦截路径:匹配/books下所有请求
                // .excludePathPatterns("/books/login"); // 排除路径:不拦截/books/login
    }
}

3.3 SpringMVC添加SpringMvcSupport包扫描

复制代码
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig{
}

3.4 测试拦截器执行流程

启动Tomcat服务器,通过PostMan访问`http://localhost:8080/books\`,控制台输出顺序为:

复制代码
preHandle...
book getAll...
postHandle...
afterCompletion...

可见执行顺序为:preHandle(预处理)→ Controller方法 → postHandle(后处理)→ afterCompletion(完成后)

若`preHandle`返回`false`,则直接拦截请求,Controller方法、postHandle均不会执行,仅afterCompletion(若preHandle放行过则执行)或直接结束。

补充

如果发送 http://localhost/books/100 会发现拦截器没有被执行,原因是拦截器的 addPathPatterns 方法配置的拦截路径是 /books ,我们现在发送的是 /books/100 ,所以没有匹配上,因此没有拦截,拦截器就不会执行。

(1)修改拦截器拦截规则
复制代码
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );
    }
}

这个时候,如果再次访问 http://localhost/books/100 ,拦截器就会被执行。

最后说一件事,就是拦截器中的 preHandler 方法,如果返回true,则代表放行,会执行原始Controller类中要求的方法,如果返回false,则代表拦截,后面的就不会再执行了。

步骤6:简化SpringMvcSupport的编写,放在SpringMvcConfig类里
复制代码
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性(也就是这个程序已经和spring强绑定了,即这个类和spring的api关联在一起了)
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/**");
    }
}

此后咱们就不用再写 SpringMvcSupport 类了。


四、拦截器核心方法详解

4.1 preHandle(预处理)

  • 执行时机 :Controller方法执行
  • 返回值:`boolean`类型,`true`放行,`false`拦截。
  • 核心参数
    • `request`/`response`:请求/响应对象,可获取请求参数、设置响应头。
    • `handler`:处理器对象,可通过`handler.getClass()`获取目标类信息,`handler.getMethod()`获取目标方法信息。
  • 典型场景:权限校验(判断用户是否登录)、请求参数预处理、日志记录(记录请求开始时间)。

4.2 postHandle(后处理)

  • 执行时机 :Controller方法执行 ,视图渲染
  • 核心参数:多了`ModelAndView`对象,可修改视图名称、添加/修改视图数据。
  • 核心作用
    • 前后端分离场景(返回JSON):`ModelAndView`为`null`,该方法几乎无用;
    • 传统JSP/Thymeleaf场景:可修改视图数据、调整视图跳转路径。
  • 典型场景:数据脱敏、视图数据统一补充、异常统一处理(需结合异常处理器)。

4.3 afterCompletion(完成后)

  • 执行时机视图渲染完成后执行,仅`preHandle`返回`true`时才会执行。
  • 核心参数:多了`Exception`对象,可获取请求执行过程中的异常信息。
  • 典型场景:资源释放(如关闭数据库连接)、异常日志记录、统计请求执行总时长。

五、多拦截器执行流程

实际开发中可能存在多个拦截器,其执行顺序由注册顺序决定,核心规则如下:

5.1 执行顺序规则

拦截器配置顺序 preHandle执行 postHandle执行 afterCompletion执行
拦截器1 → 拦截器2 → 拦截器3 按注册顺序执行 按注册逆序执行 按注册逆序执行

5.2 多拦截器返回false的特殊流程

假设3个拦截器执行顺序为`pre1→pre2→pre3→handle→post3→post2→post1→after3→after2→after1`,不同返回值的执行逻辑:

  1. pre3返回false:执行`pre1→pre2→pre3`后拦截,执行`after2→after1`(仅已放行拦截器的afterCompletion);
  2. pre2返回false:执行`pre1→pre2`后拦截,执行`after1`;
  3. pre1返回false:仅执行`pre1`,直接拦截,后续所有方法均不执行。

5.3 多拦截器开发示例

新增第二个拦截器`ProjectInterceptor2`:

复制代码
@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle2...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle2...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion2...");
    }
}

在`SpringMvcConfig`中注册两个拦截器:

复制代码
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 先注册interceptor1,再注册interceptor2
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
		registry.addInterceptor(projectInterceptor2).addPathPatterns("/books/**");
    }
}

访问`/books`后,控制台输出顺序为:

复制代码
preHandle...
preHandle2...
book getAll...
postHandle2...
postHandle...
afterCompletion2...
afterCompletion...

六、拦截器实战应用场景

6.1 权限校验拦截器

复制代码
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求头中的token
        String token = request.getHeader("token");
        if (token == null || !token.equals("valid_token")) {
            // 未授权,返回401
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Unauthorized Access");
            return false; // 拦截
        }
        return true; // 放行
    }
}

6.2 日志记录拦截器

复制代码
@Component
public class LogInterceptor implements HandlerInterceptor {
    private long startTime;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 记录请求开始时间
        startTime = System.currentTimeMillis();
        System.out.println("请求路径:" + request.getRequestURI() + ",开始执行");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 记录请求耗时
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("请求路径:" + request.getRequestURI() + ",执行耗时:" + cost + "ms");
    }
}

七、总结与注意事项

  1. 核心执行流程:`preHandle`(放行/拦截)→ Controller → `postHandle`(视图渲染前)→ `afterCompletion`(视图渲染后);
  2. 返回值控制:`preHandle`返回`false`则直接终止请求,需谨慎处理;
  3. 作用范围:仅拦截Spring MVC请求,静态资源请求不会经过拦截器;
  4. 多拦截器顺序:`preHandle`按注册顺序执行,`postHandle`/`afterCompletion`按逆序执行;
  5. 场景适配:前后端分离项目中`postHandle`使用频率较低(`ModelAndView`为`null`)
相关推荐
zhanghongbin012 小时前
AI 采集器:Claude Code、OpenAI、LiteLLM 监控
java·前端·人工智能
计算机毕设vx_bysj68692 小时前
【免费领源码】77196基于java的手机银行app管理系统的设计与实现 计算机毕业设计项目推荐上万套实战教程JAVA,node.js,C++、python、大屏数据可视化
java·mysql·智能手机·课程设计
忘梓.2 小时前
墨色规则与血色节点:C++红黑树设计与实现探秘
java·开发语言·c++
hhh3u3u3u2 小时前
Visual C++ 6.0中文版安装包下载教程及win11安装教程
java·c语言·开发语言·c++·python·c#·vc-1
星河耀银海2 小时前
C++ 模板进阶:特化、萃取与可变参数模板
java·开发语言·c++
格鸰爱童话2 小时前
向AI学习项目技能(五)
java·学习
程序员萌萌2 小时前
Java之mysql实战讲解(三):聚簇索引与非聚簇索引
java·mysql·聚簇索引
好家伙VCC2 小时前
**发散创新:基于Python与ROS的机器人运动控制实战解析**在现代机器人系统开发中,**运动控制**是实现智能行为的核心
java·开发语言·python·机器人
程途知微3 小时前
ConcurrentHashMap线程安全实现原理全解析
java·后端