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

SpringBoot拦截器实战与原理剖析

SpringBoot 统一功能处理

拦截器

拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码

拦截器的使用步骤分为两步:

1.定义拦截器

2.注册配置拦截器

自定义拦截器

preHandle  方法:目标方法执行前执行,返回 true:继续执行后续操作,返回 false:中断后续操作

postHandle 方法: 目标方法执行后执行

afterCompletion 方法:视图渲染完毕之后执行,最后执行

注册配置拦截器

拦截器使用细节

1.拦截器的拦截路径配置

2.拦截器实现原理

拦截路径

拦截路径 是指我们定义的这个拦截器,对那些请求生效

addPathPatterns( ) 指定要拦截哪些请求

excludePathPatterns( ) 指定不拦截哪些请求

常见的拦截路径设置:

拦截器执行流程

添加拦截器之后,执行 Controller 方法之前,请求会先被拦截器拦截住,执行 preHandle()方法,如果这个方法返回 true 则会放行,反之不放行。

在 controller 方法执行完毕之后,再回来执行 postHandle( ) 这个方法以及 afterCompletion( ) 方法,执行完毕之后,最终给浏览器响应数据

其实这跟我们前面说的这些方法的定义是一样的

登录校验

定义拦截器

注册配置拦截器

我们排除了 登录 和 前端静态资源

我们在未登录的情况下访问其他接口不出意外的被阻拦了,并且返回了401,这在我们的意料之中

我们发现这次没有拦截成功,正是因为我们登录了,session 中有我们的信息

DispatcherServlet 源码分析

当我们观察服务器启动日志之后:

在 Tomcat 启动之后,有一个核心类 DispatcherServlet它来控制程序的执行顺序,所有的请求都会先进到 DispatcherServlet,执行 doDispatch 调度方法,如果有拦截器,会先执行拦截器 preHandle( ) 方法的代码,如果这个方法返回 true 则会放行,反之不放行。

在 controller 方法执行完毕之后,再回来执行 postHandle( ) 这个方法以及 afterCompletion( ) 方法,执行完毕之后,最终给浏览器响应数据。

这跟我们前面说的拦截器执行顺序是一样的

当然我们还是来看源码

当我们来到源码后,发现很多的东西,这时候不必想着这些是干啥的,我们要清楚我们来的目的就是来探寻 DispatcherServlet 收到请求之后是咋做的。

初始化

在进行一系列的初始化之后,就开始工作了

处理请求

DispatcherServlet 接收到请求后,执行 doDispatch 调度方法,再将请求转给 Controller,我们来看doDispatch()方法的具体实现

我们看到这么多陌生的东西,其实只需要看try里面的就好,先找找有没有自己认识的,找来找去终于找到了认识的了,字面意思不就是应用我们拦截器里的 prehandle( ) 方法嘛

在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle( ) 方法

进入 applyPreHandle( ) 方法

在 applyPreHandle 中会获取所有拦截器 HandleIntercetor,并执行拦截器中的preHandle 方法

如果返回 true,继续执行后续逻辑处理,返回false中断后续操作,这可以在 applyPreHandle( ) 看到

适配器模式

适配器模式也叫包装器模式,将一个类的接口,转换成客户期望的另一个接口,适配器让原本不兼容的类可以合作无间,比如日常用到的转接头

适配器模式角色

Target: 目标接口

Adaptee: 适配者

Adapter: 适配器类,此模式的核心,通过继承或者引用适配者对象,把适配者转为目标接口

Client: 需要使用适配器的对象

适配器模式的实现

之前用的 slf4j 就是使用了适配器模式,slf4j 作为门面对象提供了一系列打印日志的 api,真正底层调用的是 log4j 或者 logback 来打印日志,我们作为调用者只需调用 slf4j 的 api就行了

/*** slf4j接口*/
interface Slf4jApi{void log(String message);
}/*** log4j 接口*/
class Log4j{void log4jLog(String message){System.out.println("Log4j打印:"+message);}
}/*** slf4j和log4j适配器*/
class Slf4jLog4JAdapter implements Slf4jApi{private Log4j log4j;public Slf4jLog4JAdapter(Log4j log4j) {this.log4j = log4j;}@Overridepublic void log(String message) {log4j.log4jLog(message);}
}/*** 客户端调用*/
public class Slf4jDemo {public static void main(String[] args) {Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());slf4jApi.log("使用slf4j打印日志");}
}

适配器模式的应用场景

适配器模式可以看做一种 “补偿模式”,用来补救设计上的缺陷,所以它更多的应用场景是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能

统一数据返回格式

在前面的图书管理系统下,我们做了两部分的工作:

1.通过 Session 来判断用户是否登录

2.对后端返回数据进行封装,告知前端处理的结果

统一返回结果

我们对返回的数据进行了封装

我们来看SpringBoot 是如何帮我们实现统一的数据返回的

它的实现需要 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现

@ControllerAdvice 表示控制器通知类

添加类 ResponseAdvice,实现 ResponseBodyAdvice 接口,并在类上添加 @ControllerAdvice 注解

supports 方法:判断是否要执行 beforeBodyWrite 方法,true 为执行,false 不执行,通过该方法可以选择哪些类或哪些方法的 response 要进行处理,现在我们统一为都处理

beforeBodyWrite 方法,对 response 方法进行具体操作处理

测试:

添加统一数据返回格式之前:

添加统一数据返回格式之后:

我们继续测试其他接口,发现一个问题

在测试修改的时候发生问题了,它报错信息显示的是,接口声明返回的是 String 但是却返回的是

Result 这肯定是因为统一接口返回格式出现了问题

虽然是报错了,但是数据库确实修改成功了

这种错误只有在 返回结果 String 类型时才有这种错误发生

我们来看看解决方案

我们在遇到属于 String 类型的时候进行单独处理

为啥要这样做呢?

SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (分别为 ByteArrayHttpMessageConverter , StringHttpMessageConverter , SourceHttpMessageConverter ,  AllEncompassingFormHttpMessageConverter )

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {//...public RequestMappingHandlerAdapter() {this.messageConverters = new ArrayList<>(4);this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());if (!shouldIgnoreXml) {try {this.messageConverters.add(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}}this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());}//...
}

其中AllEncompassingFormHttpMessageConverter会根据项⽬依赖情况添加对应的 HttpMessageConverter

public AllEncompassingFormHttpMessageConverter() {if (!shouldIgnoreXml) {try {addPartConverter(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}}if (kotlinSerializationJsonPresent) {addPartConverter(new KotlinSerializationJsonHttpMessageConverter());}if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());}else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}else if (jsonbPresent) {addPartConverter(new JsonbHttpMessageConverter());}if (jackson2XmlPresent && !shouldIgnoreXml) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}
}

在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到messageConverters 链的末尾.

Spring会根据返回的数据类型,从 messageConverters 链选择合适的HttpMessageConverter .

当返回的数据是⾮字符串时,使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象.

当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为StringHttpMessageConverte 可以使用

在 ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage) 的处理中,调⽤⽗类的write⽅法

由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法,所以会执⾏⼦类的⽅法

然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此 时t为Result类型,所以出现类型不匹配"Resultcannotbecasttojava.lang.String"的异常

所以我们的返回结果如果为 String 类型,使用SpringBoot 内置提供的 Jackson 来实现信息的序列化

优点

1.方便前端程序员更好的接收和解析后端数据接口返回的数据

2.降低前端和后端的沟通成本,按照某个格式实现就可以了,所有的接口都是这样返回的

3.有利于项目统一数据的维护和修改

统一异常处理

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的

@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当前出现异常的时候执行某个通知

比如:

根据异常类型返回不同的信息

http://www.dtcms.com/a/541799.html

相关文章:

  • 把握智能语音风口:云蝠智能【声・纪元】VoiceAgent 实时语音智能论坛邀您同行
  • 一文吃透二叉树、完全平衡树、红黑树原理及C语言实现
  • 做网站用别人的图片沈阳设计公司排名
  • 浙江自己如何做网站wordpress 做后台
  • 网站 模板下载陕西富通建设有限公司网站
  • 淄博高效网站建设免费网站建站模板
  • Bootstrap4 Jumbotron详解与使用指南
  • IoT技术在产线实践中的应用
  • 合格VR大空间企业:核心要素有哪些?
  • 06.OpenStack网络管理
  • C++学习记录(23)智能指针
  • 网站内容策划方案wordpress底部版权信息修改
  • python 在class中几种函数的定义和用法
  • 电商数据中台基石:通过 API 构建淘宝商品实时数据源
  • 川崎机器人焊接电源气体省气
  • 理想汽车基于 Hologres + Flink 构建万亿级车联网信号实时分析平台
  • php教育视频网站开发如何做古诗词网站
  • 自发购卡网站在吗做手机建立网站软件
  • Git Tag 理解和使用
  • 如何写一个WebRTC ACE音频应用处理模块
  • 当机器拥有感觉:从电子皮肤到视频神经系统的具身智能革命
  • 快速搭建网站服务器网站推广策划方案
  • 【Linux基础知识系列:第一百六十三篇】创建虚拟网络:Linux网络桥接
  • 东方财经报道|深兰科技落户张江,AI医疗与情感陪伴并进,拓展智能未来版图
  • 跨区域多院区如何破局?浙江三甲医院实现核心医疗系统国产化重构
  • 做网站的怎么挣钱wordpress设计漂亮的页面
  • 【前端】圆角和非圆角实现渐变边框的区别(border)
  • 模板网站免费淘宝网页版官网
  • 苏州建设工程招标在哪个网站电子商务网站建设需要什么
  • 网站建设丿金手指花总9志愿北京网站注册