本文介紹我的對 logging 包下源碼的理解。java
咱們先從日誌的配置加載開始閱讀, MyBatis 的各項配置的加載過程均可以從 XMLConfigBuilder
類中找到,咱們定位到該類下的日誌加載方法 loadCustomLogImpl
:apache
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
是框架內部獲取 Log
對象的手段,經過它的名字也能看出來。它有以下幾類方法:緩存
// 注意這個類型的方法都是同步方法 public synchronized static useXxxLogging(...); public static Log getLog(...); private static tryImplementation(Runnable); private static setImplementation(Class);
剛剛咱們看到被調用的方法 useCustomLogging()
方法,是用來設置內部使用的日誌框架, MyBatis 自身已經適配了一些常見的日誌框架,如 Slf4j
、 Commons Log
、 Log4j
等等。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
這個類,它是一個接口,是 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); } }
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
類使用它就是一個普通的函數接口。除去這些就沒什麼了,不過是調用了 Runnable
的 run()
方法而已。
getLog()
沒什麼多說的,就是經過反射建立 Log
接口實現類,這裏沒有使用到緩存,每次調用都是建立一個新的對象。
這個包與其餘包有些不一樣,它的職能是爲各個階段的流程提供日誌打印,該包一共就五個類,它們的關係以下:
除了 BaseJdbcLogger
類其餘類都實現了 InvocationHandler
接口,這個接口是 JDK 提供的動態代理接口,因此顯而易見能夠知道它們就是經過代理在各個階段打印相應的日誌。
如下爲 BaseJdbcLogger
類的部分說明:
SET_METHODS
:靜態字段,記錄 PreparedStatement
中 set
開頭的的方法名EXECUTE_METHODS
:靜態字段,記錄 SQL 執行的方法名columnXxx
:實例字段,記錄 SQL 參數信息statementLog
:日誌對象queryStack
:查詢棧數getParameterValueString()
:將 SQL 參數轉爲一個字符串removeBreakingWhitespace()
:移除 SQL 中多餘的空白字符prefix()
:獲取前綴 ==>/<==
而其他四個類都是簡單的邏輯:判斷執行的方法是否爲指定方法,而後打印相應的日誌。
以上即是我的研究 logging 包的內容,本包使用瞭如下設計模式(包含不限於):
其中適配器模式應該是一個比較不錯的示例,可作參考。