简述
在Java开发中经常使用的日志框架有Log4j、Log4j二、Apache Commons Log、java.util.logging、slf4j等,这些工具对外的接口并不相同。为了统一这些工具的接口,MyBatis定义了一套统一的日志接口供上层使用,并为上述经常使用的日志框架提供了相应的适配器。java
适配器模式
首先,咱们简单介绍设计模式中有六大原则。 apache
单一职责原则: 不要存在多于一个致使类变动的缘由,简单来讲,一个类只负责惟一项职责。 编程
里氏替换原则: 若是对每个类型为T1的对象t1,都有类型为T2的对象t2,使得以T1定义的全部程序P在全部的对象t1都代换成t2时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。遵照里氏替换原则,能够帮助咱们设计出更为合理的继承体系。 设计模式
依赖倒置原则: 系统的高层模块不该该依赖低层模块的具体实现,两者都应该依赖其抽象类或接口,抽象接口不该该依赖具体实现类,而具体实现类应该于依赖抽象。简单来讲,咱们要面向接口编程。当需求发生变化时对外接口不变,只要提供新的实现类便可。 框架
接口隔离原则: 一个类对另外一个类的依赖应该创建在最小的接口上。简单来讲,咱们在设计接口时,不要设计出庞大臃肿的接口,由于实现这种接口时须要实现不少没必要要的方法。咱们要尽可能设计出功能单一的接口,这样也能保证明现类的职责单一。ide
迪米特法则: 一个对象应该对其余对象保持最少的了解。简单来讲,就是要求咱们减低类间耦合。 工具
开放-封闭原则: 程序要对扩展开放,对修改关闭。简单来讲,当需求发生变化时,咱们能够经过添加新的模块知足新需求,而不是经过修改原来的实现代码来知足新需求。 spa
在这六条原则中,开放-封闭原则是最基础的原则,也是其余原则以及后文介绍的全部设计模式的最终目标。debug
适配器模式的主要目的是解决因为接口不能兼容而致使类没法使用的问题,适配器模式会将须要适配的类转换成调用者可以使用的目标接口。这里先介绍适配器模式中涉及的几个角色:设计
- 目标接口(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); } }