MyBatis 的日志设计--适配器模式 & 工厂模式

​ MyBatis 想要打印日志,时不时想要来句 log.debug() 、log.error() ,需要个打印机 ,可是自己又不想去实现(而且跟着整个项目用同样的打印系统才是王道啊),需要去用别人家的打印机,要用别人家的产品啊,那问题可就来了。

# 问题 1

​ 市面上各家的打印机 slf4j、java.util.logging、log4j 甚至 System.out 都是各种不同的用法,这使用起来就太麻烦了。

# 思路

​ 不管市面上打印机有多少型号,我家 MyBatis 包里的类只用自家的统一接口,我家的类只管 log.debug()、log.error()…

​ 定义好了接口,就需要实现类 impl 来实现这些 debug()、error() 方法了 , 咱假装是自己来实现,其实去调用真正打印机 slf4、log4j 的方法,这样就把别人家的打印机和咱自家的接口关联起来啦。

​ 这就是适配器模式

​ 我家的每个实现类其实就是一个适配器,每个适配器去适配一种打印机。比如 slf4jLoggerImpl 就完成了对 slf4j 打印机的适配,slf4jLoggerImpl .debug() 调用了slf4j.Logger.debug()。

​ 这样市面上每多一种打印机,比如想用 log4j2 了,我就只需要加一种适配器 log4j2LoggerImpl 去适配它就可以了。

# 代码

​ 定义接口

1
2
3
4
5
public interface Log {
  boolean isDebugEnabled();
  void debug(String s);
  ...
}

​ 在实现类里完成适配,比如适配 slf4j 的 Slf4jLoggerImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import org.apache.ibatis.logging.Log;
import org.slf4j.Logger;

class Slf4jLoggerImpl implements Log {
  private final Logger log;
  
  //注意这个地方,入参为 org.slf4j.Logger 
  //说明咱这个适配器要用起来,是需要传入一个真正的 slf4j 家的打印机进来的
  public Slf4jLoggerImpl(Logger logger) {
    log = logger;
  }
  
  //看似外面调用咱 MyBatis 的 Log 的 debug()
  //其实是调用 org.slf4j.Logger 的 debug() 
  @Override
  public void debug(String s) {
    log.debug(s);
  }
}

# 问题 2

​ 适配器咱是做好了,slf4j、log4j、stdout…做了那么多适配器,可是怎么用啊?我真正要打印机的时候,怎么才能知道我该调用哪款适配器啊?难道每次取打印机的时候,都去查一遍咱的系统配了那款打印机(jar) 包么?而且以后多加了1种打印机,我还去每个地方都改一遍,都多加一道判断么?

# 思路

​ 这其实就是获取实例的时候的问题,咱获取实例太累了,不如来个统一的工厂吧,我每次想打印的时候,都去找工厂要一台打印机,你工厂按照我 Log 的接口给我一个实例就是了。我也不管你给的具体实现到底是 slf4j 家的,还是 log4j 的,甚至可能是 NoLoggingImpl 每次调接口都不处理的这种空壳打印机。

​ 这样一来,有了统一的工厂,判断系统用哪种适配器的任务就可以在工厂完成了。

​ 而且以后就算新加了打印机,也只要改改工厂的代码,在工厂里多加一重判断就可以了。

​ 这就是工厂模式

# 代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class LogFactory {
  private static Constructor<? extends Log> logConstructor;
  static {
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useSlf4jLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useCommonsLogging();
      }
    });
  }

  private LogFactory() {
    // disable construction
  }
   public static Log getLog(String logger) {
    try {
      return logConstructor.newInstance(logger);
    } catch (Throwable t) {
    }
  }
}

​ LogFactory 提供了对外的 getLog(String logger) 方法,给需要 logger 的地方提供一个 Log 的实例。

​ 内部的实现依靠 logConstructor 这个构造器通过反射来实例化一个 Log 的 Impl ,也就是之前的适配器,比如 Slf4jLoggerImpl 。

​ 判断采用哪种logConstructor 的任务则在类初始化的时候就执行了,依次尝试了我们的每一款适配器,碰上报错就说明没 jar 包,下一款,全都没有的话,就用 NoLoggingImpl 了。