ORM框架之Mybatis(六):logging源碼實現(xiàn)分析

mybatis框架源碼的實現(xiàn)相對spring來說要簡單的很多,模塊的分工也很明確,每個模塊的代碼量也不是很大,比較容易閱讀,如果你對設(shè)計模式很了解的話。里面用到很多設(shè)計模式,如工廠模式、代理模式、裝飾器模式、適配器模式等等,都是值得平時開發(fā)學(xué)習(xí)和借鑒的,都說高手的代碼都是向高級框架靠攏,誰知道他是自己設(shè)計還是看了源碼學(xué)習(xí)的呢,對不?

mybatis的模塊比較多,如對外的用SqlSession、內(nèi)部底層日志logging、數(shù)據(jù)源模塊、反射等,這一篇來看mybatis源碼中最簡單的部分logging日志。這個模塊主要就是用工廠模式生成適配不同logging組件的日志類。然后就是適配器模式,適配不同的logging組件,并支持無侵入的logging組件擴展。

logging整體了解

想了解細(xì)節(jié),先了解整體,會更好??磮D:

image

市場上日志組件有很多,各個公司采用的日志組件不盡相同,但是可能都要使用到mybatis,如果mybatis只能支持一種日志的話,那么就會出現(xiàn)其他日志組件無法打印日志的問題。

此時使用適配器模式,不論其他的組件是什么樣的,統(tǒng)一使用適配類去對這些組件進行封裝,適配類都實現(xiàn)Log接口,將Log暴露給mybatis調(diào)用方,調(diào)用方直接調(diào)用log接口內(nèi)的方法即可。不用管底層到底是適配哪個日志組件。另外每種日志組件的日志級別分類都是有所差別的,做了統(tǒng)一封裝,就不用考慮日志級別變化的問題。

這種設(shè)計拓展性很好,如果需要添加日志組件,只要寫一個適配類去封裝此日志組件,然后在工廠類中添加日志適配類加載的代碼就可以,上層的業(yè)務(wù)代碼是無需任何修改,是無感的。

看一下源碼

Log接口類

Log接口類的代碼:

public interface Log {
  boolean isDebugEnabled();//是否可以debug日志
  boolean isTraceEnabled();//是否可以跟進日志
  //錯誤
  void error(String s, Throwable e);
  //錯誤
  void error(String s);
  //調(diào)試
  void debug(String s);
  //跟進
  void trace(String s);
  //警告
  void warn(String s);
}

這里日志接口很簡單,就四種日志級別。如果多l(xiāng)og4j、commons-logging這些日志組件有所了解的話,可以知道他們的日志級別都是有所不同。因此這里就必須要是做適配了,適配器就上場嘍。(slf4j為例)

Slf4jImpl實現(xiàn)類
public class Slf4jImpl implements Log {

  private Log log;
  // 構(gòu)造函數(shù)(很重要)
  public Slf4jImpl(String clazz) {
    // 獲取logger實例,這是對應(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)建適配對象
        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)建適配器對象的。先根據(jù)實際日志組件類創(chuàng)建logger實例,然后根據(jù)對logger實例進行包裝,得到一個適配后的log實例,也就是對應(yīng)日志組件的log適配器對象。Slf4jLocationAwareLoggerImpl內(nèi)的代碼就不跟進去看了,里面很簡單,就是將傳入的logger賦值給成員變量。然后在四種日志級別的方法里面都使用這個logger對象進行日志打印。

LogFactory工廠類

工廠類做了兩件事情,第一步在靜態(tài)代碼塊中依次掃描日志實現(xiàn),然后根據(jù)優(yōu)先級加載日志實現(xiàn)類的構(gòu)造器,只會加載一個,誰在前加載誰。第二步提供getLog方法,通過此方法中調(diào)用構(gòu)造器的newInstance方法構(gòu)造正式的日志對象和日志適配對象。

//加載日志構(gòu)造器的靜態(tài)代碼塊
//根據(jù)順序依次執(zhí)行日志類的加載,有加載順序和優(yōu)先級
//順序(優(yōu)先級):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)造器并不是對應(yīng)日志組件的日志類構(gòu)造器,而是適配器的構(gòu)造器??梢钥匆幌律厦孢m配器的源碼構(gòu)造方法。

//獲取日志對象,調(diào)用getLog(String logger)方法
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);
  }
}

創(chuàng)建日志實例的方法如上,很簡單,就是newInstance方法的調(diào)用。

Overview

日志組件適配的整個過程不難,代碼也很簡潔,過程也很清晰,然后現(xiàn)在通過下面的圖總結(jié)一下執(zhí)行的流程。

mybatis日志源碼-加載

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

mybatis日志源碼-創(chuàng)建

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

微信公眾號

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容