前言
本文介紹個人對 logging 包下源碼的理解。分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧
logging 配置加載
我們先從日志的配置加載開始閱讀, mybatis 的各項配置的加載過程都可以從 xmlconfigbuilder 類中找到,我們定位到該類下的日志加載方法 loadcustomlogimpl:
1
2
3
4
5
6
7
8
|
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
usecustomlogging()方法
logfactory 是框架內部獲取 log 對象的手段,通過它的名字也能看出來。它有如下幾類方法:
1
2
3
4
5
6
7
8
|
// 注意這個類型的方法都是同步方法 public synchronized static usexxxlogging(...); public static log getlog(...); private static tryimplementation(runnable); private static setimplementation( class ); |
剛剛我們看到被調用的方法 usecustomlogging()
方法,是用來設置內部使用的日志框架, mybatis 自身已經適配了一些常見的日志框架,如 slf4j 、 commons log 、 log4j 等等。
usecustomlogging()
方法內部調用 setimplementation(class)
方法,此方法代碼如下,功能簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 接口
剛剛我們接觸到了 log 這個類,它是一個接口,是 mybatis 內部使用的日志對象的抽象,它是為了兼容市面上各種各樣的日志框架,使用了適配器模式,通過 log 接口來連接 mybatis 和其他日志框架,通過實現 log 接口連著 mybatis 和需要適配的日志框架。
log 接口代碼如下,先試著發現該接口與其他常見的日志接口的區別:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 類,它代碼非常簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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 ); } } |
tryimplementation() 方法
logfactory 類再介紹一下被靜態代碼塊使用的方法 tryimplementation(runnable)
。靜態代碼塊代碼如下:
1
2
3
4
5
6
7
8
9
|
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() 方法
getlog()
沒什么多說的,就是通過反射創建 log 接口實現類,這里沒有使用到緩存,每次調用都是創建一個新的對象。
jdbc 包
這個包與其他包有些不同,它的職能是為各個階段的流程提供日志打印,該包一共就五個類,它們的關系如下:
除了 basejdbclogger 類其他類都實現了 invocationhandler 接口,這個接口是 jdk 提供的動態代理接口,所以顯而易見可以知道它們就是通過代理在各個階段打印相應的日志。
以下為 basejdbclogger 類的部分說明:
- set_methods:靜態字段,記錄 preparedstatement 中 set 開頭的的方法名
- execute_methods:靜態字段,記錄 sql 執行的方法名
- columnxxx:實例字段,記錄 sql 參數信息
- statementlog:日志對象
- querystack:查詢棧數
- getparametervaluestring():將 sql 參數轉為一個字符串
- removebreakingwhitespace():移除 sql 中多余的空白字符
- prefix():獲取前綴 ==>/<==
而其余四個類都是簡單的邏輯:判斷執行的方法是否為指定方法,然后打印相應的日志。
總結
以上便是個人研究 logging 包的內容,本包使用了以下設計模式(包含不限于):
- 適配器模式
- 代理模式
其中適配器模式應該是一個比較不錯的示例,可做參考。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://segmentfault.com/a/1190000018365826