Mybatis日誌源碼分析

Mybatis的日誌分析

在使用Mybatis的時候,可以看到控制檯的日誌輸出。有很多的日誌框架,那麼Mybatis如何與這些日誌框架進行整合呢?優先級又如何確定?日誌信息如何優雅的輸出?

1、統一日誌標準

使用接口進行標準統一。

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);
}

2、採用適配器模式進行適配

每個日誌的實現都提供對應的適配器,以log4j爲例:
將第三方的日誌接口轉換爲Mybatis規定的統一日誌接口

public class Log4jImpl implements Log {

  private static final String FQCN = Log4jImpl.class.getName();
  // 待適配的對象
  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);
  }
}

3、按優先級進行加載,加載到之後,後序的不再加載

按順序加載:

static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

工廠類中保存對應的日誌的Construct對象

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);
    }
  }

4、動態代理模式進行優雅輸出

爲了將業務與日誌進行解耦,採取動態代理模式,以ConnectionLogger爲例:

   
    public Object invoke(Object proxy, Method method, Object[] params)
          throws Throwable {
        // 從Object類中繼承過來的方法不進行增強
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, params);
          }
          /**
           *  PreparedStatment:  connection.prepareStatement(sql);
           *  connection.prepareCall(sql);
           */
          if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
            if (isDebugEnabled()) {
                // 日誌輸出
              debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
            }
            PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
            stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
            return stmt;
          } else if ("createStatement".equals(method.getName())) {
            Statement stmt = (Statement) method.invoke(connection, params);
            stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
            return stmt;
          } else {
            return method.invoke(connection, params);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章