MyBatis日志模块


MyBatis源码学习系列文章目录



前言

MyBatis定了自己的Log接口以及级别,但是并没有考虑自己去实现,而是使用第三方的日志,但是第三方日志多种多样而且日志级别也不同,MyBatis是如何对接的呢?如果当前环境中存在多个第三方的日志插件,MyBatis又该如何选择才能保证日志正常加载呢?


MyBatis日志实现

首先看一下MyBatis的日志接口

package org.apache.ibatis.logging;

public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwable e);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}

可以看出MyBatis的日志接口提供了四种日志级别:分别为trace、debug、warn、error。而另一个使用比较广泛的日志插件slf4j则提供了trace、debug、info、warn、error五个级别。如果现在需要兼容slf4j,需要怎么办呢?在MyBatis中,通过适配器模式解决个这个问题。
在这里插入图片描述
在适配器模式当中,首先有一个target目标角色,也就是期待得到的结果,在上面案例当中,就是org.apache.ibatis.logging.Log接口。然后就是Adaptee适配者角色,也就是需要被适配的接口,在上面就是org.slf4j.Logger,为了完成适配,还必须存在一个适配器角色,将待适配的接口转换成目标接口,在上面案例中就是将org.slf4j.Logger适配为org.apache.ibatis.logging.Log,这样从使用者角度看只用关注org.apache.ibatis.logging.Log接口,而无需关注org.slf4j.Logger接口。

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

可能有人会问,为啥MyBatis不直接使用slf4j呢?其实作为一款轻量级开源组件,当然不想强制用户使用某一种日志组件了,而是应该兼容常见的日志组件,MyBatis不是仅仅兼容了slf4j,而是多种。
在这里插入图片描述
日志模块采用适配器模式,日志组件、适配器以及统一接口定义清晰明确符合单一职责原则;同时,客户端在使用日志时,面向Log接口编程,不需要关心底层日志模块的实现,符合依赖倒转原则,最为重要的是,如果需要加入其他第三方日志框架,只需要扩展新的模块满足新需求即可,而不需要修改原有代码,这又符合了开闭原则。

优雅加载日志组件

通过适配器模式,我们可以适配所有第三方的日志组件了,但是通常项目中只会包含其中的一个或多个,甚至没有,此时如何加载这些日志呢?也就是该使用上面这些适配器中的哪个作为Log的实现呢?对应的实现类为org.apache.ibatis.logging.LogFactory。在这个类中存在以下的静态块。

static {
	tryImplementation(() -> useSlf4jLogging());
	tryImplementation(() -> useCommonsLogging());
	tryImplementation(() -> useLog4J2Logging());
	tryImplementation(() -> useLog4JLogging());
	tryImplementation(() -> useJdkLogging());
	tryImplementation(() -> useNoLogging());
}

对应的方法为

private static Constructor<? extends Log> logConstructor;

<font face="Consolas" color=#151663 >无非就是执行目标方法
private static void tryImplementation(Runnable runnable) {
	if (logConstructor == null) {
		try {
			runnable.run();
		} catch (Throwable t) {
			// ignore
		}
	}
}

而这些方法都是调用了setImplementation
在这里插入图片描述

private static void setImplementation(Class<? extends Log> implClass) {
	try {
	    1. 获取构造器
		Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
		2. 反射创建适配器对象
		Log log = candidate.newInstance(LogFactory.class.getName());
		if (log.isDebugEnabled()) {
			log.debug("Logging initialized using '" + implClass + "' adapter.");
		}
		3. 设置logConstructor属性  
		logConstructor = candidate;
	} catch (Throwable t) {
		throw new LogException("Error setting Log implementation.  Cause: " + t, t);
	}
}

这些任务无非就是通过适配器构造反射出适配器对象并设置到logConstructor属性当中,如果一切正常,因为logConstructor已经有值了,后续的日志组件都不会再加载了,如果抛出了异常(一般就是被适配的日志组件包不存在),会在tryImplementation捕捉但不处理,logConstructor也不会有值,继续加载下一个适配器,因为其实在静态块中定义了加载日志组件的优先级别为useSlf4jLogging->useCommonsLogging->useLog4J2Logging->useLog4JLogging->useJdkLogging,即使没有任何日志组件,也没有关系,useNoLogging,即不打印日志。
在这里插入图片描述


总结

MyBatis的日志模块使用了适配器模式解决必须兼容多种不同日志组件的目的,同时通过一个静态块优雅的加载日志组件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值