摘要:本文深入讲解Spring MVC中拦截器(Interceptor)的核心概念、工作原理、开发步骤及实战应用。从拦截器的基础定义入手,对比其与过滤器的区别,通过完整的代码示例演示拦截器的环境搭建、开发流程、参数配置与执行流程,同时解析多拦截器执行顺序与生命周期方法的使用场景,帮助开发者全面掌握Spring MVC拦截器的核心技能,适配实际项目中的业务场景开发。
关键词:Spring MVC;拦截器;Interceptor;Java Web;请求拦截
一、拦截器基础认知
1.1 拦截器概念
拦截器(Interceptor)是Spring MVC提供的动态拦截机制,用于在Controller方法执行的前后进行逻辑增强,核心作用是对请求进行预处理、后处理及完成后的收尾操作,且仅对Spring MVC的请求进行拦截(区别于过滤器对所有Web请求的拦截范围)。
请求处理流程
- 浏览器发送一个请求会先到Tomcat的web服务器
- Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
- 如果是静态资源,会直接到Tomcat的项目部署目录下去直接访问
- 如果是动态资源,就需要交给项目的后台代码进行处理
- 在找到具体的方法之前,我们可以去配置过滤器(可以配置多个),按照顺序进行执行
- 然后进入到到中央处理器(SpringMVC中的内容),SpringMVC会根据配置的规则进行拦截
- 如果满足规则,则进行处理,找到其对应的controller类中的方法进行执行,完成后返回结果
- 如果不满足规则,则不进行处理
- 这个时候,如果我们需要在每个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`,不同返回值的执行逻辑:
- pre3返回false:执行`pre1→pre2→pre3`后拦截,执行`after2→after1`(仅已放行拦截器的afterCompletion);
- pre2返回false:执行`pre1→pre2`后拦截,执行`after1`;
- 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");
}
}
七、总结与注意事项
- 核心执行流程:`preHandle`(放行/拦截)→ Controller → `postHandle`(视图渲染前)→ `afterCompletion`(视图渲染后);
- 返回值控制:`preHandle`返回`false`则直接终止请求,需谨慎处理;
- 作用范围:仅拦截Spring MVC请求,静态资源请求不会经过拦截器;
- 多拦截器顺序:`preHandle`按注册顺序执行,`postHandle`/`afterCompletion`按逆序执行;
- 场景适配:前后端分离项目中`postHandle`使用频率较低(`ModelAndView`为`null`)