文章目录
- 前言
- 一、为什么不使用 SpringAOP ?
- 1, 需求分析
- 2, SpringAOP 能实现吗?
- 二、使用 HandlerInterceptor
- 1, 实现 HandlerInterceptor 接口
- 2, 将自定义拦截器加入到系统配置
- 三、HandlerInterceptor 实现原理
- 源码分析
- 总结
前言
各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你:
📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
📗 Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
📘 JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)
提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!
一、为什么不使用 SpringAOP ?
1, 需求分析
上篇文章 介绍了 AOP 和 SpringAOP , 接下来分析一个需求 : UserController 类里面定义了许多和用户相关的控制层代码, 在一层, 很多业务逻辑都是需要用户登录之后才能执行的
@RestController
@RequestMapping("/user")
public class UserController {
// 某⽅法 1
@RequestMapping("/m1")
public Object method(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,进行业务处理
return true;
} else {
// 未登录
return false;
}
}
// 某⽅法 2
@RequestMapping("/m2")
public Object method2(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
}
从上述代码可以看出, 每个方法中都有相同的用户登录验证权限, 它的缺点是:
-
- 每个方法中都要单独写⽤户登录验证的方法, 即使封装成公共方法, 也⼀样要传参调⽤和在方法中
进⾏判断
- 每个方法中都要单独写⽤户登录验证的方法, 即使封装成公共方法, 也⼀样要传参调⽤和在方法中
-
- 添加控制器越多, 调用用户登录验证的方法也越多, 这样就增加了后期的修改成本和维护成本
-
- 这些⽤户登录验证的方法和接下来要实现的业务几乎没有任何关联, 但每个方法中都要写⼀遍
为了解决这一问题, 首先想到的就是 AOP 面向切面编程
2, SpringAOP 能实现吗?
上篇文章 介绍了 SpringAOP 的各种通知的使用方式, 在当前需求场景下, 使用前置通知或环绕通知 “应该” 是可以首先的, 但仔细一想就会有问题 :
- 要验证用户的登陆状态, 就要先获取到内存中的 session 对象, 但是通过前置或者环绕通知的方式时很难拿到请求对象的, 也就很难拿到 session 对象进行判断
- 与我们用户相关的控制器中并非所有方法都要进行拦截判断(像登录、注册方法, 通过原生 SpringAOP 的切点表达式配置拦截规则几乎是做不到的, 说白了就是无法定义哪些方法需要被拦截, 哪些方法不能被拦截
更好的方式是使用 HandlerInterceptor (拦截器)
二、使用 HandlerInterceptor
HandlerInterceptor 拦截器是将传统 AOP 进行了封装, 内置了 reuqest, response 对象, 提供了更加方便的功能
拦截器的实现分为以下两个步骤:
- 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法
- 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法
1, 实现 HandlerInterceptor 接口
定义一个 LoginInterceptor 类表示登录拦截器, 实现 HandlerInterceptor 接口, 重写 preHandle()
@Component // 表示这是一个组件
public class LoginInterceptor implements HandlerInterceptor {
private final String SEEION_KEY = "SEEION";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("SEEION") != null) {
return true;
}
return false;
}
}
preHandle() 是调用目标方法(要被拦截的方法)执行之前的方法, 此方法返回的是 boolean 类型的值
- 如果返回的 true 表示可以放行, 允许继续走后续的流程, 执行目标方法
- 如果返回 false, 表示不能放行, 不允许执行后续的流程和目标方法
2, 将自定义拦截器加入到系统配置
定义一个 APPConfig 类, 实现 WebMvcConfigurer 接口, 重写 addInterceptors()
addInterceptors() 这个方法需要将我们上面写好的自定义拦截器( LoginInterceptor 的 Bean 对象)加入到系统配置, 所以可以使用 @Autowired 进行注入
@Configuration // 表示这是一个配置
public class APPConfig implements WebMvcConfigurer {
// 把拦截器那个 Bean 注入进来
@Autowired
private LoginInterceptor loginInterceptor;
// 添加拦截器规则
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor) // 添加我们的自定义拦截器
.addPathPatterns("/**") // 拦截所有url
.excludePathPatterns("/user/login")// 放行这个url
.excludePathPatterns("/login.html")// 放行这个url
.excludePathPatterns("/reg.html")// 放行这个url
.excludePathPatterns("/css/**")// 放行这个url
.excludePathPatterns("/editor.md/**")// 放行这个url
.excludePathPatterns("/editor.md/**")// 放行这个url
.excludePathPatterns("/img/**")// 放行这个url
.excludePathPatterns("/js/**");// 放行这个url
}
}
1, addPathPatterns() 表示需要拦截的 url ,
**
表示拦截任意 url
2, excludePathPatterns():表示需要排除(不拦截)的 url
说明:以上拦截规则可以拦截此项目中使用的 url, 包括静态文件件(图片, html, css, js 等)
三、HandlerInterceptor 实现原理
在使用拦截器之前, 用户访问 web 网站的前后端交互流程大致如下 :
前端的所有请求都会先来到 Controller 层, 但加入了拦截器, 会在 Controller 层之前工作
源码分析
-
所有的 Controller 层的执行都会通过一个 调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台打印的日志信息看出,如下图所示
-
而所有方法都会执行 DispatcherServlet 中的 doDispatch() 调度方法, doDispatch() 部分源码如下(只看重点)
// 预处理【重点】
if (!mappedHandler.applyPreHandle(processedRequest, respon se)) {
return;
}
// 往后执⾏ Controller 中的业务
mv = ha.handle(processedRequest, response, mappedHandler.g
etHandler());
if (asyncManager.isConcurrentHandlingStarted()){
return;
}
上述代码表示 : 开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle()
applyPreHandle() 方法是Boolean 类型, 如果 applyPreHandle() 返回值为 true, 才能执行Controller 层的方法
- applyPreHandle() 的源码如下 :
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 获取项⽬中使⽤的拦截器 HandlerInterceptor
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
上述代码表示, 遍历 interceptorList (我们自定义拦截器的集合), 执行我们重写的 preHandle()
如果我们自定义的拦截器都返回 true 了, applyPreHandle() 才会返回 true , 才能执行 Controller 层的方法
总结
拦截器 HandlerInterceptor 相比于 SpringAOP 有两大优点 :
- preHandle() 可以轻松的获取并使用 request 和 response 对象
- addInterceptors() 中将自定义的拦截器加入到系统配置, addPathPatterns() 和 excludePathPatterns() 配合可以很自由的定义拦截规则
拦截器会在Controller 层之前执行, 执行我们定义的预处理逻辑
以上就是本篇的所有内容了, 如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~
上山总比下山辛苦
下篇文章见