[toc]
单词表
| 单词 | 词性 | 发音 | 含义 |
|---|---|---|---|
| intercept | 动词 | 英/ˌɪntəˈsept/ 美/ˌɪntərˈsept/ | 拦截 |
| interceptor | 名词 | 英/ˌɪntəˈseptə(r)/ 美/ˌɪntərˈseptər/ | 拦截器 |
| handle | 动词 | 英/ˈhændl/ 美/ˈhændl/ | 处理 |
| handler | 名词 | 英/ˈhændlə(r)/ 美/ˈhændlər/ | 处理器 |
| preHandle | 动词 | /priː ˈhændl/ | 在目标Handler方法之前执行的操作 |
| postHandle | 动词 | /pəʊst ˈhændl/ | 在目标Handler方法之后执行的操作 |
| complete | 动词 | 英/kəmˈpliːt/ 美/kəmˈpliːt/ | 完成 |
| completion | 名词 | 英/kəmˈpliːʃn/ 美/kəmˈpliːʃn/ | 完成 |
| configurer | 名词 | /kənˈfɪɡərə(r)/ | 配置者、配置器 |
| configuration | 名词 | /kənˌfɪɡəˈreɪʃn/ | 配置 |
| registry | 名词 | 英/ˈredʒɪstri/ 美/ˈredʒɪstri/ | 登记处;注册处 |
| add | 动词 | 英/æd/ 美/æd/ | 添加 |
| path | 名词 | 英/pɑːθ/ 美/pæθ/ | 路径 |
| pattern | 名词 | 英/ˈpætn/ 美/ˈpætərn | 匹配模式 |
| exclude | 动词 | 英/ɪkˈskluːd/ 美/ɪkˈskluːd/ | 排除 |
Lecture09-特定功能-实验01-拦截器
1、概念
①拦截器要解决的问题
[1]生活中坐地铁的场景·
为了提高乘车效率,在乘客进入站台前统一检票:

[2]程序中
在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测。

②拦截器三要素
- 拦截:必须先把请求拦住,才能执行后续操作
- 过滤:拦截器存在的意义就是对请求进行统一处理,从而实现业务上的需求,需要自己写代码
- 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
2、用法:单个拦截器
①创建拦截器类

package com.atguigu.spring.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 拦截器执行的总体顺序(单个拦截器)第一种情况(目标Controller方法加了@ResponseBody):
* preHandle() 方法
* 目标 Controller 方法
* SpringMVC 底层调用 HttpMessageConverter 方法把目标 Controller 方法返回值转为 JSON,再写入响应流
* postHandle() 方法
* afterCompletion() 方法
*
* 拦截器执行的总体顺序(单个拦截器)第二种情况(目标Controller方法没加@ResponseBody):
* preHandle() 方法
* 目标 Controller 方法
* postHandle() 方法
* 根据目标 Controller 方法返回的视图名称渲染视图
* afterCompletion() 方法
*/
@Component
public class Demo01Interceptor implements HandlerInterceptor {
// preHandle() 方法在目标 Controller 方法之前执行
// 参数一:原生 request 对象
// 参数二:原生 response 对象
// 参数三:目标 Controller 对象
// 返回值:返回 true 放行;返回 false 不放行,原本要执行的后续代码都不执行了,相当于没有给前端返回响应
// 所以如果 return false,我们一定要编写代码,自己返回响应
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Demo01Interceptor preHandle() 方法执行了");
return true;
}
// preHandle() 放行之后,后续执行顺序如下:
// 1、目标 Controller 方法
// 2、【★有 @ResponseBody】HttpMessageConverter 负责把目标 Controller 方法返回值转换为 JSON 写入响应流
// 3、postHandle() 方法执行
// 4、【★无 @ResponseBody】针对目标 Controller 方法返回的视图名称,渲染视图
// 5、afterCompletion() 方法执行
// ※★标记的这两个步骤,满足哪个条件就执行哪一个
// 参数一:原生 request 对象
// 参数二:原生 response 对象
// 参数三:目标 Controller 对象
// 参数四:ModelAndView 是 SpringMVC 底层常用的对象,里面封装了模型和视图(视图是后端渲染情况下才会用到)
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Demo01Interceptor postHandle() 方法执行了");
}
// 参数一:原生 request 对象
// 参数二:原生 response 对象
// 参数三:目标 Controller 对象
// 参数四:前面代码抛出的异常,“前面代码”包括preHandle()、postHandle()和目标 Controller 方法
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Demo01Interceptor afterCompletion() 方法执行了");
}
}
②配置注册拦截器
[1]创建配置类

[2]在配置类中注册拦截器
@Configuration
public class DemoConfig implements WebMvcConfigurer {
@Autowired
private Demo01Interceptor demo01Interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(demo01Interceptor);
}
}
③匹配规则
[1]精确匹配
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(demo01Interceptor);
// 注册其它拦截器
registry.addInterceptor(demo02Interceptor)
.addPathPatterns("/demo/private/target03");
}
访问指定的路径:/demo/private/target03(Demo02拦截器执行了)

访问未指定的路径:/demo/private/target04(Demo02拦截器没执行)

[2]模糊匹配:匹配单层路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(demo01Interceptor);
// 注册其它拦截器
registry.addInterceptor(demo02Interceptor)
.addPathPatterns("/demo/private/*");
}
访问不匹配的路径:/demo/public/target01(Demo02不执行)

访问匹配的路径:/demo/private/target02(Demo02执行)

[3]模糊匹配:匹配多层路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(demo01Interceptor);
// 注册其它拦截器
registry.addInterceptor(demo02Interceptor)
.addPathPatterns("/demo/**");
}
访问测试:/demo/public/target01

访问测试:/demo/private/target02

[4]配置不拦截路径
在前面拦截的范围内,通过excludePathPatterns()指定一个特殊情况范围
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(demo01Interceptor);
// 注册其它拦截器
registry.addInterceptor(demo02Interceptor)
.addPathPatterns("/demo/**")
.excludePathPatterns("/demo/private/target03");
}
访问测试:/demo/public/target01

访问测试:/demo/private/target03

3、用法:多个拦截器
①默认顺序
当一个请求匹配多个拦截器时,这些拦截器就都会执行,顺序如下:
- 所有匹配的拦截器的preHandle()方法依次执行,顺序和注册顺序一致
- 目标 Controller 方法
- HttpMessageConverter的write()方法(仅在目标 Controller 方法标记@ResponseBody时生效)
- 所有匹配的拦截器的postHandle()方法依次执行,顺序和注册顺序相反
- 渲染视图(仅在目标 Controller 方法没有标记@ResponseBody,需要SpringMVC渲染视图时生效)
- 所有匹配的拦截器的afterCompletion()方法依次执行,顺序和注册顺序相反
拦截器相关方法源码位置:
- 源码所在的类:org.springframework.web.servlet.HandlerExecutionChain
- 调用所有拦截器preHandle()的方法:applyPreHandle()
- 调用所有拦截器postHandle()的方法:applyPostHandle()
- 调用所有拦截器afterCompletion()的方法:triggerAfterCompletion()
HttpMessageConverter接口实现类源码位置:
- 实现类全类名:org.springframework.http.converter.AbstractGenericHttpMessageConverter
- 方法名:write()
②明确指定
在order()方法中指定一个整数:数值越小,优先级越高
- 优先级高:在正序中排在前面,倒序中排在后面
- 优先级低:在正序中排在后面,倒序中排在前面
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册其它拦截器
registry.addInterceptor(demo02Interceptor)
.addPathPatterns("/demo/**")
.excludePathPatterns("/demo/private/target03")
.order(2)
;
// 注册拦截器
registry.addInterceptor(demo01Interceptor).order(1);
}
使用 @Order 注解标记在拦截器类上也可以实现相同效果,如果注解和调用 order() 同时存在,以调用 order() 方法做的设置为准
③某个拦截器不放行(了解)
- 外层拦截器preHandle()方法不放行:后续所有操作都不执行了
- 内层拦截器的所有方法
- 目标Controller方法
- 内层拦截器preHandle()方法不放行:
- 对内层拦截器自己的影响:
- 目标Controller方法不执行了
- 自己的postHandle()不执行
- 自己的afterCompletion()不执行
- 对外层拦截器的影响:
- 外层的preHandle()方法不受影响(原因:在内层 preHandle() 之前已经执行完)
- 外层的postHandle()方法没有执行(原因:目标 Controller 方法没有执行)
- 外层的afterCompletion()方法不受影响
- 对内层拦截器自己的影响:
4、练习
①需求
检测:当前请求是否包含一个请求参数
- 请求参数名称:monster
- 请求参数值:Peter
判断标准:当前请求包含 monster 这个请求参且值是 Peter 就放行,否则就返回错误信息
②创建拦截器类
@Component
public class DemoMonsterInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1、获取请求参数:调用原生 request 对象的方法
String monster = request.getParameter("monster");
// 2、检查 monster 是否等于 Peter
// 字符串类型变量和字符串字面量比较时,最佳写法:用字面量字符串调用 equals() 方法
// 因为如果是用字符串变量调用方法还需要先判断它是否为 null
// if (monster == null || !monster.equals("Peter"))
if ("Peter".equals(monster)) {
// 3、满足既定条件则放行
return true;
}
// 4、不满足条件就不放行
// 不放行的情况,需要返回一个错误信息,此时需要使用原生的响应对象
// [1]设置响应体的字符集
response.setCharacterEncoding("UTF-8");
// [2]获取 Writer 对象,专门负责把响应数据写入缓冲区
PrintWriter writer = response.getWriter();
// [3]把响应数据通过 writer 对象写入缓冲区
writer.write("抱歉!您的请求不符合要求,没有携带指定的怪兽!");
// [4]冲刷缓冲区,让服务器把缓冲区的数据作为响应返回给前端
writer.flush();
return false;
}
}
③注册拦截器类
@Configuration
public class DemoWebConfig implements WebMvcConfigurer {
@Autowired
private DemoMonsterInterceptor demoMonsterInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoMonsterInterceptor);
}
}