mybatis log模塊
??Mybatis的日志模塊的作用是為了讓mybatis在項(xiàng)目中被使用的時(shí)候衔彻,無(wú)論什么情況都能有一種日志實(shí)現(xiàn)讓mybatis可以正常的打日志且预。所以mybatis不能用單一的實(shí)現(xiàn)方式去打印日志。
不能像我們平常一樣用slf4j的LogFactory.getLogger(XXXX)一樣去用日志爵卒。因?yàn)樗赡茉跊]有Slf4j的項(xiàng)目里被使用。難道就不打日志了么?
??那mybatis怎么辦呢古毛?他就在各種輸出手段的外面再包一層統(tǒng)一的接口門面。然后哪個(gè)可以用就用哪個(gè)都许。也就是設(shè)計(jì)模式中的“適配器模式”稻薇。通過(guò)LogFactory和Log統(tǒng)一封裝底層不同實(shí)現(xiàn)的差異。在mybatis自己的代碼中胶征,記錄日志的常規(guī)用法就是LogFactory.getLog(XXX.class)
這里面的主要代碼集中實(shí)現(xiàn)在LogFactory中塞椎。對(duì)照注釋閱讀。
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 {
/**
* 依次去嘗試設(shè)置LogFactory會(huì)用哪一種Log實(shí)現(xiàn)去做日志睛低。
* 第一個(gè)成功的會(huì)塞入logConstructor案狠,用于后面創(chuàng)建mybatis的通用日志接口Logger
* 最優(yōu)先的是slf4j。從上到下一次排列
*
* 這里有一個(gè)問(wèn)題是為什么每一種useXXX方法都是同步的呢暇昂?他們明明就在同一個(gè)static塊中被依次調(diào)用莺戒。
* 后來(lái)仔細(xì)一想,是因?yàn)檫@些方法也可以在LogFactory的Class被初始化加載之后急波,在不用的地方被不同的線程調(diào)用从铲。
* 從而動(dòng)態(tài)改變Log的具體實(shí)現(xiàn)者,如果這些方法不是synchronized的話澄暮,不同的線程對(duì)logConstructor就會(huì)出現(xiàn)可見性問(wèn)題名段。
* 類似于單例懶加載雙重檢查鎖。
*/
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<?> 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);
}
//····省略一堆userXXLogging方法……
//這里的邏輯一種是一種裝載緩存內(nèi)容的典型判斷泣懊,沒有就嘗試去裝載logConstructor
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
//這里嘗試傳入的是實(shí)現(xiàn)了ibatis的Log適配器接口的實(shí)現(xiàn)類
private static void setImplementation(Class<? extends Log> implClass) {
try {
//這些實(shí)現(xiàn)類都有一個(gè)string參數(shù)的構(gòu)造器伸辟,用于傳入logger的名字,
//不過(guò)這一點(diǎn)并不是從語(yǔ)法上限制死的馍刮。而是mybatis對(duì)每一個(gè)實(shí)現(xiàn)都提供了這樣的constructor
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//用mybatis的LogFactory的名字創(chuàng)建一個(gè)mybatis的log接口信夫,具體的實(shí)現(xiàn)由被適配的具體實(shí)現(xiàn)決定
Log log = candidate.newInstance(LogFactory.class.getName());
//馬上就用了,打印的第一句就是卡啰,表明用了什么日志適配器實(shí)現(xiàn)
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
//Log適配器的構(gòu)造器緩存下來(lái)静稻,用于后面創(chuàng)建其他日志
logConstructor = candidate;
//所以這個(gè)方法的核心作用就是,抽取我們的真正的日志適配器實(shí)現(xiàn)的constructor緩存起來(lái)匈辱,用于后面創(chuàng)建log
} catch (Throwable t) {
//注意這個(gè)方法其實(shí)會(huì)經(jīng)常拋錯(cuò)的振湾。因?yàn)橐玫讓拥膶?shí)現(xiàn)的前提是引入了這個(gè)實(shí)現(xiàn)的包。并且能用亡脸。
// 當(dāng)不成功的時(shí)候押搪,通常就是缺了實(shí)現(xiàn)類树酪。比如第一個(gè)的Slf4j的logg實(shí)現(xiàn),如果沒有slff4j的jar大州,自然就失敗了续语。
//所以在外面tryImplementation這個(gè)方法會(huì)抓住我們的初始化異常。并忽略它摧茴。設(shè)置失敗就算咯~嘗試其他的绵载。
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}