当前位置: 首页 > news >正文

Spring Boot Interceptor(拦截器使用及原理)

之前的博客中讲解了关于 Spring AOP的思想和原理,而实际开发中Spring Boot对于AOP的思想的具体实现就是Spring Boot Interceptor。在 Spring Boot 应用程序开发中,拦截器(Interceptor)是一个非常有用的工具。它允许我们在 HTTP 请求到达 Controller 之前或响应离开 Controller 之后执行一些自定义逻辑。本文将介绍 Spring Boot 中如何使用拦截器,并提供一些实际的使用示例。

我们还是以对用户是否登录进行校验这个场景来展开叙述。


1 不使用拦截器的用户登陆验证

比如我们的UserController中有多个方法,那么执行这些方法之前肯定要对用户是否登录进行校验。每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。 随着添加的控制器越来越多,调用用户登陆验证的方法也就越来越多,这样就会增加后期的修高成本和维护成本,并且这些代码和实际要实现的业务是没有关系的,实际开发中是不希望一些无关代码侵入业务代码的。

上面的问题可不就是AOP所解决的问题吗!所以提供一个公共的AOP方法来进行统一的用户登录验证是必要的,Spring Boot也为我们提供了使用的方法。

原生的Spring Boot AOP也可以通过前置通知或者环绕通知来进行拦截,但是在配饰拦截规则的时候使用的aspectj时十分不友好的,并且再拦截的时候想要排除一些特定的方法,比如登录和注册,那个拦截规则是很难定义的,甚至根本没办法实现。

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor。

2 Spring 拦截器

在 Spring Boot 应用程序开发中,拦截器(Interceptor)是一个非常有用的工具。它允许我们在 HTTP 请求到达 Controller 之前或响应离开 Controller 之后执行一些自定义逻辑。拦截器的实现可以分为以下两个步骤:

  1. 创建自定义拦截器:实现HandlerInterceptor接口并重写接口的preHander方法(执行具体方法之前的预处理方法)。
  2. 注册定义拦截器:将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。(自定义配置类实现WebMvcConfigurer 并且重写addInterceptors方法,重写addInterceptors方法的目的就是将自定义的拦截器注册到项目中,在这个过程中可以配置拦截规则)

2.1 创建自定义拦截器

首先,我们需要创建一个实现 HandlerInterceptor 接口的类。HandlerInterceptor 接口提供了三个方法:

  • preHandle:在请求处理之前调用
  • postHandle:在请求处理之后调用,但在视图渲染之前
  • afterCompletion:在整个请求完成之后调用,通常用于资源清理

现在的业务需求下我们只需要在重写preHandle方法即可:

/*自定义拦截器 实现HandlerInterceptor接口 */
@Component
public class LoginInterceptor implements HandlerInterceptor {// 调用目标方法之前执行的方法// 此方法返回 boolean 类型的值,//          如果返回的是true 表示(拦截器)验证成功,继续走后续的流程,执行目标方法//          如果返回的是 false 表示拦截器验证失败,后续的流程和目标方法就不执行。@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//用户登录判断业务HttpSession session = request.getSession(false);if(session != null && session.getAttribute("admin")!=null){System.out.println("登录了");// 用户已登录了return true;}//用户没有登录System.out.println("还没有登录");response.sendRedirect("/user/login");//可以重定向到系统的登录路由//response.setStatus(401);//向前端返回相应的状态码  401:没有权限return false;}
}

2.2 注册自定义拦截器

在自定义拦截器之后我们还需要将自定义的拦截器注册到系统的配置中。

addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
excludePathPatterns:表示需要排除的 URL。
UserController代码:
 
@RestController
@RequestMapping("user")
public class UserController {@GetMapping("login")public String login(HttpServletRequest req){System.out.println("执行了login!");// 登录成功,创建会话HttpSession session = req.getSession(true);session.setAttribute("admin","lisi");return "lisi login";}@GetMapping("reg")public String reg(){return "reg";}@GetMapping("index")public String index(HttpServletRequest req){HttpSession session = req.getSession(false);Object admin = session.getAttribute("admin");System.out.println(admin);return "index";}}
运行程序观察执行情况:

1. 没有登陆的状态下访问index:此时请求成功被拦截器拦截下来
2.访问login:此时lisi成功的登录,并且设置了session

3.登陆后再次访问index:触发拦截器但是通过了后端的验证,所以后端会打印“登录了”和session的value也即是lisi,同时返回给前端"index"。

结果:

这样一个通过拦截器实现用户登陆验证的简单案例就完成了。

3 拦截器实现原理

在没有加入拦截器的时候,程序的调用顺序如图所示:

 然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:

Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的。

通过源码分析进一步理解拦截器的工作原理

所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码如下

@SuppressWarnings("deprecation")protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new ServletException("Handler dispatch failed: " + err, err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

 

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,而applyPreHandle ⽅法的实现源码如下:
从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了:

 此时⽤户登录权限的验证⽅法就会执⾏,这就是拦截器的实现原理。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • FltSendMessage 超时时间相关问题
  • 华为HCIP认证H12-831新增变题
  • 基于Netty实现安全认证的WebSocket(wss)服务端
  • 知识分享:隔多久查询一次网贷大数据信用报告比较好?
  • qt for android 重新编译Qt6Android.jar
  • 大整数运算详解升级版
  • 速盾:负载均衡能防ddos攻击吗?
  • 学 Java 具体能干什么?
  • 我的创作纪念日——我与CSDN一起走过的128天
  • 选择排序与堆排序
  • Rust开源Web框架Salvo源码编译
  • Vue中引入组件需要哪三步
  • PostgreSQL的扩展(extensions)-常用的扩展之pg_store_plans
  • Windows系统使用Docker部署Focalboard团队协作工具详细流程
  • 521源码-免费下载-WordPress全能自动采集与发布插件 – WP-AutoPostPro 汉化版
  • Angular2开发踩坑系列-生产环境编译
  • Bootstrap JS插件Alert源码分析
  • HTTP请求重发
  • JavaScript-Array类型
  • Javascript弹出层-初探
  • javascript数组去重/查找/插入/删除
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • Web设计流程优化:网页效果图设计新思路
  • 欢迎参加第二届中国游戏开发者大会
  • 开发基于以太坊智能合约的DApp
  • 那些被忽略的 JavaScript 数组方法细节
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 深入浅出webpack学习(1)--核心概念
  • 用jQuery怎么做到前后端分离
  • 怎么把视频里的音乐提取出来
  • C# - 为值类型重定义相等性
  • 阿里云移动端播放器高级功能介绍
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​马来语翻译中文去哪比较好?
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #mysql 8.0 踩坑日记
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (1)无线电失控保护(二)
  • (52)只出现一次的数字III
  • (70min)字节暑假实习二面(已挂)
  • (Windows环境)FFMPEG编译,包含编译x264以及x265
  • (待修改)PyG安装步骤
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (一) springboot详细介绍
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)visual stdio 书签功能介绍
  • (转载)从 Java 代码到 Java 堆
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET6实现破解Modbus poll点表配置文件
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .NET程序员迈向卓越的必由之路
  • .net打印*三角形