看源碼需要先下載源碼,可以去Mybatis的github上的倉庫進行下載,Mybatis
這次就先整理一下日志這一塊的源碼分析,這塊相對來說比較簡單而且這個模塊是Mybatis的基礎模塊。
之前的文章有談到過Java的日志實現,大家也可以參考一下:日志實現以及使用
我這里看的是目前最新的版本:3.5.7版本。
設計模式
我們先來談談這個模塊用到的設計模式。
在市面上有第三方日志實現,但是Mybatis總不可能將每個第三方日志組件實現都做一遍單獨的接入,所以日志用到的模式叫適配器模式。
適配器模式(Adapter Pattern)
是作為兩個不兼容的接口之間的橋梁。這種類型的設計模式屬于結構型模式,它結合了兩個獨立接口的功能。
借用一下網上的UML:
Target:目標角色,即期待得到的接口。
Adaptee:適配者角色,被適配的接口(第三方日志接口)。
Adapter:適配器角色,將被適配接口與目標接口進行橋接。
適用場景:當調用雙方都不太容易修改的時候,為了復用現有組件可以使用適配器模式;在系統中接入第三方組 件的時候經常被使用到;
注意:如果系統中存在過多的適配器,會增加系統的復雜性,設計人員應考慮對系統進行重構;
代理模式(Proxy Pattern)
一個類代表另一個類的功能。這種類型的設計模式屬于結構型模式。
在代理模式中,我們創建具有現有對象的對象,以便向外界提供功能接口。
在Mybatis中日志模塊用到的代理模式使用將查詢的參數,結果以及SQL語句進行打印。
源碼解析
在源碼中org.apache.ibatis.logging包下
Mybatis并沒有實現自己的日志接口,但是MyBatis統一提供了trace、debug、warn、error四個級別;只是定義了一個接口Log,一個適配日志工廠LogFactory;
在LogFactory的靜態代碼塊中,調用的方法順序為:slf4j -> commons-logging -> log4j2 -> log4j -> JDKLogging -> NoLogging。
所以在沒有指定Mybatis的日志類型的時候,會去按照這個順序自動查找對應的第三方日志組件的實現。
package org.apache.ibatis.logging;/*** @author Clinton Begin*/
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);}
package org.apache.ibatis.logging;import java.lang.reflect.Constructor;public final class LogFactory {public static final String MARKER = "MYBATIS";private static Constructor<? extends Log> logConstructor;static {tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}private LogFactory() {// disable construction}public static Log getLog(Class<?> clazz) {return getLog(clazz.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);}}}
我們就拿一個示例講解:Log4j
我們找到Log4j的適配器調用的類,如下
其他類型的日志組件適配都是以這種形式進行編碼的。
第三方的日志組件已經適配好,接下來就是將查詢過程的各項參數結果集打印。
都知道所有的ORM框架底層都是采用JDBC做查詢的,Mybatis也不例外。
這里就必須用到了代理模式,Mybatis的代理模式是通過JDK的動態代理進行實現的之前講AOP時講過這個;AOP代理及實現
在Mybatis中查詢SQL主要是由Exceutor去查詢的,默認使用SimpleExecutor,
我們進去這個getConnection方法
這個newInstance方法里面是通過JDK動態代理進行創建的
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);ClassLoader cl = Connection.class.getClassLoader();return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);}
所以在拿到Connection鏈接之后,需創建一個Statement對象,所以在創建的時候,就會進入到invoke方法
這樣依次進行代理,在使用ResultSet的時候也會進入到對應的代理對象當中
這樣日志打印的過程就是這樣的,
具體的日志類型加載后面會講,日志模塊分析就差不多這樣講完了。