MyBatis源碼閱讀之日誌logging

MyBatis源碼閱讀之日誌logging

本文介紹我的對 logging 包下源碼的理解。java

logging 配置加載

咱們先從日誌的配置加載開始閱讀, MyBatis 的各項配置的加載過程均可以從 XMLConfigBuilder 類中找到,咱們定位到該類下的日誌加載方法 loadCustomLogImplapache

private void loadCustomLogImpl(Properties props) {
    // 從 MyBatis 的 TypeAliasRegistry 中查找 logImpl 鍵所對應值的類對象
    // 這裏 logImpl 對應的 value 值能夠從 org.apache.ibatis.session.Configuration 的構造方法中找到
    // 注意 Log 類,這是 MyBatis 內部對日誌對象的抽象
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    // 將查找到的 Class 對象設置到 Configuration 對象中
    configuration.setLogImpl(logImpl);
}

很簡單的一個方法,每行都有註釋,其中 configuration.setLogImpl() 裏面調用了 LogFactory.useCustomLogging(),這出現了新類 LogFactory 類,接下來咱們就來聊聊這個類。設計模式

LogFactory

useCustomLogging()方法

LogFactory 是框架內部獲取 Log 對象的手段,經過它的名字也能看出來。它有以下幾類方法:緩存

// 注意這個類型的方法都是同步方法
public synchronized static useXxxLogging(...);

public static Log getLog(...);

private static tryImplementation(Runnable);

private static setImplementation(Class);

剛剛咱們看到被調用的方法 useCustomLogging() 方法,是用來設置內部使用的日誌框架, MyBatis 自身已經適配了一些常見的日誌框架,如 Slf4jCommons LogLog4j 等等。session

useCustomLogging() 方法內部調用 setImplementation(Class) 方法,此方法代碼以下,功能簡單:多線程

private static void setImplementation(Class<? extends Log> implClass) {
    try {
        // 獲取 Log實現類的構造方法,它只有一個字符串做爲參數
        Constructor<? extends Log> candidate = implClass.getConstructor(String.class);

        // 建立一個 Log 對象,打印 debug 日誌
        Log log = candidate.newInstance(LogFactory.class.getName());
        if (log.isDebugEnabled()) {
            log.debug("Logging initialized using '" + implClass + "' adapter.");
        }

        // ...
        // 把 candidate 對象設置到 LogFactory 的靜態變量 logConstructor,這個靜態變量在 getLog() 方法
        // 中被用到
        logConstructor = candidate;
    } catch (Throwable t) {
        throw new LogException("Error setting Log implementation.  Cause: " + t, t);
    }
}

Log 接口

剛剛咱們接觸到了 Log 這個類,它是一個接口,是 MyBatis 內部使用的日誌對象的抽象,它是爲了兼容市面上各類各樣的日誌框架,使用了適配器模式,經過 Log 接口來鏈接 MyBatis 和其餘日誌框架,經過實現 Log 接口連着 MyBatis 和須要適配的日誌框架。框架

Log 接口代碼以下,先試着發現該接口與其餘常見的日誌接口的區別:ide

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

}

可有發現?Log 接口缺乏了 info 級別的日誌輸出方法,我的猜想應該是 MyBatis 內部不須要 info 級別的日誌輸出,畢竟 Log 接口設計之初就是爲了內部使用,而框架使用者是不會採用 MyBatis 的日誌做爲系統的日誌。注意一點: 實現了 Log 接口的類必須擁有一個參數只有一個字符串的構造方法 ,MyBatis 就是經過這個構造方法建立日誌對象的。函數

MyBatis 適配了許多常見的日誌框架,這裏就單單介紹 Log4jImpl 類,它代碼很是簡單:ui

public class Log4jImpl implements Log {

  private static final String FQCN = Log4jImpl.class.getName();

  // 這裏包裝了 Log4j 框架的日誌對象,從而實現適配
  private final Logger log;

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

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

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

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

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

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

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

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

}

tryImplementation() 方法

LogFactory 類再介紹一下被靜態代碼塊使用的方法 tryImplementation(Runnable)。靜態代碼塊代碼以下:

static {
    // 依次執行以下代碼,當沒有該類會拋 ClassNotFoundException ,而後繼續執行
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
}

這個方法有點迷惑性,由於它使用 Runnable 接口做爲參數,而 useXxxLOgging() 方法又是同步方法,很容易聯想到多線程,實際上這裏並無, Runnable 接口不結合 Thread 類使用它就是一個普通的函數接口。除去這些就沒什麼了,不過是調用了 Runnablerun() 方法而已。

getLog() 方法

getLog() 沒什麼多說的,就是經過反射建立 Log 接口實現類,這裏沒有使用到緩存,每次調用都是建立一個新的對象。

jdbc 包

這個包與其餘包有些不一樣,它的職能是爲各個階段的流程提供日誌打印,該包一共就五個類,它們的關係以下:
jdbc包類uml圖

除了 BaseJdbcLogger 類其餘類都實現了 InvocationHandler 接口,這個接口是 JDK 提供的動態代理接口,因此顯而易見能夠知道它們就是經過代理在各個階段打印相應的日誌。

如下爲 BaseJdbcLogger 類的部分說明:

  • SET_METHODS:靜態字段,記錄 PreparedStatementset 開頭的的方法名
  • EXECUTE_METHODS:靜態字段,記錄 SQL 執行的方法名
  • columnXxx:實例字段,記錄 SQL 參數信息
  • statementLog:日誌對象
  • queryStack:查詢棧數
  • getParameterValueString():將 SQL 參數轉爲一個字符串
  • removeBreakingWhitespace():移除 SQL 中多餘的空白字符
  • prefix():獲取前綴 ==>/<==

而其他四個類都是簡單的邏輯:判斷執行的方法是否爲指定方法,而後打印相應的日誌。

總結

以上即是我的研究 logging 包的內容,本包使用瞭如下設計模式(包含不限於):

  1. 適配器模式
  2. 代理模式

其中適配器模式應該是一個比較不錯的示例,可作參考。

相關文章
相關標籤/搜索