MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇文章介绍MyBatis的日志模块

在 Java 开 发 中常用的日志框 架有 Log4jLog4j2Apache Commons Logjava.util.loggingslf4j等,这 些工具对 外的接口不尽 相同。

为 了统 一这 些工具的接口,MyBatis定义 了一套统 一 的日志接口供上层 使用,并 为 上述常用的日志框 架提供了相应 的适配器。

适配器模式

先简单介绍一下设计模式中的六大原则

  • 单一职责原则

    不要存在多于一个导致类变更的原因,简单来说,一个类只负责唯一 项职责。

  • 里氏替换原则

    如果对 每一个 类 型为 T1的对 象tl,都有类 型为 T2的对 象使得以 T1定义 的所有程序P在所有的对 象tl都代换 成t2时 ,程序P 的行为 没 有发 生变 化, 那么 类 型T2是类 型T1的子类 型。遵守里氏替换 原则 ,可以帮 助我们 设 计 出更为 合 理的继 承体 系。

  • 依赖倒置原则

    系统的高层模块不应该依赖低层模块的具体实现,二者都应该依赖其 抽象类 或接口,抽象接口不应 该 依赖 具体 实 现 类 ,而具体 实 现 类 应 该 于依赖 抽象。简 单 来 说 ,我们 要面向接口编 程。当 需求发 生变 化时 对 外接口不变 ,只要提供新的实 现 类 即 可。

  • 接口隔离原则

    一个类对另一个类的依赖应该建立在最小的接口上。简单来说,我们 在设 计 接口时 ,不要设 计 出庞 大臃 肿 的接口,因为 实 现 这 种 接口时 需要实 现 很 多不必 要的方法。我们 要尽 量设 计 出功能单 一的接口,这 样 也能保证 实 现 类 的职 责 单 一。

  • 迪米特法则

    一个对象应该对其他对象保持最少的了解。简单来说,就是要求我们减 低类 间 耦 合。

  • 开放-封闭原则

    程序要对扩展开放,对修改关闭。简单来说,当需求发生变化时,我 们 可以通过 添加新的模块 满 足新需求,而不是通过 修改原来 的实 现 代码 来 满 足新需求。

在这 六条 原则 中,开 放-封闭 原则 是最基础 的原则 ,也是其他原则 以及后文介绍 的所有设 计 模式的最终 目标 。

适配器模式的主要目的是解决 由于接口不能兼容而导 致类 无 法使用的问 题 ,适配器模式会 将 需要适配的类 转 换 成调 用者能够 使用的目标 接口。这 里先介绍 适配器模式中涉及的几 个 角色,如下所述。

  • 目标接口

    用者能够 直接使用的接口。

  • 需要适配的类(Adaptee)

    —般情况下,Adaptee类中有真正的业务逻辑,但是其接口 不能被调 用者直接使用。

  • 适配器(Adapter)

    Adapter 实 现 了 Target 接口,并 包装 了一个 Adaptee 对 象。Adapter 在实 现 Target接口中的方法时 ,会 将 调 用委托给 Adaptee对 象的相关 方法,由Adaptee 完成具体 的业 务 。

适配器模式类图:

image-20200608165414581

使用适配器模式的好处 就是复 用现 有组 件。

应 用程序需要复 用现 有的类 ,但接口不能被该 应 用程序兼容,则 无法直接使用。

这 种 场 景下就适合使用适配器模式实 现 接口的适配,从 而完 成组 件的复 用。

很 明显 ,适配器模式通过 提供Adapter的方式完成接口适配,实 现 了程序复 用 Adaptee的需求,避免了修改Adaptee实 现 接口,这 符合“开 放-封闭 ”原则 。

当 有新的Adaptee 需要被复 用时 ,只要添加新的Adapter即 可,这 也是符合“开 放-封闭 ”原则 的。

在 MyBatis的日志模块 中,就使用了适配器模式。

MyBatis内 部调 用其日志模块 时 ,使用 了其内 部接口(也就是后面要介绍 的org.apache.ibatis.loggingLog 接口)。但是 Log4jLog4j2 等第三方日志组 件对 外提供的接口各不相同,MyBatis为 了集成和复 用这 些第三方日志组 件,

在其日志模块 中提供了多种 Adapter, 将 这 些第三方日志组 件对 外的接口适配成了 org.apache.ibatis.logging.Log 接口,这 样 MyBatis 内 部就可以统 一通过 org.apache.ibatis.logging.Log接口调 用第三方日志组 件的功能了。

日志适配器

MyBatis 统 一提供了 tracedebugwarnerror四个 级 别 用于对应上诉的各种第三方日志组件的级别,这 基本与 主流日志框 架的曰志级 别 类 似,可以满 足绝 大多数 场 景的日志 需求。

MyBatis的日志模块 位于org.apache.ibatis.logging包中,该 模块 中通过 Log接口定义 了日志 模块 的功能,当 然日志适配器也会 实 现 此接口。LogFactory工厂 类 负 责 创 建对 应 的日志组 件适 配器。

在LogFactory类 加载 时 会 执 行其静 态 代码 块 ,其逻 辑 是按序加载 并 实 例化对 应 日志组 件的 适配器,然后使用LogFactory. logConstructor这 个 静 态 字段,记 录 当 前使用的第三方日志组 件的 适配器,具体 代码 如下所示。

private static Constructor<? extends Log> logConstructor;

static {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}

LogFactory.tryImplementation()方 法 首 先 会 检 测 logConstructor字 段 , 若 为 空 则 调 用 Runnable.run()方法(注意,不是***start()***方法),如上述代码 所示,其中会 调 用use*Logging()方法。 这 里以useJdkLogging()为 例进 行介绍 ,具体 代码 如下:

public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}

private static void setImplementation(Class<? extends Log> implClass) {
try {
// 获取指定适配器的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
// 初始化logConstructor字段
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}

相应的实现类都在org.apache.ibatis.logging的子包下。且都实 现 了 org.apache.ibatis.logging.Log 接口,并 封装 了 java.util.logging. Logger 对 象 ,org.apache.ibatis.logging.Log 接口 的功能全部通过 调 用 java.util.logging.Logger 对 象 实 现 ,这 与 前面介绍 的适配器模式完全一致。

image-20200608171903948

JDBC调试

在MyBatis的日志模块 中有一个 Jdbc包,它 并 不是将 日志信息通过 JDBC保存到数 据库 中, 而是通过 JDK动 态 代理的方式,将 JDBC操作通过 指定的日志框 架打印出来 。这 个 功能通常在 开 发 阶 段使用,它 可以输 出SQL语 句、用户 传 入的绑 定参 数 、SQL语 句影响 行数 等等信息,对 调 试 程序来 说 是非常重要的。

BaseJdbcLogger是一个 抽象类 ,它 是Jdbc包下其他Logger类 的父类 ,继 承关 系如图所示。

image-20200608172259632

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》