分析Mybatis源码,并详解其用到的设计模式---日志模块

一、下载Mybatis源码

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

源码包导入过程: 1. 下载 MyBatis 的源码

2. 检查 maven 的版本,必须是 3.25 以上,建议使用 maven 的最新版本

3. MyBatis 的工程是 maven 工程,在开发工具中导入,工程必须使用 jdk1.8 以上版本;

4. 把 MyBatis 源码的 pom 文件中<optional>true</optional>,全部改为 false;

5. 在工程目录下执行 mvn cleaninstall-Dmaven.test.skip=true,将当前工程安装到本地仓库 (pdf 插件报错的话,需要将这个插件屏蔽); 注意:安装过程中会可能会有很多异常信息,只要不中断运行,请耐心等待;

6. 其他工程依赖此工程,可以进行调试,我们也可以对Mybatis源码加注释

二、源码的架构分析

1.利用一个脑图去分析Mybatis源码的模块

2.Mybatis的十六个模块可分为三层,如下图:

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

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

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

3.门面(外观)设计模式提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高级接口,让子系统更容易使用。

Facade角色:提供一个用于客户端访问的对外接口,对内可以访问子系统中的所有功能

SubSystem(子系统):子系统在整个系统中可以是一个或多个模块,每个模块都有若干类组成,这些类可能相互之间有着比较复杂的关系

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

所以从Mybatis源码的架构分析,特别是接口层的设计,可以看出其符合门面模式

门面模式的使用场景:一个复杂的模块或子系统提供一个供外界访问的接口;子系统相对独立--外界对子系统的访问只要黑箱操作即可。

4.面向对象设计中需要遵循的六大设计原则

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

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

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

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

5. 里氏代换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象;
6. 接口隔离原则:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该 建立在最小的接口上;

三、对日志模块的分析

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

以JDK提供的日志组件为例,输出级别为FINE,FINER,SERVRE,WARNING,这与Mybatis规定的Log接口格格不入,如何处理呢?来看一下关于JDK日志组件的一个适配器类。

这就是适配器模式的应用啊!将原本毫无关联的两个类变得能产生调用关系,且没有对原类做任何的改变,只是加了一个中间层,实现了父接口,注入了真正的日志组件功能的实体类,通过在实现父接口的方法中进行对真正实体类的调用。

适配器模式:作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使原本接口不兼容而不能一起工作的那些类一起工作。类图如下:

 Target:目标角色,期待得到的接口

Adaptee:被适配的接口

Adapter:将源接口转换成目标接口

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

总结一下Mybatis日志模块使怎么使用适配器模式的?

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

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

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

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

2.日志模块如何实现优先加载日志组件?

在LogFactory类中的静态代码块中,控制了第三方日志插件加载的优先级。

可以看到tryImplementation方法就是判断当构造器不为空就执行参数的任务(即传过去的那些方法) 

以第一个useSlf4jLogging为例

是将slf4j的适配器对象传给了setImplementation方法,最后通过此方法利用反射进行创建适配器的构造器对象

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

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

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

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

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

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

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

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

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

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

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

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

4)ResultSetLogge:负责打印数据结果信息;

这里用到了代理设计模式:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性

2)通过代理对象对原有的业务增强;

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

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

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

缺点:冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。不易维护,一旦接口增加方法,目标对象与代理对象都要进行修改。

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

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

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

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

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

发布了227 篇原创文章 · 获赞 77 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/m2606707610/article/details/103416453
今日推荐