文章目录

  • 1.源码分析概述
    • ①.Mybatis架构分析
    • ②.门面模式
    • ③.设计模式的原则
  • 2.日志模块分析
    • ①.适配器模型
    • ②.动态代理
    • ③.日志模块分析
  • 3.数据源模块分析
    • ①.工厂模式
    • ②.数据源模块分析
    • ③.数据库连接池源码分析
  • 4.缓存模块分析
    • ①.装饰器模式
    • ②.缓存模块分析
  • 5.反射模块分析
    • ①.反射过程分析
    • ②.反射的核心类

1.源码分析概述

MyBatis 源码下载地址:https://github.com/MyBatis/MyBatis-3

①.Mybatis架构分析

MyBatis整体架构: MyBatis源码共16个模块,可以分成三层,如下图 :

基础支撑层:技术组件专注于底层技术实现,通用性较强无业务含义;

核心处理层:业务组件专注MyBatis的业务流程实现,依赖于基础支撑层;

接口层:MyBatis对外提供的访问接口,面向SqlSession编程;

②.门面模式

​ 从源码的架构分析,特别是接口层的设计,可以看出来MyBatis的整体架构符合门面模式的。

门面模式定义:提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。

​ 门面模式优点:使复杂子系统的接口变的简单可用,减少了客户端对子系统的依赖,达到了解耦的效果;遵循了OO原则中的迪米特法则,对内封装具体细节,对外只暴露必要的接口。

门面模式使用场景:

  • 一个复杂的模块或子系统提供一个供外界访问的接口

  • 子系统相对独立 ― 外界对子系统的访问只要黑箱操作即可

③.设计模式的原则

  1. 单一职责原则:一个类或者一个接口只负责唯一项职责,尽量设计出功能单一的接口;

  2. 依赖倒转原则:高层模块不应该依赖低层模块具体实现,解耦高层与低层。既面向接口编程,当实现发生变化时,只需提供新的实现类,不需要修改高层模块代码;

  3. 开放-封闭原则:程序对外扩展开放,对修改关闭;换句话说,当需求发生变化时,我们可以通过添加新模块来满足新需求,而不是通过修改原来的实现代码来满足新需求;

  4. 迪米特法则:一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合度;实现这个原则,要注意两个点,一方面在做类结构设计的时候尽量降低成员的访问权限,能用private的尽量用private;另外在类之间,如果没有必要直接调用,就不要有依赖关系;这个法则强调的还是类之间的松耦合;

  5. 里氏代换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象;

  6. 接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上;

2.日志模块分析

  1. MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,而MyBatis统一提供了trace、debug、warn、error四个级别;

  2. 自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

    ​ 在org.apache.ibatis.logging.LogFactory中的静态代码块中,通过静态代码块确保第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

    static{tryImplementation(LogFactory::useSlfjLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);
    }
    
  3. 日志的使用要优雅的嵌入到主体功能中;

①.适配器模型

日志模块的第一个需求是一个典型的使用适配器模式的场景,适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组件的时候经常被使用到;注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构;

MyBatis日志模块是怎么使用适配器模式?实现如下:

  • Target:目标角色,期待得到的接口。org.apache.ibatis.logging.Log接口,对内提供了统一的日志接口;

  • Adaptee:适配者角色,被适配的接口。其他日志组件组件如slf4J 、commonsLoging 、Log4J2等被包含在适配器中。

  • Adapter:适配器角色将源接口转换成目标接口。针对每个日志组件都提供了适配器,每个适配器都对特定的日志组件进行封装和转换;如 Slf4jLoggerImpl 、 JakartaCommonsLoggingImpl 等;

日志模块适配器结构类图:

  总结:日志模块实现采用适配器模式,日志组件(Target)、适配器以及统一接口(Log接口)定义清晰明确符合单一职责原则;同时,客户端在使用日志时,面向Log接口编程,不需要关心底层日志模块的实现,符合依赖倒转原则;最为重要的是,如果需要加入其他第三方日志框架,只需要扩展新的模块满足新需求,而不需要修改原有代码,这又符合了开闭原则;

②.动态代理

代理模式定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用;目的:(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;(2)通过代理对象对原有的业务增强;

代理模式有静态代理和动态代理两种实现方式。

静态代理这种代理方式需要代理对象和目标对象实现一样的接口。

优点:

可以在不修改目标对象的前提下扩展目标对象的功能。

缺点:

冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

静态代理代码示例

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。静态代理与动态代理的区别主要在:

  1. 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件

  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。

注意:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

 动态代理代码示例

JDK中生成代理对象主要涉及两个类,第一个类为java.lang.reflect.Proxy,通过静态方法newProxyInstance生成代理对象,第二个为java.lang.reflect.InvocationHandler接口,通过invoke方法对业务进行增强;

③.日志模块分析

如何优雅的增强日志功能?

首先搞清楚那些地方需要打印日志?通过对日志的观察,如下几个位置需要打日志:

  1. 在创建prepareStatement时,打印执行的SQL语句;

  2. 访问数据库时,打印参数的类型和值;

  3. 查询出结构后,打印结果数据条数。

因此在日志模块中有BaseJdbcLogger、ConnectionLogger、PreparedStatementLogger和ResultSetLogge通过动态代理负责在不同的位置打印日志;几个相关类的类图如下:

ConnectionLogger:负责打印连接信息和SQL语句,并创建PreparedStatementLogger;
PreparedStatementLogger:负责打印参数信息,并创建ResultSetLogger;
ResultSetLogge:r负责打印数据结果信息;

  • BaseJdbcLogger:所有日志增强的抽象基类,用于记录JDBC那些方法需要增强,保存运行期间sql参数信息;

  • ConnectionLogger:负责打印连接信息和SQL语句。通过动态代理,对connection进行增强,如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句并返回prepareStatement的代理对象(PreparedStatementLogger),让prepareStatement也具备日志能力,打印参数;

  • PreparedStatementLogger:对prepareStatement对象增强,增强的点如下:

    ①增强PreparedStatement的setxxx方法将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备;

    ②增强PreparedStatement的execute相关方法,当方法执行时,通过动态代理打印参数,返回动态代理能力的resultSet;

    ③如果是查询,增强PreparedStatement的getResultSet方法,返回动态代理能力的resultSet;如果是更新,直接打印影响的行数

  • ResultSetLogger:负责打印数据结果信息;

3.数据源模块分析

常见的数据源组件都实现了javax.sql.DataSource接口;
MyBatis不但要能集成第三方的数据源组件,自身也提供了数据源的实现;
一般情况下,数据源的初始化过程参数较多,比较复杂;

①.工厂模式

工厂模式(Factory Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

产品接口(Product):产品接口用于定义产品类的功能,具体工厂类产生的所有产品都必须实现这个接口。调用者与产品接口直接交互,这是调用者最关心的接口;
具体产品类(ConcreteProduct):实现产品接口的实现类,具体产品类中定义了具体的业务逻辑;
工厂接口(Factory):工厂接口是工厂方法模式的核心接口,调用者会直接和工厂接口交互用于获取具体的产品实现类;
具体工厂类(ConcreteFactory):是工厂接口的实现类,用于实例化产品对象,不同的具体工厂类会根据需求实例化不同的产品实现类;

②.数据源模块分析


为什么要使用工厂模式?

使用new关键字直接创建对象
优点
通过反射机制创建对象;
通过工厂类创建对象;
缺点
对象创建和对象使用的职责耦合在一起,违反单一原则;
当业务扩展时,必须修改代业务代码,违反了开闭原则;

使用工厂模式创建对象
优点
把对象的创建和使用的过程分开,对象创建和对象使用使用的职责解耦;
如果创建对象的过程很复杂,创建过程统一到工厂里管理,既减少了重复代码,也方
便以后对创建过程的修改维护;
当业务扩展时,只需要增加工厂子类,符合开闭原则;

③.数据库连接池源码分析

PooledDataSource:一个简单,同步的、线程安全的数据库连接池;
PooledConnection:使用动态代理封装了真正的数据库连接对象;
PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别 管理空闲状态的连接资源和活跃状态的连接资源。

请详细描述从数据库连接池中获取一个连接资源的过程?

获取连接:getConnection()

归还连接:pushConnection()

4.缓存模块分析

Ø MyBatis缓存的实现是基于Map的,从缓存里面读写数据是缓存模块的核心基础功能;
Ø 除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(fifo、lru)、序列化功
能、日志能力、定时清空能力等;
Ø 附加功能可以以任意的组合附加到核心基础功能之上;

怎么样优雅的为核心功能添加多种附加能力?
使用动态代理或继承的办法扩展多种附加功能?

优化思路:装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀;

①.装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀;

- 组件(Component):组件接口定义了全部组件类和装饰器实现的行为;

- 组件实现类(ConcreteComponent):实现Component接口,组件实现类就是被装饰器装饰的原始对象,新功能或者附加功能都是通过装饰器添加到该类的对象上的;- 装饰器抽象类(Decorator):实现Component接口的抽象类,在其中封装了一个Component 对象,也就是被装饰的对象;- 具体装饰器类(ConcreteDecorator):该实现类要向被装饰的对象添加某些功能;


优点:相对于继承,装饰器模式灵活性更强,扩展性更强;
灵活性:装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的添加功能,甚至对添加的新功能进行自由的组合;
扩展性:当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式添加这个新装饰器,无需修改已有代码,符合开闭原则;

​ 装饰器模式使用举例:

​ - IO中输入流和输出流的设计

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("c://a.txt")));

​ -对网络爬虫的自定义增强,可增强的功能包括:多线程能力、缓存、自动生成报表、黑白名单、random触发等。

②.缓存模块分析

MyBatis缓存组件:


Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作;
PerpetualCache:在缓存模块中扮演ConcreteComponent角色,使用HashMap来实现cache的相关操作;
BlockingCache:阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据;
缓存装饰器解读:
- FifoCache:先进先出缓存淘汰策略的缓存;
- LoggingCache:日志能力的缓存;
- ScheduledCache:定时清空的缓存;
- BlockingCache:阻塞式缓存;
- SerializedCache:序列化能力的缓存;
- SynchronizedCache:进行同步控制的缓存;

Mybatis的缓存功能使用HashMap实现会不会出现并发安全的问题?

CacheKey解读:MyBatis中涉及到动态SQL的原因,缓存项的key不能仅仅通过一个String来表示,所以通过CacheKey来封装缓存的Key值,CacheKey可以封装多个影响缓存项的因素;判断两个CacheKey是否相同关键是比较两个对象的hash值是否一致。

5.反射模块分析

orm框架查询数据过程:

①.反射过程分析

②.反射的核心类

  • ObjectFactory:MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO;
  • ReflectorFactory:创建Reflector的工厂类,Reflector是MyBatis反射模块的基础,每个Reflector对象都对应一个类,在其中缓存了反射操作所需要的类元信息;
  • ObjectWrapper:对对象的包装,抽象了对象的属性信息,他定义了一系列查询对象属性信息的方法,以及更新属性的方法;
  • ObjectWrapperFactory: ObjectWrapper 的工厂类,用于创建ObjectWrapper ;
  • MetaObject:封装了对象元信息,包装了MyBatis中五个核心的反射类。也是提供给外部使用的反射工具类,可以利用它可以读取或者修改对象的属性信息。

mybatis核心流程三大阶段:

为什么使用mapper接口就能对数据库进行访问?

为什么在spring容器中,并没有出现sqlSession的身影?

学习源码的步骤:

  1. 精心挑选要阅读的源码项目;
  2. 饮水思源——官方文档,先看文档再看源码;
  3. 下载源码,安装到本地,保证能编译运行;
  4. 从宏观到微观,从整体到细节;
  5. 找到入口,抓主放次,梳理核心流程;
  6. 源码调试,找到核心数据结构和关键类;
  7. 勤练习,多折腾;

论学习源码的目的 请大牛现身说法:

  1. 编写优雅、高效的代码经验;
  2. 提升微观的架构设计能力,重点在思维和理念;
  3. 解决工作中、学习中的各种疑难杂症;

MyBatis源码学习笔记(从设计模式看源码)相关推荐

  1. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  2. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  3. Android学习笔记-常用的一些源码,防止忘记了

    Android学习笔记-常用的一些源码,防止忘记了... 设置拨打电话 StringdialUri="tell:"+m_currentTelNumble; IntentcallIn ...

  4. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  5. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  6. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  7. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

  8. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  9. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  10. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

最新文章

  1. 想要准备阿里/百度/腾讯/美团的面试?了解一下
  2. Windows SDK程序的输出文字和格式控制(wsprintf、swprintf、Textout)
  3. (Oracle学习笔记) sql语言
  4. MyBitis(iBitis)系列随笔之二:类型别名(typeAliases)与表-对象映射(ORM)
  5. Java黑皮书课后题第6章:**6.27(反素数)反素数(反转拼写的素数)是指一个非回文素数,将其反转之后也是一个素数。编写程序,显示前100个反素数,每行显示10个,并且数字间用空格隔开
  6. 复制的时候提示下标越界_移动硬盘打不开提示格式化怎么办?
  7. 微波感应模块电路图_关于人体感应灯,你不知道的“冷”知识
  8. linux查看usb文件,linux下查看usb个数
  9. C/C++面试题总结
  10. c语言入门介绍 Hello, World
  11. 扫盲贴-万能密码的原理
  12. 使用SecOC打造的CAN网络依旧很不安全
  13. Adobe Dreamweaver CS6快捷键使用
  14. 【MySQL学习】数据库问题及着重点汇总
  15. 如何在不清空原有配置的情况下修改路由器密码??????
  16. 《假如给我三天光明》读后感及其摘录(2)
  17. 联合国儿童基金会宣布与微软达成新合作
  18. nnet3-chain-copy-egs用于chain模型输入数据
  19. 全国统筹明年启动,养老金发放将迎来哪些变化?
  20. java语音、视频、其他文件下载

热门文章

  1. 【清华大学】操作系统 陈渝——Part6 全局页面置换算法
  2. WebGIS期中复习
  3. python1到100奇数相加_c# 计算1-100之间的所有奇数的和
  4. 关于排序算法,看这一篇就够了!这篇看不懂麻烦找我拿红包
  5. GD32F405RGT6IIC主机模式(简单配置)
  6. python实时语音智能聊天<讯飞语音识别+青云客机器人>
  7. 鸿蒙测试版流畅度,华为鸿蒙系统开始公测,流畅度媲美EMUI11,4款手机优先体验...
  8. VHDL——4选1多路选择器
  9. appium启动报错The instrumentation process cannot be initialized. Make sure the application under test do
  10. 北京大学计算机考博英语,《北京大学2016考博英语原版试题(清晰版)》.pdf