mybatis框架源碼的實(shí)現(xiàn)相對(duì)spring來說要簡(jiǎn)單的很多,模塊的分工也很明確,每個(gè)模塊的代碼量也不是很大,比較容易閱讀,如果你對(duì)設(shè)計(jì)模式很了解的話。里面用到很多設(shè)計(jì)模式,如工廠模式、代理模式、裝飾器模式、適配器模式等等,都是值得平時(shí)開發(fā)學(xué)習(xí)和借鑒的,都說高手的代碼都是向高級(jí)框架靠攏,誰知道他是自己設(shè)計(jì)還是看了源碼學(xué)習(xí)的呢,對(duì)不?
mybatis的模塊比較多,如對(duì)外的用SqlSession、內(nèi)部底層日志logging、數(shù)據(jù)源模塊、反射等,這一篇來看mybatis源碼中最簡(jiǎn)單的部分logging日志。這個(gè)模塊主要就是用工廠模式生成適配不同logging組件的日志類。然后就是適配器模式,適配不同的logging組件,并支持無侵入的logging組件擴(kuò)展。
logging整體了解
想了解細(xì)節(jié),先了解整體,會(huì)更好。看圖:

市場(chǎng)上日志組件有很多,各個(gè)公司采用的日志組件不盡相同,但是可能都要使用到mybatis,如果mybatis只能支持一種日志的話,那么就會(huì)出現(xiàn)其他日志組件無法打印日志的問題。
此時(shí)使用適配器模式,不論其他的組件是什么樣的,統(tǒng)一使用適配類去對(duì)這些組件進(jìn)行封裝,適配類都實(shí)現(xiàn)Log接口,將Log暴露給mybatis調(diào)用方,調(diào)用方直接調(diào)用log接口內(nèi)的方法即可。不用管底層到底是適配哪個(gè)日志組件。另外每種日志組件的日志級(jí)別分類都是有所差別的,做了統(tǒng)一封裝,就不用考慮日志級(jí)別變化的問題。
這種設(shè)計(jì)拓展性很好,如果需要添加日志組件,只要寫一個(gè)適配類去封裝此日志組件,然后在工廠類中添加日志適配類加載的代碼就可以,上層的業(yè)務(wù)代碼是無需任何修改,是無感的。
看一下源碼
Log接口類
Log接口類的代碼:
public interface Log {
boolean isDebugEnabled();//是否可以debug日志
boolean isTraceEnabled();//是否可以跟進(jìn)日志
//錯(cuò)誤
void error(String s, Throwable e);
//錯(cuò)誤
void error(String s);
//調(diào)試
void debug(String s);
//跟進(jìn)
void trace(String s);
//警告
void warn(String s);
}
這里日志接口很簡(jiǎn)單,就四種日志級(jí)別。如果多l(xiāng)og4j、commons-logging這些日志組件有所了解的話,可以知道他們的日志級(jí)別都是有所不同。因此這里就必須要是做適配了,適配器就上場(chǎng)嘍。(slf4j為例)
Slf4jImpl實(shí)現(xiàn)類
public class Slf4jImpl implements Log {
private Log log;
// 構(gòu)造函數(shù)(很重要)
public Slf4jImpl(String clazz) {
// 獲取logger實(shí)例,這是對(duì)應(yīng)到組件的logger
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
// 創(chuàng)建適配對(duì)象
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
這里主要看的就是構(gòu)造函數(shù),看構(gòu)造函數(shù)是如何創(chuàng)建適配器對(duì)象的。先根據(jù)實(shí)際日志組件類創(chuàng)建logger實(shí)例,然后根據(jù)對(duì)logger實(shí)例進(jìn)行包裝,得到一個(gè)適配后的log實(shí)例,也就是對(duì)應(yīng)日志組件的log適配器對(duì)象。Slf4jLocationAwareLoggerImpl內(nèi)的代碼就不跟進(jìn)去看了,里面很簡(jiǎn)單,就是將傳入的logger賦值給成員變量。然后在四種日志級(jí)別的方法里面都使用這個(gè)logger對(duì)象進(jìn)行日志打印。
LogFactory工廠類
工廠類做了兩件事情,第一步在靜態(tài)代碼塊中依次掃描日志實(shí)現(xiàn),然后根據(jù)優(yōu)先級(jí)加載日志實(shí)現(xiàn)類的構(gòu)造器,只會(huì)加載一個(gè),誰在前加載誰。第二步提供getLog方法,通過此方法中調(diào)用構(gòu)造器的newInstance方法構(gòu)造正式的日志對(duì)象和日志適配對(duì)象。
//加載日志構(gòu)造器的靜態(tài)代碼塊
//根據(jù)順序依次執(zhí)行日志類的加載,有加載順序和優(yōu)先級(jí)
//順序(優(yōu)先級(jí)):slf4j->commons-logging->log4j-logging->log4j->jdk->no-loging
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();
}
});
}
詳細(xì)如代碼,但是這里要知道的是,加載的日志構(gòu)造器并不是對(duì)應(yīng)日志組件的日志類構(gòu)造器,而是適配器的構(gòu)造器。可以看一下上面適配器的源碼構(gòu)造方法。
//獲取日志對(duì)象,調(diào)用getLog(String logger)方法
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
//獲取日志對(duì)象
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);
}
}
創(chuàng)建日志實(shí)例的方法如上,很簡(jiǎn)單,就是newInstance方法的調(diào)用。
Overview
日志組件適配的整個(gè)過程不難,代碼也很簡(jiǎn)潔,過程也很清晰,然后現(xiàn)在通過下面的圖總結(jié)一下執(zhí)行的流程。

日志工廠類加載日志適配器類過程。

通過gotLog方法觸發(fā)創(chuàng)建日志適配器類對(duì)象和日志組件對(duì)象。

本文作者:IT-CRUD
原文地址:http://blog.itcrud.com/blogs/2018/09/orm-mybatis-source-logging
版權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)注明出處