简述

在Java开发中常用的日志框架有Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j等,这些工具对外的接口并不相同。为了统一这些工具的接口,MyBatis定义了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器。

适配器模式

首先,我们简单介绍设计模式中有六大原则。

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

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

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

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

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

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

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

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

  • 目标接口(Target):调用者能够直接使用的接口。
  • 源(Adaptee)角色:需要适配的接口
  • 适配器(Adapter)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。

使用适配器模式的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完成组件的复用。很明显,适配器模式通过提供Adapter的方式完成接口适配,实现了程序复用Adaptee的需求,避免了修改Adaptee实现接口,这符合“开放-封闭”原则。当有新的Adaptee需要被复用时,只要添加新的Adapter即可,这也是符合“开放-封闭”原则的。

在MyBatis的日志模块中,就使用了适配器模式。Log4j、Log4j2等第三方日志组件对外提供的接口各不相同,MyBatis为了集成和复用这些第三方日志组件,在其日志模块中提供了多种Adapter,将这些第三方日志组件对外的接口适配成了org.apache.ibatis.logging.Log接口,这样MyBatis内部就可以统一通过org.apache.ibatis.logging.Log接口调用第三方日志组件的功能了。

日志适配器

前面提到的多种第三方日志组件都有各自的Log级别,且都有所不同,例如java.util.logging提供了All、FINEST、FINER、FINE、CONFIG、INFO、WARNING等9种级别,而Log4j2则只有trace、debug、info、warn、error、fatal这6种日志级别。MyBatis统一提供了trace、debug、warn、error四个级别,这基本与主流日志框架的日志级别类似,可以满足绝大多数场景的日志需求。 MyBatis的日志模块位于org.apache.ibatis.logging包中,该模块中通过Log接口定义了日志模块的功能,当然日志适配器也会实现此接口。LogFactory工厂类负责创建对应的日志组件适配器

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

    /**
* 记录当前使用的第三方日志组件所对应的适配器的构造方法
*/
private static Constructor<? extends Log> logConstructor; /**
* 调用方法tryImplementation顺序加载每种组件
*/
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()方法

private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}

每种日志组件的加载都是调用setImplementation方法,这里以Slf4j为例,如下:

    public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.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 = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}

Mybatis源码学习之日志(五)的更多相关文章

  1. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  2. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  3. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  4. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  5. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  6. Mybatis源码学习第八天(总结)

    源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...

  7. Mybatis源码学习之整体架构(一)

    简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...

  8. MyBatis源码解析之日志记录

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

  9. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

随机推荐

  1. Angular7如何动态刷新Echarts图表

    1 概述 echarts是百度的开源图表插件 Angular中引入echarts网上教程很多 Angular引入echarts,并使用动态刷新 2 安装 请参考大神的博客:https://blog.c ...

  2. sql server的循环语句

    WITH TEST_CTEAS(SELECT id,position,Parentid,Cast(Parentid AS NVARCHAR(4000)) AS PATHFROM op_client_s ...

  3. 微信小程序修改radio和checkbox的默认样式和图标

    wxml: <view class="body"> <view class="body-content"> 第1题:企业的价值观是 ? ...

  4. 买条Vineyard Vines裙子为啥子那么难?因为能遮胖?因为英国王子穿过?

    为了这件vineyard vines, 我周六冒雨,去斯坦福shopping center说卖完了,我冒雨赶回家,上网买到了,今天早上发email说没货了,自动取消我的订单.我下午又打了40分钟电话给 ...

  5. 如何使用classnames模块库为react动态添加class类样式

    摘要 在react中添加动态的css时,传统的方式较为繁琐,今天刚好学习到一个模块库可以便捷的解决这个问题.对的,它就是“classnames”. classnames模块库 npm安装 npm in ...

  6. select input 等控件进行清空操作

    <html> <head> <meta charset="utf-8" /> <title></title> <s ...

  7. 8.JSP与JavaBean

    1.<jsp:useBean> <html> <head> <title>jsp:useBean 标签的使用</title> </he ...

  8. vscode 踩坑汇总

    gopls 提示 update 将 "go.useLanguageServer": true 改为 "go.useLanguageServer": false

  9. 前端基础(一):HTML内容

    HTML介绍 Web服务本质 import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen(5 ...

  10. 对于写Python学习笔记的看法

    学习写笔记是一个不错的学习方法,好些同学在学习Python过程中也会写学习笔记.俗话说好记性不如烂笔头,我很赞同这个说法. 我列举几个学习Python写笔记的好处: 1.Python知识的二度巩固 通 ...