拦截器
上个章节我们完成了强制登录的功能, 后端程序根据Session来判断用户是否登录, 但是实现方法是比较麻烦的:
- 需要修改每个接口的处理逻辑
- 需要修改每个接口的返回结果
- 接口定义修改, 前端代码也需要跟着修改
有没有更简单的办法, 统一拦截所有的请求, 并进行Session校验呢, 这里我们学习一种新的解决办法: 拦截器
什么是拦截器?
拦截器
是Spring框架
提供的核心功能之一,主要用来拦截用户的请求
, 在指定方法(目标方法)前后, 根据业务需要执行预先设定的代码
.
也就是说, 允许开发人员提前预定义一些逻辑
, 在用户的请求响应前后执行. 也可以在用户请求前阻止其执行
.
在拦截器当中,开发人员可以在应用程序
中做一些通用性的操作;
比如通过拦截器来拦截前端发来的请求, 判断Session中是否有登录用户的信息
. 如果有就可以放行, 如果没有就进行拦截.
拦截器的基本使用
拦截器的使用步骤分为两步:
- 定义拦截器
- 注册配置拦截器
自定义拦截器
自定义拦截器:实现HandlerInterceptor
接口,并重写其所有方法
重写完上面三个方法,我们就已经定义好了拦截器,接下来,我们需要定义拦截器需要完成的工作:
preHandle()
:- 目标方法
执行前
执行. - 返回true: 继续执行后续操作; 返回false: 中断后续操作.
- 目标方法
postHandle()
:- 目标方法
执行后
执行
- 目标方法
afterCompletion()
:- 视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图, 暂不了解)
使用addInterceptors( )注册配置拦截器
注册配置拦截器:实现WebMvcConfigurer
接口,并重写addInterceptors
方法
实现WebMvcConfigurer
接口:
重写addInterceptors
方法 :
创建一个拦截器对象:
创建一个拦截器对象还是太吃操作了,煮啵,有没有更简单好用的方法推荐一下呢?
有的兄弟,有的!我们已经学过了 SpringIoC 和 DI,也就是依赖管理与依赖注入,能否把创建拦截器对象这个操作,交由 Spring 进行,而不用我们自己手动创建呢?
思考,比起手动创建对象,使用 Spring 帮我们管理对象,并且使用 DI 依赖注入,这样操作有什么优点呢?
【Spring DI】深入解析依赖注入 DI:搭配类注解通过 @Autowired 注解完成属性注入、构造方法注入、Setter 注入方法和三种注入方法的优缺点分析-CSDN博客
将 loginInterceptor
注册到 Spring MVC 的拦截器链中,并配置其拦截的请求路径规则
拦截器使用细节
拦截器的入门程序完成之后,接下来我们来介绍拦截器的使用细节。拦截器的使用细节我们主要介绍两个部分:
- 拦截器的拦截路径配置
- 拦截器实现原理
拦截路径
拦截路径是指我们定义的这个拦截器, 对哪些请求生效.
我们在注册配置拦截器的时候, 通过addPathPatterns()方法指定要拦截哪些请求.
也可以通过excludePathPatterns()
指定不拦截哪些请求.
上述代码中, 我们配置的是
/**
, 表示拦截所有的请求.
比如用户登录校验, 我们希望可以对除了登录之外所有的路径生效.
启动服务,注意观察构造请求前后日志变化:
使用 postman 访问任意请求:
观察后端日志 ,此时的目标方法是 login 接口:
可以看到, preHandle
方法执行之后就放行了, 开始执行目标方法
目标方法执行完成之后执行postHandle和afterCompletion
方法.
我们把拦截器中preHandle
方法的返回值改为false:
再观察运行结果
可以看到, 拦截器拦截了请求, 没有进行响应.
我们对所有路径进行拦截,程序启动程序,使用 Postman 构造请求:
我们看看日志中执行的 SQL
常见拦截路径设置
在拦截器中除了可以设置/**
拦截所有资源外,还有一些常见拦截路径设置:
以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图图片文件, JS 和 CSS 等文件).
拦截器执行流程
正常的调用顺序:
有了拦截器之后,会在调用Controller
之前进行相应的业务处理,执行的流程如下图
- 添加拦截器后, 执行
Controller
的方法之前, 请求会先被拦截器拦截住. - 执行preHandle()方法, 这个方法需要返回一个布尔类型的值.
- 如果返回true, 就表示放行本次操作, 继续访问controller中的方法.
- 如果返回false,则不会放行(controller中的方法也不会执行).
- controller当中的方法执行完毕后,再回过来执行
postHandle()
这个方法以及afterCompletion()
方法 - 执行完毕之后,最终给浏览器响应数据.
排除拦截路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/css/**", "/login"); // 排除静态资源和登录页
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截所有 /api/ 开头的请求
.excludePathPatterns("/api/login"); // 排除 /api/login 路径
excludePathPatterns( )的源码解读及排除拦截页面的方法
完整的背景在这篇博客下面,这里单独提取排除拦截页面请求的方法
图书管理系统:基于拦截器完成登录校验功能
我们来看下面的注册配置拦截器 WebConfig 中 addInterceptors() 的逻辑:
excludePathPatterns( )的源码解读
接下来,我们修改注册配置拦截器
拦截的页面,排除拦截页面需要用到的方法是 excludePathPatterns()
对于excludePathPatterns()
,可以传集合
,也可以传字符串
:
分别表示的排除拦截页面的方法:
硬编码排除
:- 链式调用多个
excludePathPatterns()
,来排除多个页面请求; - 适合路径较少且固定的情况,代码直观。
- 链式调用多个
动态排除
:- 把要排除的页面设置成集合,使用一个
excludePathPatterns()
排除集合中的所有页面请求; - 适合路径较多或可能动态变化的情况,便于维护和扩展。
- 把要排除的页面设置成集合,使用一个
方法一:硬编码排除
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 自定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 设置拦截器拦截的请求路径(/**表示拦截所有请求)
.excludePathPatterns("/user/login") // 设置拦截器排除拦截的路径
.excludePathPatterns("/**/*.js") // 排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}
方法二:动态排除
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 自定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
private List<String> excludePaths = Arrays.asList(
"/user/login",
"/**/*.js",
"/**/*.css",
"/**/*.png",
"/**/*.html"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 设置拦截器拦截的请求路径(/**表示拦截所有请求)
.excludePathPatterns(excludePaths); // 设置拦截器排除拦截的路径
}
}