【SpringBoot】一篇文章讲清楚拦截器所有知识

【SpringBoot】一篇文章讲清楚拦截器所有知识

🎬 那我掉的头发算什么个人主页
🔥 个人专栏: 《javaSE》《数据结构》《数据库》《javaEE》

⛺️待到苦尽甘来日


在这里插入图片描述

文章目录

拦截器

快速入门

拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法执行的前后,根据业务需要执行预先设定的代码。
也就是说,拦截器允许开发人员提前定义一些通用逻辑,在用户的请求被处理前、响应返回后执行;也可以在用户请求处理前直接阻止其执行。
在拦截器中,开发人员可以实现应用程序中的一些通用性操作。比如通过拦截器拦截前端发来的所有请求,判断 Session 中是否存在登录用户的信息:如果存在则放行请求,继续处理业务;如果不存在则拦截请求,拒绝后续处理

在这里插入图片描述


就好比上学时进校门出校门需要带出入证,保安就是拦截器,他会检查你是否带了出入证,带了放行,没带不放行。

定义拦截器

packagecom.hbu.book.interceptor;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importorg.jspecify.annotations.Nullable;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;publicclassLoginInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{returnHandlerInterceptor.super.preHandle(request, response, handler);}@OverridepublicvoidpostHandle(HttpServletRequest request,HttpServletResponse response,Object handler,@NullableModelAndView modelAndView)throwsException{HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@OverridepublicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,@NullableException ex)throwsException{HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}

preHandle () 方法:在目标方法执行前执行。返回 true 时,继续执行后续操作(如目标方法、其他拦截器逻辑);返回 false 时,直接中断后续所有操作。

postHandle () 方法:在目标方法执行完成后执行。

afterCompletion () 方法:在视图渲染完毕后执行,是拦截器中最后执行的方法(后端开发中现在几乎不涉及视图相关开发,暂时无需深入了解)。

注册配置拦截器

packagecom.hbu.book.config;importcom.hbu.book.interceptor.LoginInterceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{//自己定义的拦截器对象@AutowiredprivateLoginInterceptor loginInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//拦截器拦截的请求路径}}

注册完成之后拦截器就算真正配置到了项目中了。下面我们来把拦截器代码简化一下,先看看他是怎么使用的:

在这里插入图片描述


在这里插入图片描述


当我们产生请求时,也就是访问一次Controller层时,在进入控制层之前会先执行preHandle逻辑,业务逻辑执行完了会执行postHandle逻辑。

因此,如果我们把preHandle的返回结果改成false,那么所有的请求根本就不会进入到Controller。就算只是访问的前端页面,但是页面的返回还是需要经过后端的,最终页面也就不会被加载出来:

在这里插入图片描述


在这里插入图片描述

拦截器详解

拦截路径

在这里插入图片描述


我们在设置拦截路径的时候还可以使用excludePathPatterns()来指定不拦截哪些请求。
比如,我们可以在拦截器中定义检查Session来确定用户是不是登录了来防止别人恶意进入我们网站,此时就可以对除了登录路径/User/Login以外的路径进行拦截:

packagecom.hbu.book.config;importcom.hbu.book.interceptor.LoginInterceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{//自己定义的拦截器对象@AutowiredprivateLoginInterceptor loginInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginInterceptor).addPathPatterns("/**")//拦截器拦截的请求路径.excludePathPatterns("/user/login");}}

拦截器路径设置:

在这里插入图片描述

拦截器执行流程

正常流程:

在这里插入图片描述


有了拦截器之后:

在这里插入图片描述


添加拦截器后,执行 Controller 的方法之前,请求会先被拦截器拦截,执行 preHandle() 方法。该方法需要返回一个布尔类型的值:
返回 true:表示放行本次请求,继续执行 Controller 中的目标方法;
返回 false:表示拒绝放行,Controller 中的方法不会被执行。

当 Controller 中的方法执行完毕后,会依次执行 postHandle() 方法和 afterCompletion() 方法,待这些方法执行完毕后,最终才会向浏览器响应数据。

登录校验

学会了拦截之后,我们就可以对图书管理系统的登录检验功能进行拦截啦。

项目结构:

在这里插入图片描述

定义注册拦截器

packagecom.hbu.book.interceptor;importcom.hbu.book.constant.Constants;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importjakarta.servlet.http.HttpSession;importorg.jspecify.annotations.Nullable;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;@ComponentpublicclassLoginInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{HttpSession httpSession = request.getSession(false);if(httpSession !=null&& httpSession.getAttribute(Constants.SESSION_USER_KEY)!=null){returntrue;} response.setStatus(401);returnfalse;}@OverridepublicvoidpostHandle(HttpServletRequest request,HttpServletResponse response,Object handler,@NullableModelAndView modelAndView)throwsException{System.out.println("方法执行之后执行-postHandle");}@OverridepublicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,@NullableException ex)throwsException{HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}
packagecom.hbu.book.config;importcom.hbu.book.interceptor.LoginInterceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{//自己定义的拦截器对象@AutowiredprivateLoginInterceptor loginInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginInterceptor).addPathPatterns("/**")//拦截器拦截的请求路径.excludePathPatterns("/user/login").excludePathPatterns("/**/*.js")//排除前端静态资源.excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.png").excludePathPatterns("/**/*.html");}}

此时我们就完成了对不通过登录接口想直接访问我们的页面的行为的检验。

@RequestMapping("/getListByPage")publicResultgetListByPage(PageRequest pageRequest,HttpSession session){//参数校验//返回数据if(session.getAttribute(Constants.SESSION_USER_KEY)==null){returnResult.unlogin();}UserInfo userInfo =(UserInfo)session.getAttribute(Constants.SESSION_USER_KEY);if(userInfo==null|| userInfo.getId()<=0){//用户未登录returnResult.unlogin();}PageResp<BookInfo> listByPage = bookService.getListByPage(pageRequest); log.info("此页码的数据:{}",listByPage);returnResult.success(listByPage);}

因此,这里的登录检验就可以删除了。

DispatcherServlet 源码分析

在这里插入图片描述


Tomcat 启动后,Spring MVC 核心类 DispatcherServlet 负责控制请求的整体执行顺序,具体流程如下:
1.所有客户端请求都会首先进入 DispatcherServlet,并执行其核心调度方法 doDispatch();
2.若当前请求配置了拦截器(Interceptor),则优先执行拦截器的 preHandle() 方法;
3.若 preHandle() 方法返回 true,请求会继续向后执行,进入对应的 Controller 层方法并完成业务逻辑处理;
4.Controller 方法执行完毕后,会回溯执行拦截器的 postHandle() 方法和 afterCompletion() 方法;
5.上述流程全部完成后,结果返回至 DispatcherServlet,最终由 DispatcherServlet 将响应数据返回给浏览器。

在这里插入图片描述

Tomcat 启动后,DispatcherServlet作为前端控制器,是所有 HTTP 请求的统一入口。它不负责具体业务逻辑,只做请求分发、组件协调、流程控制,相当于整个 Spring MVC 的「指挥中心」。
而它所有的核心调度逻辑,都封装在doDispatch()方法中 —— 这也是我们分析源码的核心入口。

源码:(仅保留主干代码,关键步骤标注注释版)

protectedvoiddoDispatch(HttpServletRequest request,HttpServletResponse response)throwsException{HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler =null;boolean multipartRequestParsed =false;WebAsyncManager asyncManager =WebAsyncUtils.getAsyncManager(request);try{ModelAndView mv =null;Exception dispatchException =null;try{// 1. 预处理:判断是否为文件上传请求,做包装处理 processedRequest =checkMultipart(request); multipartRequestParsed =(processedRequest != request);// 2. 根据当前请求,匹配对应的Handler(Controller中的目标方法) mappedHandler =getHandler(processedRequest);// 没有找到Handler,直接返回404异常if(mappedHandler ==null){noHandlerFound(processedRequest, response);return;}// 3. 核心:根据Handler,找到对应的HandlerAdapter(适配器)HandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());// 4. 执行拦截器的preHandle()方法// 返回true则继续,返回false直接中断请求if(!mappedHandler.applyPreHandle(processedRequest, response)){return;}// 5. 通过HandlerAdapter执行Handler(真正调用Controller方法) mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 异步请求直接返回,不执行后续同步逻辑if(asyncManager.isConcurrentHandlingStarted()){return;}// 给ModelAndView设置默认视图名applyDefaultViewName(processedRequest, mv);// 6. 执行拦截器的postHandle()方法 mappedHandler.applyPostHandle(processedRequest, response, mv);}catch(Exception ex){ dispatchException = ex;}// 7. 统一处理结果:渲染视图、执行拦截器afterCompletion()processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}finally{// 异步请求&文件上传请求的资源清理if(asyncManager.isConcurrentHandlingStarted()){if(mappedHandler !=null){ mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else{if(multipartRequestParsed){cleanupMultipart(processedRequest);}}}}

1.请求预处理:判断是否是文件上传请求,做包装适配;
2.查找 Handler:通过HandlerMapping根据 URL 匹配到目标 Controller 方法(即 Handler);
3.查找 HandlerAdapter:根据 Handler 类型,找到能执行它的适配器;
4.前置拦截:执行所有拦截器的preHandle(),校验通过才继续;
5.执行 Handler:通过 HandlerAdapter 调用 Controller 方法,得到 ModelAndView;
6.后置拦截:执行拦截器的postHandle(),可对结果做二次处理;
7.结果处理:渲染视图、执行拦截器afterCompletion(),最终响应浏览器。

适配器模式

HandlerAdapter在Spring MVC中使用了适配器模式。

适配器模式定义
适配器模式,也叫包装器模式。将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
简单来说就是目标类不能直接使用,通过一个新类进行包装一下,适配调用方使用。把两个不兼容的接口通过一定的方式使之兼容。
比如下面两个接口,本身是不兼容的(参数类型不一样,参数个数不一样等等)。

在这里插入图片描述


通过适配器可以让他们兼容

在这里插入图片描述


在这里插入图片描述


比如生活中插头的转换器也是这样。

适配器模式角色
・Target: 目标接口 (可以是抽象类或接口), 客户希望直接用的接口
・Adaptee: 适配者,但是与 Target 不兼容
・Adapter: 适配器类,此模式的核心。通过继承或者引用适配者的对象,把适配者转为目标接口
・client: 需要使用适配器的对象

咱们前面提到的slf4j门面模式其实也是通过适配器模式兼容的

packagecom.hbu.book.adapter;publicclass logback {publicvoidprint(String log){System.out.println("logback: "+ log);}}
packagecom.hbu.book.adapter;publicclass logbackAdapter implementsSlf4j{private logback logback;publicvoidlog(String log){ logback.print(log);}}
packagecom.hbu.book.adapter;publicclassLog4j{publicvoidprint(String log){System.out.println("log4j: "+ log);}}
packagecom.hbu.book.adapter;publicclassLog4jAdapterimplementsSlf4j{privateLog4j log4j;publicvoidlog(String log){ log4j.print(log);}}
packagecom.hbu.book.adapter;publicinterfaceSlf4j{voidlog(String log);}
packagecom.hbu.book.adapter;publicclassClient{publicstaticvoidmain(String[] args){Slf4j slf4j =newLog4jAdapter();Slf4j slf4j1 =newlogbackAdapter(); slf4j.log("111"); slf4j1.log("222");}}

咱们的Slf4j对用户提供了一个log接口打印日志,但是底层调用的Logback和Log4j提供的方法却是print,不适配。此时就可以通过适配器来兼容。

适配器模式应用场景
一般来说,适配器模式可以看作一种 “补偿模式”,用来补救设计上的缺陷。应用这种模式算是 “无奈之举”, 如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使用适配器模式了所以适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能。比如版本升级等.

以上就是本篇博客全部内容!

Read more

【数据结构和算法】链表的综合算法练习:1.返回倒数第k个节点 2.相交链表 3.回文链表

【数据结构和算法】链表的综合算法练习:1.返回倒数第k个节点 2.相交链表 3.回文链表

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、返回倒数第k个节点 * 1.1题目 * 1.2 算法原理 * 1.3 代码 * 二、相交链表 * 2.1 题目 * 2.2 算法原理 * 2.3 代码 * 三、回文链表 * 3.1 题目 * 3.2 算法原理 * 3.3 代码 * 总结与每日励志 前言 链表作为数据结构的基础核心,是算法面试与嵌入式开发中高频考察的重点。

By Ne0inhk

Python文本为什么会乱码?从根源到解决方案的深度解析

“乱码”是每个Python开发者,尤其是处理中文、日文等非ASCII字符时,都会遇到的“噩梦”。明明代码逻辑正确,文件也存在,但打印出来或保存的文件却是一堆莫名其妙的符号(如éÂ\x87Â\x91éÂ\x9eÂ\x93)。 这篇文章将带你彻底理解乱码产生的根本原因,并提供一套行之有效的解决方案和最佳实践。 一、乱码的本质:编码与解码的“鸡同鸭讲” 要理解乱码,首先必须明白两个核心概念:字符集(Charset) 和 字符编码(Character Encoding)。 1. 字符集(Charset):是一个系统支持的所有抽象字符的集合。比如: * ASCII:包含128个字符(英文字母、数字、符号),用1个字节(8位)表示。 * GBK/GB2312:中国国家标准,包含汉字、符号等,用1或2个字节表示。 * Unicode:

By Ne0inhk

B站充电视频下载器(需配合会员Cookie使用,仅供学习交流,Python)

这个程序是一个用于下载B站充电视频的工具,依赖于用户提供的会员Cookies。如何获取B站cookie请参考本站cookie登录b站获取cookie登录billbill教程。 程序主要功能:加载和验证Cookies,从文件中读取Cookies,并验证其有效性。获取视频信息,通过B站API获取视频的详细信息。获取视频播放地址,通过B站API获取视频的实际播放地址。 下载视频,从播放地址下载视频文件,并显示下载进度。 首先,类定义和初始化。初始化时从 cookie_file 中加载Cookies,并设置HTTP请求头。 class ChargeVideoDownloader: def __init__(self, cookie_file): self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/

By Ne0inhk

python addict的用法

addict 是一个流行的 Python 第三方库,主要用来让你像访问属性一样访问字典里的数据(即点操作符访问属性,而不是方括号),从而使字典操作更方便和美观。常用的核心类为Dict。 基本用法 安装 addict pip install addict 基本示例 from addict import Dict data = Dict() data.name ="Tom"# 直接赋值属性 data.age =21print(data)# {'name': 'Tom', 'age': 21}print(data.name)# 'Tom'print(data[

By Ne0inhk