mybatis 源码系列 组件之 logging

Question:

.  如何 实现 自动 日志组件的 可插拔? 

. 日志组件 优先级


为了实现 日志组件的可插拔, mybatis 本身 定义了一个 Log 接口, 并针对 commons, jdbc, jdk14, log4j, log4j2, nologging, slf4j, stdout 8中 组件 定义了相应的 adapter,将 这些 组件的 功能 适应到 mybatis 自己定义的 Log 接口,

先看看 mybaits 定义的 Log 接口 以及相应 方法:

public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwablee);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}


包括 error, debug, trace, warn 四个日志级别(比 log4j 少了 一个 info级别,为什么没有 info级别? 有知道的可以给出来), 同时 含有 isDebugEnabled 和 isTraceEnabled 两个日志级别判断方法,具体实现 以 slfj和stdout 做为例子:

import org.apache.ibatis.logging.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class Slf4jImpl implements Log {

  private Log log;

  public Slf4jImpl(String clazz) {
    Logger logger = LoggerFactory.getLogger(clazz); // slf4j 获取 org.slf4j.Logger 实例

    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);
        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); // 将 org.slf4j.Logger 实例 转换为 mybatis 自己的实现,adpter类 为 Slf4jLoggerImp
  }

  public boolean isDebugEnabled() {
    return log.isDebugEnabled(); //代理实现
  }

  public boolean isTraceEnabled() {
    return log.isTraceEnabled(); //代理实现
  }

  public void error(String s, Throwable e) {
    log.error(s, e); //代理实现
  }

  public void error(String s) {
    log.error(s); //代理实现
  }

  public void debug(String s) {
    log.debug(s); //代理实现
  }

  public void trace(String s) {
    log.trace(s); //代理实现
  }

  public void warn(String s) {
    log.warn(s); //代理实现
  }

}


看到了吗? 全部方法 由 底层的 org.slf4j.Logger 实例 来 实现,而  stdoutimpl 则 完全有 System.err.println 和 system.out.println 来 实现 error 和 debug、trace, warn 方法

package org.apache.ibatis.logging.stdout;

import org.apache.ibatis.logging.Log;

/**
 * @author Clinton Begin
 */
public class StdOutImpl implements Log {

  public StdOutImpl(String clazz) {
  }

  public boolean isDebugEnabled() {
    return true;
  }

  public boolean isTraceEnabled() {
    return true;
  }

  public void error(Strings, Throwablee) {
    System.err.println(s);
    e.printStackTrace(System.err);
  }

  public void error(Strings) {
    System.err.println(s);
  }

  public void debug(Strings) {
    System.out.println(s);
  }

  public void trace(Strings) {
    System.out.println(s);
  }

  public void warn(Strings) {
    System.out.println(s);
  }
}


至于其他实现,也都是用组合模式来实现,当然 nologgingImpl 则 更简单了, 它什么也不做


至于如何实现可插拔,则归功于 LogFactory 类, 这也是 mybatis 其他组件 获取 日志实例的  门店 ,包括两个 获取日志实例的重载方法:

  public static Log getLog(Class<?>aClass) {
    return getLog(aClass.getName());
  }

  public static Log getLog(Stringlogger) {
    try {
      return logConstructor.newInstance(new Object[] {logger });
    } catch (Throwable t) {
      thrownew LogException("Error creating logger for logger " +logger + ".  Cause: " +t,t);
    }
  }


getLog(Class<?>  aClass) 底层会调用 getLog(String  logger) 方法, 而 getLog(String  logger) 方法的实现 完全是由 logConstructor 反射 newInstance 实现的,而 logConstructor 则 会在 LoFactory 的 static 块里定义:并按照 优先级 

useSlf4jLogging useCommonsLogging useLog4J2Logging useLog4JLogging useJdkLogging useNoLogging 来定义,只要其中一个能返回  logConstructor,则不在调用其他实现,具体代码如下:

private static void tryImplementation(Runnable runnable) {
    if (logConstructor ==null) {// 会先判断 logConstructor 是否为空,只有在为空的情况下,才会 调用 相应 的实现 
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

对于 logConstructor 则 由如下代码赋值:

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 = candidate;
    } catch (Throwable t) {
      thrownew LogException("Error setting Log implementation.  Cause: " +t,t);
    }
  }


从中也可以看出 各个日志adapter类必须要 含有 string类型参数的构造函数, 同时 mybatis 只会 选择 一个 日志组件 来 实现 日志的输出


最后,介绍一下 logging包下的jdbc包,它主要的功能是对

Connection, PreparedStatement, ResultSet, Statement 进行代理,并使得代理出的对象的方法含有日志功能,即在某些方法调用前后可以打印出相关的debug或者tracee信息,下面以 StatementLogger为例,父类为 BaseJdbcLogger,利用组合模式 实现了 Log 接口相应的方法,同时 添加了一些基础方法,  而StatementLogger 实现 InvocationHandler 接口,并在 invoke方法中针对 部分 方法 进行了代理,并在需要时打印相关日志

public Object invoke(Object proxy, Method method, Object[] params)throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this,params);  // 从Object 类继承而来的方法不做任何处理
      }    
      if (EXECUTE_METHODS.contains(method.getName())) {// 如果方法名为  execute executeUpdateexecuteQuery addBatch 中得一个,则打印日志
        if (isDebugEnabled()) {
          debug(" Executing: " + removeBreakingWhitespace((String)params[0]), true);
        }
        if ("executeQuery".equals(method.getName())) {
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          if (rs !=null) {
            return ResultSetLogger.newInstance(rs,statementLog, queryStack); // 如果 方法名 是 executeQuery,并且 返回的 ResultSet 实例 rs不为空, 则对 rs进行代理,生成含有日志功能的 ResultSet 实例
          } else {
            return null;
          }
        } else {
          return method.invoke(statement,params);
        }
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        if (rs != null) {
          return ResultSetLogger.newInstance(rs,statementLog, queryStack);
        } else {
          return null;
        }
      } else if ("equals".equals(method.getName())) {
        Object ps = params[0];
        return ps instanceof Proxy && proxy == ps; // 如果 是 equals方法, 则 需要判断 equals方法的参数是否是 Proxy实例,同时 是否 相等
      } else if ("hashCode".equals(method.getName())) {
        return proxy.hashCode();
      } else {
        return method.invoke(statement,params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值