ORM框架之Mybatis(六):logging源码实现分析

mybatis框架源码的实现相对spring来说要简单的很多,模块的分工也很明确,每个模块的代码量也不是很大,比较容易阅读,如果你对设计模式很了解的话。里面用到很多设计模式,如工厂模式、代理模式、装饰器模式、适配器模式等等,都是值得平时开发学习和借鉴的,都说高手的代码都是向高级框架靠拢,谁知道他是自己设计还是看了源码学习的呢,对不?

mybatis的模块比较多,如对外的用SqlSession、内部底层日志logging、数据源模块、反射等,这一篇来看mybatis源码中最简单的部分logging日志。这个模块主要就是用工厂模式生成适配不同logging组件的日志类。然后就是适配器模式,适配不同的logging组件,并支持无侵入的logging组件扩展。

logging整体了解

想了解细节,先了解整体,会更好。看图:

image

市场上日志组件有很多,各个公司采用的日志组件不尽相同,但是可能都要使用到mybatis,如果mybatis只能支持一种日志的话,那么就会出现其他日志组件无法打印日志的问题。

此时使用适配器模式,不论其他的组件是什么样的,统一使用适配类去对这些组件进行封装,适配类都实现Log接口,将Log暴露给mybatis调用方,调用方直接调用log接口内的方法即可。不用管底层到底是适配哪个日志组件。另外每种日志组件的日志级别分类都是有所差别的,做了统一封装,就不用考虑日志级别变化的问题。

这种设计拓展性很好,如果需要添加日志组件,只要写一个适配类去封装此日志组件,然后在工厂类中添加日志适配类加载的代码就可以,上层的业务代码是无需任何修改,是无感的。

看一下源码

Log接口类

Log接口类的代码:

public interface Log {
  boolean isDebugEnabled();//是否可以debug日志
  boolean isTraceEnabled();//是否可以跟进日志
  //错误
  void error(String s, Throwable e);
  //错误
  void error(String s);
  //调试
  void debug(String s);
  //跟进
  void trace(String s);
  //警告
  void warn(String s);
}

这里日志接口很简单,就四种日志级别。如果多log4j、commons-logging这些日志组件有所了解的话,可以知道他们的日志级别都是有所不同。因此这里就必须要是做适配了,适配器就上场喽。(slf4j为例)

Slf4jImpl实现类
public class Slf4jImpl implements Log {

  private Log log;
  // 构造函数(很重要)
  public Slf4jImpl(String clazz) {
    // 获取logger实例,这是对应到组件的logger
    Logger logger = LoggerFactory.getLogger(clazz);

    if (logger instanceof LocationAwareLogger) {
      try {
        // check for slf4j >= 1.6 method signature
        logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
        // 创建适配对象
        log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
        return;
      } catch (SecurityException e) {
        // fail-back to Slf4jLoggerImpl
      } catch (NoSuchMethodException e) {
        // fail-back to Slf4jLoggerImpl
      }
    }

    // Logger is not LocationAwareLogger or slf4j version < 1.6
    log = new Slf4jLoggerImpl(logger);
  }

  @Override
  public boolean isDebugEnabled() {
    return log.isDebugEnabled();
  }
  @Override
  public boolean isTraceEnabled() {
    return log.isTraceEnabled();
  }
  @Override
  public void error(String s, Throwable e) {
    log.error(s, e);
  }
  @Override
  public void error(String s) {
    log.error(s);
  }
  @Override
  public void debug(String s) {
    log.debug(s);
  }
  @Override
  public void trace(String s) {
    log.trace(s);
  }
  @Override
  public void warn(String s) {
    log.warn(s);
  }
}

这里主要看的就是构造函数,看构造函数是如何创建适配器对象的。先根据实际日志组件类创建logger实例,然后根据对logger实例进行包装,得到一个适配后的log实例,也就是对应日志组件的log适配器对象。Slf4jLocationAwareLoggerImpl内的代码就不跟进去看了,里面很简单,就是将传入的logger赋值给成员变量。然后在四种日志级别的方法里面都使用这个logger对象进行日志打印。

LogFactory工厂类

工厂类做了两件事情,第一步在静态代码块中依次扫描日志实现,然后根据优先级加载日志实现类的构造器,只会加载一个,谁在前加载谁。第二步提供getLog方法,通过此方法中调用构造器的newInstance方法构造正式的日志对象和日志适配对象。

//加载日志构造器的静态代码块
//根据顺序依次执行日志类的加载,有加载顺序和优先级
//顺序(优先级):slf4j->commons-logging->log4j-logging->log4j->jdk->no-loging
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();
    }
  });
}

详细如代码,但是这里要知道的是,加载的日志构造器并不是对应日志组件的日志类构造器,而是适配器的构造器。可以看一下上面适配器的源码构造方法。

//获取日志对象,调用getLog(String logger)方法
public static Log getLog(Class<?> aClass) {
  return getLog(aClass.getName());
}

//获取日志对象
public static Log getLog(String logger) {
  try {
    return logConstructor.newInstance(logger);
  } catch (Throwable t) {
    throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
  }
}

创建日志实例的方法如上,很简单,就是newInstance方法的调用。

Overview

日志组件适配的整个过程不难,代码也很简洁,过程也很清晰,然后现在通过下面的图总结一下执行的流程。

mybatis日志源码-加载

日志工厂类加载日志适配器类过程。

mybatis日志源码-创建

通过gotLog方法触发创建日志适配器类对象和日志组件对象。

微信公众号

本文作者:IT-CRUD
原文地址:http://blog.itcrud.com/blogs/2018/09/orm-mybatis-source-logging
版权归作者所有,转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容