簡述
在Java開發中經常使用的日誌框架有Log4j、Log4j二、Apache Commons Log、java.util.logging、slf4j等,這些工具對外的接口並不相同。爲了統一這些工具的接口,MyBatis定義了一套統一的日誌接口供上層使用,併爲上述經常使用的日誌框架提供了相應的適配器。java
適配器模式
首先,咱們簡單介紹設計模式中有六大原則。 apache
單一職責原則: 不要存在多於一個致使類變動的緣由,簡單來講,一個類只負責惟一項職責。 編程
里氏替換原則: 若是對每個類型爲T1的對象t1,都有類型爲T2的對象t2,使得以T1定義的全部程序P在全部的對象t1都代換成t2時,程序 P 的行爲沒有發生變化,那麼類型 T2 是類型 T1 的子類型。遵照里氏替換原則,能夠幫助咱們設計出更爲合理的繼承體系。 設計模式
依賴倒置原則: 系統的高層模塊不該該依賴低層模塊的具體實現,兩者都應該依賴其抽象類或接口,抽象接口不該該依賴具體實現類,而具體實現類應該於依賴抽象。簡單來講,咱們要面向接口編程。當需求發生變化時對外接口不變,只要提供新的實現類便可。 框架
接口隔離原則: 一個類對另外一個類的依賴應該創建在最小的接口上。簡單來講,咱們在設計接口時,不要設計出龐大臃腫的接口,由於實現這種接口時須要實現不少沒必要要的方法。咱們要儘可能設計出功能單一的接口,這樣也能保證明現類的職責單一。ide
迪米特法則: 一個對象應該對其餘對象保持最少的瞭解。簡單來講,就是要求咱們減低類間耦合。 工具
開放-封閉原則: 程序要對擴展開放,對修改關閉。簡單來講,當需求發生變化時,咱們能夠經過添加新的模塊知足新需求,而不是經過修改原來的實現代碼來知足新需求。 spa
在這六條原則中,開放-封閉原則是最基礎的原則,也是其餘原則以及後文介紹的全部設計模式的最終目標。debug
適配器模式的主要目的是解決因爲接口不能兼容而致使類沒法使用的問題,適配器模式會將須要適配的類轉換成調用者可以使用的目標接口。這裏先介紹適配器模式中涉及的幾個角色:設計
- 目標接口(Target):調用者可以直接使用的接口。
- 源(Adaptee)角色:須要適配的接口
- 適配器(Adapter)角色:適配器類是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不能夠是接口,而必須是具體類。
使用適配器模式的好處就是複用現有組件。應用程序須要複用現有的類,但接口不能被該應用程序兼容,則沒法直接使用。這種場景下就適合使用適配器模式實現接口的適配,從而完成組件的複用。很明顯,適配器模式經過提供Adapter的方式完成接口適配,實現了程序複用Adaptee的需求,避免了修改Adaptee實現接口,這符合「開放-封閉」原則。當有新的Adaptee須要被複用時,只要添加新的Adapter便可,這也是符合「開放-封閉」原則的。
在MyBatis的日誌模塊中,就使用了適配器模式。Log4j、Log4j2等第三方日誌組件對外提供的接口各不相同,MyBatis爲了集成和複用這些第三方日誌組件,在其日誌模塊中提供了多種Adapter,將這些第三方日誌組件對外的接口適配成了org.apache.ibatis.logging.Log接口,這樣MyBatis內部就能夠統一經過org.apache.ibatis.logging.Log接口調用第三方日誌組件的功能了。
日誌適配器
前面提到的多種第三方日誌組件都有各自的Log級別,且都有所不一樣,例如java.util.logging提供了All、FINEST、FINER、FINE、CONFIG、INFO、WARNING等9種級別,而Log4j2則只有trace、debug、info、warn、error、fatal這6種日誌級別。MyBatis統一提供了trace、debug、warn、error四個級別,這基本與主流日誌框架的日誌級別相似,能夠知足絕大多數場景的日誌需求。 MyBatis的日誌模塊位於org.apache.ibatis.logging包中,該模塊中經過Log接口定義了日誌模塊的功能,固然日誌適配器也會實現此接口。LogFactory工廠類負責建立對應的日誌組件適配器
在LogFactory類加載時會執行其靜態代碼塊,其邏輯是按序加載並實例化對應日誌組件的適配器,而後使用LogFactory.logConstructor這個靜態字段,記錄當前使用的第三方日誌組件的適配器,
/** * 記錄當前使用的第三方日誌組件所對應的適配器的構造方法 */ private static Constructor<? extends Log> logConstructor; /** * 調用方法tryImplementation順序加載每種組件 */ 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(); } }); }
LogFactory.tryImplementation()方法首先會檢測logConstructor字段,若爲空則調用Runnable.run()方法
private static void tryImplementation(Runnable runnable) { if (logConstructor == null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }
每種日誌組件的加載都是調用setImplementation方法,這裏以Slf4j爲例,以下:
public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } /** * 根據指定適配器實現類加載相應的日誌組件 */ 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); } }