第二章 日志模块 适配器模式

作者: Xcdf | 来源:发表于2019-01-19 21:21 被阅读59次

    简书 许乐
    转载请注明原创出处,谢谢!

      在Java开发中常用的日志框架有Log4j,Log4j2,Apache Commons Log,java.util.logging,slf4j等,这些工具对外接口不尽相同。为了统一这些工具的接口,Mybatis 定义了一套统一的日志接口供上层使用,并分别为上述常用的日志框架提供了相应的适配器。
      适配器模式的主要目的是解决由于接口不能兼容而导致类无法使用的问题,适配器模式会将需要适配的类转换成调用者能够使用的目标接口。
       目标接口(Target): 调用者能够直接使用的接口。需要适配的类(Adaptee):一般情况下,Adaptee类中有真正的业务逻辑,但是其接口不能被调用者直接使用。适配器(Adapter): Adapter 实现了Target接口,并且包装了一个Adaptee对象。Aapter在实现Target接口中的方法时,会将调用委托给Adpatee对象的相关方法,由Adaptee完成具体的业务。
    下面是适配器的类图:

    适配器类图

      在Mybatis的日志模块中就是使用了适配器模式。Mybatis内部在使用日志模块时,使用了其内部接口 org.apache.ibatis.logging.Log,但是常用的日志框架的对外接口各不相同,Mybatis为了复用和集成这些第三方日志组件,在其日志模块中,提供了多种Adapter,将这些第三方日志组件对外接口适配成org.apache.ibatis.logging.Log,这样Myabtis 就可以通过Log接口调用第三方日志了。

    Log 接口(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 提供了多种常用的日志框架的适配器,以下为Log4j的适配器:

    public class Log4jImpl implements Log {
      private final Logger log;  //持有一个org.apache.log4j.Logger的引用
      public Log4jImpl(String clazz) {
        log = Logger.getLogger(clazz);//初始化org.apache.log4j.Logger对象
      }
      //以下方法将请求全部委托给org.apache.log4j.Logger对象
      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }
      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }
     //.... 其他级别的日志输出与上述isDebugEnabled和isTraceEnabled类似  
    }
    

    LogFactory 工厂类负责创建对应的日志组件适配器

    package org.apache.ibatis.logging;
    
    import java.lang.reflect.Constructor;
    
    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() {
            useNoLogging();
          }
        });
      }
    private static void tryImplementation(Runnable runnable) {
       if(logConstructor == null) {
           try {
             runnable.run();
           } catch (Throwable t) {
            // ignore
           }
       }
    }
    public static synchronized void useLog4JLogging() {
     // **注意这里的名称为Log4jImpl.class 初始化了Log4j的适配器
     setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
    }
    //给logConstructor 赋值
    private static void setImplementation(Class<? extends Log> implClass) {
       //... 异常处理代码
       Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
       // ... 省略打日志代码
       logConstructor = candidate;
      }
    }
    //==============================
    //供上层应用调用
    public static Log getLog(Class aClass) {
        return getLog(aClass.getName());
    }
    public static Log getLog(String logger) {
        //...省略异常处理代码
        return logConstructor.newInstance(logger);
    }
    }
    

    疑问:
    1.在静态代码块static{try...}中初始化,为什么传入Runnable对象 ?
    2.静态方法"public static synchronized void useLog4JLogging() {"在初始化日志组件时,为什么要加锁?
    3.测试加锁的正确性:先将synchronized 去掉,编译打包后,开启多个线程写日志测试正确性。
    4.与SLF4J的区别? 是否可以移植logging模块到其他项目中,实现可选的日志主件?

    相关文章

      网友评论

        本文标题:第二章 日志模块 适配器模式

        本文链接:https://www.haomeiwen.com/subject/umttjqtx.html