SpringBoot之拦截器

SpringBoot之拦截器

目录

拦截器

拦截器的使用

自定义拦截器

注册配置拦截器

拦截器详解

拦截器路径

拦截器执行流程

修改之前登录校验功能

定义拦截器

注册配置拦截器

DispatcherServlet源码解析(重要)

初始化

​编辑

处理请求(核心)

适配器模式

适配器模式定义

适配器模式角色

适配器模式的实现

适配器模式的优缺点

适配器模式的应用场景


拦截器

之前我们完成的图书管理系统完成了强制登录的功能, 实现原理是后端在用户登录成功后会把用户信息存储到了Session中,后端程序根据Session来判断用户是否登录, 但是这样是⽐较麻烦的:

• 需要修改每个接⼝的处理逻辑
• 需要修改每个接⼝的返回结果
• 接⼝定义修改, 前端代码也需要跟着修改

有没有更简单的办法, 统⼀拦截所有的请求, 并进⾏Session校验呢?

这⾥我们学习⼀种新的解决办法: 拦截器。 

拦截器是Spring框架提供的核⼼功能之⼀, 主要⽤来拦截⽤⼾的请求, 在指定⽅法前后, 根据业务需要执行预先设定的代码。

拦截器的使用

拦截器的使用分为两步:

1.定义拦截器;

2.注册配置拦截器。
自定义拦截器

实现HandlerInterceptor接口,并重写其所有方法

LoginInterceptor

@Slf4j @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("LoginInterceptor 目标方法执行前执行"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("LoginInterceptor 目标方法执行后执行"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("LoginInterceptor 视图渲染完毕后执行,最后执行"); } }
• preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true表示可以继续执⾏后续操作; 返回false表示中断后续操作。
• postHandle()⽅法:⽬标⽅法执⾏后执⾏。
• afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏ 。
注册配置拦截器

实现WebMvcConfigurer接口,并重写addInterceptors方法

WebConfig

@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") ; } }

 启动服务,登陆成功后,发送根据图书id查询图书请求,观察后端日志:

可以看到preHandle方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行postHandle和afterCompletion方法。

我们吧拦截器中preHandle方法的返回值改为false,在观察后端日志:

后端日志: 

此时看到只有“目标方法执行前执行”这一条日志。

原因是因为:preHandle方法拦截了所有请求。 

拦截器详解
拦截器路径

拦截路径是指我们定义的这个拦截器, 对哪些请求⽣效.
我们在注册配置拦截器的时候, 通过 addPathPatterns() ⽅法指定要拦截哪些请求. 也可以通过
excludePathPatterns() 指定不拦截哪些请求.
上述代码中, 我们配置的是 /** , 表⽰拦截所有的请求.

⽐如用户登录校验, 我们希望可以对除了登录之外所有的路径⽣效.

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import springbook.interceptor.LoginInterceptor; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/user/login") ; } }

在拦截器中除了可以设置 /** 拦截所有资源外,还有一些常见拦截路径设置:

拦截路径含义举例
/*一级路径
能匹配/user,/book,/login,不能匹配 /user/login
/**任意级路径
能匹配/user,/user/login,/user/reg
/book/*/book下的一级路径
能匹配/book/addBook,不能匹配/book/addBook/1,/book
/book/**/book下的任意级路径
能匹配/book,/book/addBook,/book/addBook/2,不能匹
配/user/login

拦截器执行流程

正常的调用顺序:

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

1. 添加拦截器后, 执⾏Controller的⽅法之前, 请求会先被拦截器拦截住. 执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的⽅法. 如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
2. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及
afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据.
修改之前登录校验功能
定义拦截器

从session中获取⽤⼾信息, 如果session中不存在, 则返回false,并设置http状态码为401, 否则返回true. 

@Slf4j @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //true 表示放行 false表示拦截 log.info("LoginInterceptor preHandle...."); //获取session, 并且判断session中存储的userinfo信息是否为空 HttpSession session = request.getSession(); UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY); if (userInfo==null || userInfo.getId()<=0){ //用户未登录 response.setStatus(401); return false; } return true; } }
注册配置拦截器
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/user/login") .excludePathPatterns("/css/**") .excludePathPatterns("/js/**") .excludePathPatterns("/pic/**") .excludePathPatterns("/**/*.html") ; } }

使用浏览器访问

http://127.0.0.1:8080/book/queryBookById?bookId=1

访问结果:

使用Fiddler抓包:

登陆之后再访问上面的链接

DispatcherServlet源码解析(重要)

观察服务启动日志:

当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.

所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法. controller
当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion() ,返回给
DispatcherServlet, 最终给浏览器响应数据. 

初始化

DispatcherServlet的初始化⽅法 init() 在其⽗类 HttpServletBean 中实现的.
主要作⽤是加载 web.xml 中 DispatcherServlet 的 配置, 并调⽤⼦类的初始化.

在 HttpServletBean 的 init() 中调⽤了 initServletBean() , 它是在
FrameworkServlet 类中实现的, 主要作⽤是建⽴ WebApplicationContext 容器(有时也称上下⽂), 并加载 SpringMVC 配置⽂件中定义的 Bean到该容器中, 最后将该容器添加到 ServletContext 中. 下⾯是initServletBean() 的具体代码:

此处打印的⽇志, 也正是控制台打印出来的⽇志:

初始化web容器的过程中, 会通过onRefresh 来初始化SpringMVC的容器:

在initStrategies()中进⾏9⼤组件的初始化, 如果没有配置相应的组件,就使⽤默认定义的组件(在
DispatcherServlet.properties中有配置默认的策略)。关于9大组件的解释

1. 初始化⽂件上传解析器MultipartResolver:从应⽤上下⽂中获取名称为multipartResolver的Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器;
2. 初始化区域解析器LocaleResolver:从应⽤上下⽂中获取名称为localeResolver的Bean,如果没有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器;
3. 初始化主题解析器ThemeResolver:从应⽤上下⽂中获取名称为themeResolver的Bean,如果没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器;
4. 初始化处理器映射器HandlerMappings:处理器映射器作⽤,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器;
5. 初始化处理器适配器HandlerAdapter:作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的
HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认
SimpleControllerHandlerAdapter作为处理器适配器;
6. 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有
handlerExceptionResolver,则从ApplicationContext中获取到所有的
HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解析器,则不设置异常处理器;
7. 初始化RequestToViewNameTranslator:其作⽤是从Request中获取viewName,从
ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤
DefaultRequestToViewNameTranslator;
8. 初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean,如果没有,则默认InternalResourceViewResolver作为视图解析器;
9. 初始化FlashMapManager:其作⽤是⽤于检索和保存FlashMap(保存从⼀个URL重定向到另⼀个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则默认使⽤DefaultFlashMapManager 。
处理请求(核心)

DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller.
我们来看doDispatch ⽅法的具体实现: 

 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 afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }

其中有三个方法关注一下:

上面的三个方法对应我们之前使用到的HandlerInterceptor接口:

适配器模式

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

适配器模式定义
适配器模式, 也叫包装器模式. 将⼀个类的接⼝,转换成客⼾期望的另⼀个接⼝, 适配器让原本接⼝不兼容的类可以合作⽆间.

简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配调⽤⽅使⽤. 把两个不兼容的接⼝通过⼀定的⽅式使之兼容.

⽐如下⾯两个接⼝, 本⾝是不兼容的(参数类型不⼀样, 参数个数不⼀样等等):

可以通过适配器的⽅式, 使之兼容:

适配器模式角色
• Target: ⽬标接⼝ (可以是抽象类或接⼝), 客⼾希望直接⽤的接⼝
• Adaptee: 适配者, 但是与Target不兼容
• Adapter: 适配器类, 此模式的核⼼. 通过继承或者引⽤适配者的对象, 把适配者转为⽬标接⼝
• client: 需要使⽤适配器的对象
适配器模式的实现

场景: 前⾯学习的slf4j 就使⽤了适配器模式, slf4j提供了⼀系列打印⽇志的api, 底层调⽤的是log4j 或者logback来打⽇志, 我们作为调⽤者, 只需要调⽤slf4j的api就⾏了.

Log4j

public class Log4j { public void log4jPrint(String message){ System.out.println("我是Log4j, 打印日志内容为: "+message); } }

Log4jAdapter

public class Log4jAdapter implements Slf4jLog{ private Log4j log4j; public Log4jAdapter(Log4j log4j) { this.log4j = log4j; } @Override public void log(String message) { log4j.log4jPrint("我是是适配器, 打印日志为: "+ message); } }

Slf4jLog

public interface Slf4jLog { void log(String message); }

Main

public class Main { public static void main(String[] args) { Slf4jLog slf4jLog = new Log4jAdapter(new Log4j()); slf4jLog.log("我是客户端"); } }

运行结果:

可以看出, 我们不需要改变log4j的api,只需要通过适配器转换下, 就可以更换⽇志框架, 保障系统的平稳运行.

适配器模式的优缺点

优点

提高系统的灵活性和可扩展性:通过适配器模式,可以很方便地增加新的适配器来支持新的接口,而不需要修改原有的代码。
解耦:客户端通过适配器与需要适配的类进行交互,降低了客户端与需要适配的类之间的耦合度。

缺点

过多使用适配器会使系统变得复杂:由于引入了适配器,系统的类数量会增加,这可能会使系统的结构变得复杂。
可能隐藏了需要适配的类的真正功能:客户端通过适配器与需要适配的类进行交互,可能会忽略需要适配的类的某些功能。
适配器模式的应用场景
当系统需要使用现有的类,而这些类的接口不符合系统的需要时。
想要建立一个可以重复使用的类库,以便与一些彼此之间没有太大关联甚至完全不兼容的类一起工作。
在开发过程中,由于某些原因(如框架升级、第三方库变更等)导致原有的接口发生变化,而客户端代码依赖于旧的接口时。

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk