mybatis原始碼解析-日誌介面卡

全me村的希望發表於2020-06-08

  1.為什麼需要使用介面卡?
      整合第三方日誌元件,遮蔽日誌元件底層實現,統一提供寫日誌的介面。

  2.什麼是介面卡模式
   定義:將一個類的介面變成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠一起工作。

  

  client-->Target(統一介面) Adapter繼承Target,並封裝Adaptee物件 Adaptee類做具體的工作
  Target目標角色:
  該角色定義把其他類轉換為何種介面
  Adaptee源角色:
  你想把誰轉換成目標角色,這個“誰”就是源角色,他是已經存在的角色。
  Adapter 介面卡角色:
  介面卡模式的核心角色,其他兩個角色都是已經存在的角色,而介面卡角色是需要新建立的,它的職責是把源角色轉換為目標角色,實現方式是通過繼承或者組合的方式.

  3.mybatis中日誌適配

         

   從commons包到stdout都可視為介面卡角色,Log介面為目標角色,LogFactory為建立具體日誌元件介面卡的工廠。

  適配關係如下:

       

  

  從原始碼角度看:
  目標角色:Log

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);
}

  

  統一定義了日誌級別。
  介面卡角色,以Jdk14LoggingImpl 為例,

  

package org.apache.ibatis.logging.jdk14;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.ibatis.logging.Log;

/**
 * @author Clinton Begin
 */
public class Jdk14LoggingImpl implements Log {

  private final Logger log;

  public Jdk14LoggingImpl(String clazz) {
    log = Logger.getLogger(clazz);
  }

  @Override
  public boolean isDebugEnabled() {
    return log.isLoggable(Level.FINE);
  }

  @Override
  public boolean isTraceEnabled() {
    return log.isLoggable(Level.FINER);
  }

  @Override
  public void error(String s, Throwable e) {
    log.log(Level.SEVERE, s, e);
  }

  @Override
  public void error(String s) {
    log.log(Level.SEVERE, s);
  }

  @Override
  public void debug(String s) {
    log.log(Level.FINE, s);
  }

  @Override
  public void trace(String s) {
    log.log(Level.FINER, s);
  }

  @Override
  public void warn(String s) {
    log.log(Level.WARNING, s);
  }

}

  

  Jdk14LoggingImpl 通過繼承關係實現Log,組合java.util.logging.Logger物件log; ,介面卡實現繼承的方法的時候通過關聯物件log實現。
  4.mybatis怎麼初始化日誌介面卡

 

package org.apache.ibatis.logging;

import java.lang.reflect.Constructor;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public final class LogFactory {

  /**
   * Marker to be used by logging implementations that support markers
   */
  public static final String MARKER = "MYBATIS";
    //記錄當前使用的第三方日誌元件所對應的介面卡的構造方法
  private static Constructor<? extends Log> logConstructor;

  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();
      }
    });
  }

  private LogFactory() {
    // disable construction
  }

  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);
    }
  }

  public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
    setImplementation(clazz);
  }

  public static synchronized void useSlf4jLogging() {
    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  }

  public static synchronized void useCommonsLogging() {
    setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
  }

  public static synchronized void useLog4JLogging() {
    setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
  }

  public static synchronized void useLog4J2Logging() {
    setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
  }

  public static synchronized void useJdkLogging() {
    setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
  }

  public static synchronized void useStdOutLogging() {
    setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
  }

  public static synchronized void useNoLogging() {
    setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
  }

  private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

  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);
    }
  }

}

 

  上面介面卡工廠我們通過mybatis的官方文件中的日誌模組的使用來進行輔助理解:
  官網說明如下:
  Mybatis 通過使用內建的日誌工廠提供日誌功能。內建日誌工廠將會把日誌工作委託給下面的實現之一:
  SLF4J
  Apache Commons Logging
  Log4j 2
  Log4j
  JDK logging
  MyBatis 內建日誌工廠會基於執行時檢測資訊選擇日誌委託實現。它會(按上面羅列的順序)使用第一個查詢到的實現。當沒有找到這些實現時,將會禁用日誌功能。

  這段話和原始碼中的static靜態程式碼塊相對應。

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();
      }
    });
  }

  

  官網中的說明順序一樣,
  以其中JdkLogging為例說明初始化日誌適配過程:

private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

  首先判斷當前logConstructor日誌介面卡構造方法是否已經初始化,如果初始化則返回,如果沒有初始化則執行run()方法,對應當前示例就是useJdkLogging()方法,

  

public static synchronized void useJdkLogging() {
 交給了setImplementation
    setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
  }
  private static void setImplementation(Class<? extends Log> implClass) {
    try {
        //通過傳入型別獲取介面卡構造方法 如果沒有找到對應型別則返回null
      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);
    }
  }

  

  按上面次序進行日誌介面卡的初始化。

  最後一個useNoLogging代表禁用日誌功能,因為介面卡NoLoggingImpl中什麼都沒有做。

package org.apache.ibatis.logging.nologging;

import org.apache.ibatis.logging.Log;

/**
 * @author Clinton Begin
 */
public class NoLoggingImpl implements Log {

  public NoLoggingImpl(String clazz) {
    // Do Nothing
  }

  @Override
  public boolean isDebugEnabled() {
    return false;
  }

  @Override
  public boolean isTraceEnabled() {
    return false;
  }

  @Override
  public void error(String s, Throwable e) {
    // Do Nothing
  }

  @Override
  public void error(String s) {
    // Do Nothing
  }

  @Override
  public void debug(String s) {
    // Do Nothing
  }

  @Override
  public void trace(String s) {
    // Do Nothing
  }

  @Override
  public void warn(String s) {
    // Do Nothing
  }
}

 

    官網說明第二段
   你也可以呼叫以下任一方法來選擇日誌實現:

org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging()

你應該在呼叫其它 MyBatis 方法之前呼叫以上的某個方法。另外,僅當執行時類路徑中存在該日誌實現時,日誌實現的切換才會生效。
如果你的環境中並不存在 Log4J,你卻試圖呼叫了相應的方法,MyBatis 就會忽略這一切換請求,並將以預設的查詢順序決定使用的日誌實現。


因為上面的方法為LogFactory中的靜態方法,所以可以直接呼叫來初始化介面卡工廠中的介面卡構造方法(logConstructor),從而實現第三方日誌元件的切換,並且該方法執行於static靜態程式碼塊之後。

 

本文參考:mybatis技術內幕,Mybatis官網

 

相關文章