mybatis学习-01-日志模块源码

本文深入探讨MyBatis日志模块的设计与实现,包括日志级别的统一、第三方日志框架的适配策略及优雅的日志功能嵌入方式。

日志模块的需求

  1. mybatis没有自己提供日志的实现类,需要接入第三方的日志框架。但第三方的日志组件的日志级别等都不一致。所以mybatis统一往外提供了
    trace debug warn error四个级别
  2. 自动扫描日志实现 mybatis需要实现,并且第三方日志的加载有固定级别 : slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;
  3. 日志功能需要优雅的嵌入到主体功能中
包结构

日志模块的接口
整体的代码量其实很少,主要是设计思想的学习吧
针对需求一,本身无实现,各日志框架不统一,使用适配器模式

适配器模式:
作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。
角色: Targe 希望被转成的目标对象    Log
            Adaptee  谁将会被转换成target    Slfj 、log4j等日志框架的logger 
            Adapter  适配者  log4j包、slf4j包下面的Slf4jLoggerImpl 适配对象

针对需求二,多个第三方日志框架,mybatis如何实现加载,加载哪一个?

public final class LogFactory {

  /**
   * 部分框架有MARKER
   */
  public static final String MARKER = "MYBATIS";

  /**
   * 最终获取到的日志框架的构造
   */
  private static Constructor<? extends Log> logConstructor;

  static {
  	// 这里其实就是核心,按照顺序来执行
  	// 尝试获取logConstructor,以slf4j为例,跟进去
    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();
      }
    });
  }
// 如果构造方法为空,执行run方法,注意这里只是run,没有开线程
// 猜测了一下这么做的原因,可能仅为了面向接口,实现隔离吧
private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore 因为存在找不到框架的问题,所以找不到直接忽略掉异常
        // 会担心都找不到呢? 其实上面静态方法最后两个,一个是jdk,一个是noLog,肯定可以找到的
      }
    }
  }

useSlf4jLogging 具体做了什么

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);
      // 报错抛异常,其实这个异常上面也只是吃掉了,没有处理
    }
  }

构造方法创建对象


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

针对需求三,mybatis如何优雅的将日志功能嵌入到代码中
思考:哪里需要日志? sql需要打印出来 参数需要打印出来 结果和条数需要打印出来
再思考:一般我们希望优雅的加入功能,怎么添加? 代理模式来增强
先介绍下代理模式:

 抽象对象: 声明了真实对象和代理对象的接口
 真实对象: 真正处理业务的对象
 代理对象: 内部包括真实对象,对被代理对象进行增强,相当于访问者和真实对象的中介,经纪人
 
 静态代理和动态代理(jdk cglib)
 静态: 实现相同的接口,内部维护真实对象的引用,编译期class就存在
             优点:可以在不修改代码的前提下,进行增强
             缺点: 冗余,可能会有很多这样的class,并且得实现所有的方法
                        当修改接口时,真实对象和代理对象都得修改
 动态: 在运行期间动态生成class文件。
       主要涉及 Proxy  InvocationHandler
       具体不细说,后续写一篇动态代理源码的分析。

刚刚我们也分析一般哪里需要日志,就去对应的模块找 参数在statment 结果再resultSet sql在哪,不太清楚,就按照sql执行开头寻找 Executor
在这里插入图片描述

doQuery: 构建sql语句 stmt = prepareStatement(handler, ms.getStatementLog());

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取连接,获取连接有ConnectionLogger,跟进
    Connection connection = getConnection(statementLog);
    //调用StatementHandler/prepareStatement的prepare()
    stmt = handler.prepare(connection);
    //调用StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }

// 拿到db的conn后,利用ConnectionLogger生成代理对象
 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      //如果需要打印Connection的日志,返回一个ConnectionLogger(代理模式)
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
// 构建statement ,
 public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //实例化Statement,内部不跟,会根据不同的实现,创建statement和prepareStatement 
      //  connection.prepareStatement  or  connection.createStatement
      statement = instantiateStatement(connection);
      //设置超时
      setStatementTimeout(statement);
      //设置读取条数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
 
 // 到这里我们可以看出来,还是调用conn里面,直接看ConnectionLogger代理类的invoke即可
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }   
      // 预处理 
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }  
        // 获取PreparedStatement,回去实例后,继续包装成 PreparedStatementLogger 
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((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 ,回去实例后,继续包装成 StatementLogger 
        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);
    }
  }

// 返回statement以后,query   
ps.execute();
String[] resultSets = mappedStatement.getResulSets();
// stament的getResultSet,继续去PrePareStatementLogger内部看下
 public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }          
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (isDebugEnabled()) {
          // 执行的时候,先打印参数日志
          debug("Parameters: " + getParameterValueString(), true);
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {
  		  // 返回resultSet 的时候,包装成ResultSetLogger
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      } else if ("getResultSet".equals(method.getName())) {
        // 返回时包装ResultSetLogger
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else if ("getUpdateCount".equals(method.getName())) {
        int updateCount = (Integer) method.invoke(statement, params);
        if (updateCount != -1) {
          debug("   Updates: " + updateCount, false);
        }
        return updateCount;
      } else {
        return method.invoke(statement, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

// 后续处理result的时候,调用rsw.getResultSet().next(),继续去ResultSetLogger看下
 public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }    
      Object o = method.invoke(rs, params);
      if ("next".equals(method.getName())) {
        if (((Boolean) o)) {
          rows++;
          if (isTraceEnabled()) {
            ResultSetMetaData rsmd = rs.getMetaData();
            final int columnCount = rsmd.getColumnCount();
            if (first) {
              first = false;
              printColumnHeaders(rsmd, columnCount);
            }
            printColumnValues(columnCount);
          }
        } else {
          // 打印结果的数量
          debug("     Total: " + rows, false);
        }
      }
      clearColumnInfo();
      return o;
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

下面是整体的一个日志模块的一个架构图吧,可以帮助理解:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值