由于常用的日志架構的對外接口各不相同,Mybatis為了複用和內建這些第三方日志元件,在其日志子產品中,提供了多種Adapter,将這些第三方日志元件對外接口适配成org.apache.ibatis.logging.Log,這樣Myabtis 就可以通過Log接口調用第三方日志了,接下來我們來看一下具體的代碼
關于Mybatis內建日志架構設定
<setting name="logImpl" value="STDOUT_LOGGING"/>
日志屬性 | 對應日志子產品包名 | 實作方式 |
SLF4J | slf4j | 使用SLF4J日志架構實作 |
LOG4J | log4j | 使用Log4J日志架構實作(1.x版本) |
LOG4J2 | log4j2 | 使用Log4J日志架構實作(2.x版本) |
JDK_LOGGING | jdk14 | 使用java.util.logging實作 |
COMMONS_LOGGING | commons | 使用Apache Commons Logging實作 |
STDOUT_LOGGING | stdout | 使用System類實作 |
NO_LOGGING | nologging | 不列印日志 |
在《Mybatis核心源碼-通過sqlSession擷取映射器代理工廠》提到了XMLConfigBuilder#parseConfiguration中的mapperElement方法,今天我們來看一下settingsElement方法是如何設定日志架構的
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
//檢查下是否在Configuration類裡都有相應的setter方法(沒有拼寫錯誤)
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
//下面非常簡單,一個個設定屬性
...
//顯式定義用什麼log架構,不定義則用預設的自動發現jar包機制
//resolveClass實際是使用的别名查詢對應的calss類
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
}
}
//假設我們使用的是标準輸出STDOUT_LOGGING
//org.apache.ibatis.logging.stdout.StdOutImpl.class
public void setLogImpl(Class<?> logImpl) {
if (logImpl != null) {
this.logImpl = (Class<? extends Log>) logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
接下來我們來看LogFactory工廠設計模式:
靜态代碼塊的作用是如果我們沒有在配置檔案中指定使用那哪個日志架構的話,這裡會嘗試挨個進行設定,如果擷取到了就不會再設定别的了,因為tryImplementation中有判斷
假設我們自己配置了日志架構,則會調用setImplementation方法,将logConstructor設定成指定的class類,這裡我們使用的是标準輸出:org.apache.ibatis.logging.stdout.StdOutImpl.class
當設定完後,使用LogFactory.getLog擷取具體日志架構時,就會根據構造方法建立對應的StdOutImpl執行個體,傳回
public final class LogFactory {
//具體究竟用哪個日志架構,那個架構所對應logger的構造函數
private static Constructor<? extends Log> logConstructor;
//靜态代碼塊
//啟動的時候嘗試挨個進行設定,如果擷取到了就不會在設定别的了
static {
//這邊乍一看以為開了幾個并行的線程去決定使用哪個具體架構的logging,其實不然
...
//log4j
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
//jdk logging
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
//沒有日志
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}
//單例模式,不得自己new執行個體
private LogFactory() {
// disable construction
}
//根據傳入的類來建構Log
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
//根據傳入的類名來建構Log
public static Log getLog(String logger) {
try {
//構造函數,參數必須是一個,為String型,指明logger的名稱
return logConstructor.newInstance(new Object[] { logger });
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
//提供一個擴充功能,如果以上log都不滿意,可以使用自定義的log
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 useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.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) {
//如果logConstructor有值就不會再設定了
if (logConstructor == null) {
try {
//這裡調用的不是start,而是run!根本就沒用多線程嘛!
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
//自定義日志輸出架構實際調用的是這裡
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(new Class[] { String.class });
Log log = candidate.newInstance(new Object[] { LogFactory.class.getName() });
log.debug("Logging initialized using '" + implClass + "' adapter.");
//設定logConstructor,一旦設上,表明找到相應的log的jar包了,那後面别的log就不找了。
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
我們再來看StdOutImpl類,看到所有的标準輸出,就是使用了java的System.out.println(s)方法列印了日志
public class StdOutImpl implements Log {
public StdOutImpl(String clazz) {
// Do Nothing
}
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public boolean isTraceEnabled() {
return true;
}
@Override
public void error(String s, Throwable e) {
System.err.println(s);
e.printStackTrace(System.err);
}
@Override
public void error(String s) {
System.err.println(s);
}
@Override
public void debug(String s) {
System.out.println(s);
}
@Override
public void trace(String s) {
System.out.println(s);
}
@Override
public void warn(String s) {
System.out.println(s);
}
}
我們來看擴充卡設計模式的應用:Mybatis中的log4j日志架構
假設我們設定的是LOG4J,則LogFactory會傳回Log4jImpl類,而Log4jImpl中使用了擴充卡設計模式,在Log4jImpl的構造函數中初始化了log4j的日志對象,當我們在使用log.error時,實際使用的是log4j日志架構的log方法
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
//這裡設定了log4j的Logger,然後在Log4jImpl的構造函數中初始化了log4j
private 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);
}
}