MyBatis 缓存设计思想

缓存本质其实就是一个 Map,MyBatis 的缓存最基本的实现是 PerpetualCache,内部使用了 HashMap 来保存数据

缓存一般还有额外的功能比如淘汰策略,最大时长等,如何扩展这些功能?

扩展类的功能有 2 种,继承和组合,但是 MyBatis 提供了很多功能并且有的是动态组合的,比如容量上限 512,淘汰策略 LRU… 如果是集成的话,那要把所有排列组合都实现一遍,会导致类爆炸

MyBatis 采用了装饰器模式,采用组合的方式扩展新功能

装饰器模式使用的是组合方式,相较于继承这种静态的扩展方式,装饰器模式可以在运行时根据系统状态,动态决定为一个实现类添加哪些扩展功能

  • Component 接口:已有的业务接口,是整个功能的核心抽象,定义了 Decorator 和 ComponentImpl 这些实现类的核心行为
    • JDK 中的 IO 流体系就使用了装饰器模式,其中的 InputStream 接口就扮演了 Component 接口的角色
  • ComponentImpl 实现类:实现了上面介绍的 Component 接口,其中实现了 Component 接口最基础、最核心的功能,也就是被装饰的、原始的基础类
    • JDK IO 流体系之中的 FileInputStream 就扮演了 ComponentImpl 的角色,它实现了读取文件的基本能力,例如,读取单个 byte、读取 byte[] 数组
  • Decorator 抽象类:所有装饰器的父类,实现了 Component 接口,其核心不是提供新的扩展能力,而是封装一个 Component 类型的字段,也就是被装饰的目标对象
    • 这里的被装饰对象可以是ComponentImpl 对象,也可以是 Decorator 实现类的对象
    • 下面DecoratorImpl1 装饰了 DecoratorImpl2,DecoratorImpl2 装饰了 ComponentImpl,经过了这一系列装饰之后得到的 Component 对象,除了具有 ComponentImpl 的基础能力之外,还拥有了 DecoratorImpl1 和 DecoratorImpl2 的扩展能力

MyBatis 中除了 PerpetualCache 之外的其他所有 Cache 接口实现类,都是装饰器实现

Spring 中的 IoC容器,它有各种各样的实现类,完全就是通过继承进行功能的扩展,与 MyBatis 的实现思路截然不同,为什么?

对比MyBatis 的 Cache 继承体系图和 BeanFactory 的继承体系图装饰器模式可以动态、灵活的扩展新的功能,但是它有一个问题,就是客户端的可控程度比较低。

无论缓存扩展了哪些功能,对于客户端来说,暴露的都只是一个 Cache 接口,客户端并不知道此时缓存已经具有了哪些能力,甚至如果客户端对内部实现不了解,自己扩展了两个互斥的包装器,还会引起系统异常的风险。

对于 MyBatis 来说,它内置的缓存功能在绝大部分场景下都是不需要客户度自行开发和扩展的,客户端只需要指定我需要缓存的哪些功能就好。在这个前提,MyBatis 可以放心地使用装饰器模式。

而 Spring 则不同,它提供的 BeanFactory、ApplicationContext 这一整套 IoC 容器,是直接暴露给客户端使用的,有时还会基于已有的容器进行二次开发。这样就需要客户端有较强的把控能力,需要明确的知道我当前使用的是哪种 BeanFactory,它具有哪些能力。

如果像 MyBatis 一样,只对外暴露一个 BeanFactory 接口,那么客户端进行二次开发就十分不友好了。

总结起来,造成 MyBatis 和 Spring 这种扩展思路的差异的最重要原因之一,就是 MyBatis 的 Cache 模块需要降低用户学习成本,直接指定需要增加的功能即可;而 Spring 的 IoC 容器则是为客户端的使用和二次开发提供了更大的空间。


MyBatis 缓存设计思想
http://showyoubug.cn/2024/08/30/MyBatis 缓存设计思想/
作者
Dong Su
发布于
2024年8月30日
许可协议