写在前面,该篇博文为我在部门的技术分享,所以文字记录不是特别详细。本文更像是一个大纲,由浅入深讲解责任链设计模式。
浅谈责任链设计模式在框架源码中的运用
- 一、分享目的
- 二、简单介绍
- 三、逐个拆解
- 四、源码环节
- 1、Tomcat
- 2、Spring MVC
- 3、Spring AOP
- 4、MyBatis
- 5、HSF
- 6、Netty
- 五、简单实战
- 1、日终跑批
- 2、结算
- 3、接收外部数据(http)
- 六、最终总结
一、分享目的
- 认识责任链设计模式
- 看得懂责任链设计模式不同的写法
- 如果有机会,可以灵活运用在项目中
二、简单介绍
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式(A->B这个过程,拆分为若干段,分别写在不同的类)

主体的对象:抽象处理者(xxxContext、xxxChain)包含众多具体处理者(xxxHandler、xxxFilter)
三、逐个拆解
初印象
- 数据来源
- 项目启动阶段加载
- 构建形式:
- 普通list、或者链表
- 拦截角度
- 直接拦截
- 关联细节
- 依次完成逻辑
- 影响面
- 可大可小
四、源码环节
接下来我将通过一个小案例,并且结合上面的五个维度,依次向大家讲述框架作者们是如何实现责任链设计模式的。
项目背景:在一个使用SpringBoot构建的Web项目中,持久层使用MyBatis,Web容器使用Tomcat,RPC框架使用Dubbo/HSF
案例背景:用户前台查看id=1的数据,后台将id传给第三方服务进行查询,然后返回数据给前台,当前项目操作日志需要落库。
1、Tomcat
请求首先来到Tomcat,当前为HTTP请求,先经过传输层逻辑,再跳转到对应的应用层解析类中,最后doGet–>processRequest–>doService–>doDispatch



与之类似的,Tomcat中还有一个org.apache.catalina.Valve接口

补充:
一个是tomcat基于Servlet规范出来的Filter,一个是Tomcat内部自己私有的Filter。我们的多数据源过滤就是使用Servlet的Filter
2、Spring MVC
此时来到DispatcherServlet,该类会完成根据映射路径转发请求,最后进入controller层。下图代码为在转发逻辑前后的拦截器代码
构建形式和Tomcat中Filter形式大体相同

小细节。拦截器执行方法afterCompletion方法执行顺序。(applyPreHandle和afterCompletion更像是一对)

3、Spring AOP
Controller调用Service层方法,如果该方法被@AutoLog注解修饰,可以完成打日志的功能,该对象的构建使用到了AOP。我们都只知道AOP是动态代理,实则还有责任链。



4、MyBatis
调用Mapper接口完成操作日志落库。如果使用MyBatis做为持久层框架,会在预处理我们的SQL前(prepareStatement),根据配置文件里配置的拦截器,完成相关的拦截器功能


MyBatisPlus的乐观锁、Pagehelp、对某些特定表的操作记录操作日志等功能
5、HSF
进入Service层后,业务逻辑RPC调用其他服务接口(在此之前,被调用方接口需要将自己的接口元数据信息序列化后放入三方注册中心),调用方接口根据指定的名字去注册中心找数据。这个元数据里面就会封装相关的拦截器属性,用于调用或者被调用的时候执行。
内部框架,类比Dubbo。
6、Netty
元数据序列化完成,使用Netty进行高效传输到注册中心,并且可以接受对应的数据



五、简单实战
1、日终跑批
- 刷新授信
- 日终批量记账
- 生成日终分录
- 核算余额对账
- 切日
- …
2、结算
- 授信处理
- 核心记账
- 生成分录
- 生成日间分录
- 发送支付
- …
3、接收外部数据(http)
- 处理请求
- 保存日志
- 封装对象
- 业务处理
- 返回
六、最终总结
- 数据来源多:spi直接加载(HSF)、借助Spring加载(业务代码)、根据业务代码选择加载(Spring MVC)、数据库或配置文件
- 构建结构多:普通List(MyBatis)、单向链表(Tomcat ValveBase)、双向链表(HSF、Netty)、递归结构(Spring AOP)
- 拦截角度多:单纯走流程(MyBatis)、可以中途直接返回(Tomcat)、返回了但又没有完全返回(Spring MVC)
- 关联细节多:每一步相互独立(Tomcat Filter)、每一步相互关联(Netty)
- 框架影响多:简单的(MyBatis)、复杂的(Netty)
小思考:我们不乏看到责任链的呈现形式是递归调用的,那么请问我装饰者模式不停的向上装饰自己(MyBatis的Cache),或者说我使用代理模式不断的代理之前的自己有什么区别?