Mybatis源码学习第一课---基础支撑层日志模块分析

MyBatis 源码下载地址:https://github.com/mybatis/mybatis-3

一、mybatis整体架构

   mybatis 整体架构主要分为三个层次:接口层、核心处理层、基础支撑层。如下图:

            Mybatis源码学习第一课---基础支撑层日志模块分析_第1张图片

二、基础支撑层源码分析

           基础支持层位于Mybatis整体架构的最底层,支撑着Mybatis的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用、独立的模块,不仅仅为mybatis提供基础支撑,也可以在合适的场景直接复用。如下图:

          Mybatis源码学习第一课---基础支撑层日志模块分析_第2张图片

三 日志模块源码解析

     良好的日志在一个软件中占了非常重要的地位,日志是开发与运维管理之间的桥梁。 日志 可以帮助运维人员和管理人员快速查找系统的故障和瓶颈,也可以帮助开发人员与运维人员沟 通, 更好地完成开发和运维任务。 但日志的信息量会随着软件运行时间不断变多,所以需要定 期汇总和清理,避免影响服务器的正常运行。

         在 Java 开发中常用的日志框架有 Log4j、 Log4j2、 Apache Commons Log、 java.util.logging、 slf.句等,这些工具对外的接口不尽相同。为了统一这些工具的接口, MyBatis 定义了一套统一 的日志接口供上层使用 , 并为上述常用的日志框架提供了相应的适配器

   日志模块使用适配器 设计模式

3.1、设计模式中有六大原则

   单一职责原则: 不要存在多于一个导致类变更的原因,简单来说, 一个类只负责唯一 项职责。

   里氏替换原则:如果对每一个类型为 Tl 的对象 tl ,都有类型为 T2 的对象 t2,使得以 Tl 定义的所有程序 P 在所有的对象 tl 都代换成 t2 时,程序 P 的行为没有发生变化, 那么类型 T2 是类型 Tl 的子类型。遵守里氏替换原则,可以帮助我们设计出更为合 理的继承体系。

    依赖倒置原则: 系统的高层模块不应该依赖低层模块的具体实现, 二者都应该依赖其 抽象类或接口,抽象接口不应该依赖具体实现类,而具体实现类应该于依赖抽象。简 单来说,我们要面向接口编程。 当需求发生变化时对外接口不变,只要提供新的实现 类即可。

    接口隔离原则: 一个类对另一个类的依赖应该建立在最小的接口上。简单来说,我们 在设计接口时,不要设计出庞大膝肿的接口,因为实现这种接口时需要实现很多不必 要的方法。 我们要尽量设计出功能单一的接口,这样也能保证实现类的职责单一。

    迪米特法则: 一个对象应该对其他对象保持最少的了解。 简单来说,就是要求我们减 低类间祸合。

    开放-封闭原则: 程序要对扩展开放,对修改关闭。简单来说,当需求发生变化时,我 们可以通过添加新的模块满足新需求,而不是通过修改原来的实现代码来满足新需求。

     在这六条原则中,开放-封闭原则是最基础的原则,也是其他原则以及后文介绍的所有设计 模式的最终目标

3.2 适配器设计模式

         适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的
另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;

       这里先介绍 适配器模式中涉及的几个角色,如下所述。

        1、目标接口(Target):调用者能够直接使用的接口 。

        2、需要适配的类(Adaptee): 一般情况下, Adaptee 类中有真正的业务逻辑,但是其接口 不能被调用者直接使用。

        3、适配器(Adapter): Adapter 实现了 Target 接口,并包装了一个 Adaptee 对象。 Adapter 在实现 Target 接口中的方法时,会将调用委托给 Adaptee 对象的相关方法,由 Adaptee 完成具体的业务。 

         Mybatis源码学习第一课---基础支撑层日志模块分析_第3张图片

           使用适配器模式的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该 应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完 成组件的复用。很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee 的需求,避免了修改 Adaptee 实现接口,这符合“开放-封闭”原则。当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这也是符合“开放一封闭”原则的。 在 MyBatis 的日志模块中,就使用了适配器模式。

       MyBatis 内部调用其日志模块时,使用 了其内部接口(也就是后面要介绍的 org.apache. atis.logging.Log 接口)。但是 Log4j、 Log4j2 等第三方日志组件对外提供的接口各不相同, MyBatis 为了集成和复用这些第三方日志组件, 在其日志模块中提供了多种 Adapter ,将这些第 三方日志 组件对外的接口适配成 了 org.apach巳. atis.logging.Log 接口,这样 MyBatis 内部就可以统一通过 org.apache.ibatis.logging. Log 接口调用第三方日志组件的功能了。

     当程序中存在过多的适配器时,会让程序显得非常复杂(后续介绍的所有模式都会有该问 题,但是与其带来的好处进行权衡后,这个问题可以忽略不计),增加了把握住核心业务逻辑的 难度,例如,程序调用了接口 A,却在又被适配成了接口 B。 如果程序中需要大量的适配器, 则不再优先考虑使用适配器模式,而是考虑将系统进行重构,这就需要设计人员进行权衡。

 3.3 日志适配器模块源码解析

       日志模块总体图组:

       Mybatis源码学习第一课---基础支撑层日志模块分析_第4张图片

            前面描述的多种第三方日志组件都有各自的 Log 级别,且都有所不同,例如 java.util.logging 提供了 All、 F卧而ST、 FINER、 FINE、 CONFIG、卧.WO、 W成NING 等 9 种级别,而 Log句2 则 只有 trace、 debug、 info、 warn、 eηor、 fatal 这 6 种日志级别。

          MyBatis 统一提供了 trace、 debug、 warn、 eηor 四个级别,这基本与主流日志框架的日志级别类似,可以满足绝大多数场景的日志 需求。 My Batis 的日志模块位于 org.apache.ibatis.logging 包中,该模块中通过 Log 接口定义了日志 模块的功能,当然日志适配器也会实现此接口。 LogFactory 工厂类负责创建对应的日志组件适配器。

                          Mybatis源码学习第一课---基础支撑层日志模块分析_第5张图片  Mybatis源码学习第一课---基础支撑层日志模块分析_第6张图片

 下面先看下 LogFactory 工厂类的源代码:

      在 LogFactory 类加载时会执行其静态代码块,其逻辑是按序加载并实例化对应日志组件的适配器,然后使用 LogFactory. logConstructor 这个静态宇段,记录当前使用的第三方日志组件的适配器,具体代码如下所示。

 //被选定的第三方日志组件适配器的构造方法
  private static Constructor logConstructor;

  //自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
  static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

  private LogFactory() {
    // disable construction
  }

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

第三方日志插件加载优先级加载顺序通过下列代码实现:

 private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {//当构造方法不为空才执行方法
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }
  //通过指定的log类来初始化构造方法
  private static void setImplementation(Class implClass) {
    try {
      Constructor 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);
    }
  }

1、日志模块适配器使用方式

1)目标接口 Target:org.apache.ibatis.logging.Log 接口。

  2)需要适配的类(Adaptee):slf4J、 commonsLoging 、Log4J2 、 Log4J 、 JdkLog等日志类

  3)适配器(Adapter):下面都是实现了org.apache.ibatis.logging.Log 接口。

     

 

jdkLog的适配器--Jdk14LoggingImpl

     Jdkl 4Logginglmpl 实现了 org.apache.ibatis.logging.Log 接口,并封装了 java.util.logging. Logger 对象, org叩ache.ibatis.logging.Log 接口的功能全部通过调用 java.util.logging.Logger 对象 实现, 这与前面介绍的适配器模式完全一致。 Jdkl4Logginglmpl 的实现如下:

      

//jdkLog的适配器
public class Jdk14LoggingImpl implements Log {

  //真正提供日志能力的jdk的日志类
  private final Logger log;

  public Jdk14LoggingImpl(String clazz) { log = Logger.getLogger(clazz); }
  @Override
  public boolean isDebugEnabled() { return log.isLoggable(Level.FINE); }

  @Override
  public boolean isTraceEnabled() {return log.isLoggable(Level.FINER);}

  @Override
  public void error(String s, Throwable e) { log.log(Level.SEVERE, s, e); }

  @Override
  public void error(String s) { log.log(Level.SEVERE, s);}

  @Override
  public void debug(String s) {log.log(Level.FINE, s); }

  @Override
  public void trace(String s) { log.log(Level.FINER, s);}

  @Override
  public void warn(String s) { log.log(Level.WARNING, s);}

}

实现了 org.apache.i batis. logging.Log 接口的其他适配器与 Jdkl4Logginglmpl 类似,这里不再 赘述,感兴趣的读者可以参考源码进行学习。


3.4 代理模式那些事

         在下一小节要介绍的 JDBC 调试功能中会涉及代理模式与 JDK 动态代理的相关知识,所以 在继续介绍日志模块中 JDBC 调试功能的实现之前,先来简单介绍一下代理模式以及 JDK 动态 代理的实现和原理。

        定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用;

       目的:(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的
不必要复杂性;
(2)通过代理对象对原有的业务增强;

              Mybatis源码学习第一课---基础支撑层日志模块分析_第7张图片

   3.4.1 代理模式带来便利

    1) 作为中介解耦客户端和真实对象,保护真实对象安全;(房屋中介)
    2) 防止直接访问目标对象给系统带来的不必要复杂性;(海外代购,SSH)
    3) 对业务进行增强,增强点多样化如:前入、后入、异常;(AOP)

   3.4.2 静态代理解析

       Mybatis源码学习第一课---基础支撑层日志模块分析_第8张图片

       其中, Subject 是程序中的业务逻辑接口, Rea!Subject 是实现了 Su均ect 接口的真正业务类, Proxy 是实现了 Subject 接口的代理类,其中封装了 Rea!Subject 对象。在程序中不会直接调动 RealSubject 对象的方法,而是使用 Proxy 对象实现相关功能。 Proxy.operation()方法的实现会调 用 RealSubject 对象的 operation()方法执行真正的业务逻辑,但是处理完业务逻辑, Proxy.operation()会在 RealSubj巳ct.operation()方法调用前后进行预处理和相关的后置处理。这就 是所谓的“代理模式”。
         使用代理模式可以控制程序对 RealSubject 对象的访问,或是在执行业务处理的前后进行相 关的预处理和后置处理。 代理模式还可以用于实现延迟加载的功能,我们知道查询数据库是一个耗时的操作,而有些时候查询到的数据也并没有真正被程序使用。 延迟加载功能就可以有效 地避免这种浪费,系统访问数据库时,首先可以得到一个代理对象,此时并没有执行任何数据 库查询操作,代理对象中自然也没有真正的数据,当系统真正需要使用数据时,再调用代理对 象完成数据库查询井返回数据。 MyBatis 中延迟加载功能的大致原理也是如此。另外,代理对 象可以协调真正 RealSubject 对象与调用者之间的关系,在一定程度上实现了解祸的效果。

        上面介绍的这种代理模式也被称为“静态代理模式”,这是因为在编译阶段就要为每个 RealSubject 类创建创建一个 Proxy 类, 当需要代理的类很多时,这就会出现大量的 Proxy 类。 熟悉 Java 编程的读者可能会说,我们可以使用 JDK 动态代理解决这个问题。 JDK 动态代 理的核心是 InvocationHandler 接口

       3.4.3 JDK 动态代理

        对于需要相同代理行为的业务类,只需要提供一个 InvocationHandler 实现即可。在程序运行时, JDK 会为每个 Rea!Subject 类动态生成代理类井加载到虚拟机中,之后创建对应的代理对象。

        动态代理图:

          Mybatis源码学习第一课---基础支撑层日志模块分析_第9张图片

这里提供一个 InvocationHandler 的示例实现,代码如下:
  

public interface UserService {
    void addUser(User user);
}
public class UserServiceImpl implements UserService {

    @Override
    public void addUser(User user) {
        System.out.println("用户导入成功,用户数据为"+user.toString());
    }
}

/**
 * jdk动态代理
 * Created by dukun on 2019/10/17.
 */
public class UserServiceInterceptor implements InvocationHandler{

    private Object realObj;//真正的业务对象,需要被代理的对象

    public Object getRealObj() {
        return realObj;
    }

    public void setRealObj(Object realObj) {
        this.realObj = realObj;
    }

    public UserServiceInterceptor(Object realObj) {
        this.realObj = realObj;
    }

    /**
     *
     * @param proxy 代理对象,不能直接调用,不然出现递归
     * @param method 每个方法对象,如:sing() act(),每个方法都会调用一次
     * @param args 方法中的参数
     * @return 调用的方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(null!=args && args.length>0 && args[0]  instanceof User){
            User user = (User) args[0];
            if(user.getAge()<18){
                throw  new RuntimeException("年龄必须大于等于18");
            }
        }
        Object ret=method.invoke(realObj,args);// 真实对象,方法的参数
        System.out.println("数据库操作成功");
        return ret;
    }
}
 public static void main(String[] args) {
        User user = new User();
        user.setAddress("huatai");
        user.setAge(19);
        user.setName("dukun");
        UserService userService = new UserServiceImpl();
        //要代理的对象作为入参 对其进行增强
        UserServiceInterceptor userServiceInterceptor = new UserServiceInterceptor(userService);
        System.out.println("数据库操作开始------");
        UserService proxy =userServiceInterceptor.getProxy();
        proxy.addUser(user);
        System.out.println("数据库操作结束------");
        
    }

          JDK 动态代理的实现原理是动态创建代理类井通过指定 类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。当调用代理 对象时,会调用 InvokerHandler.invoke()方法,并最终调用真正业务对象的相应方法。 JDK 动态 代理不仅在 MyBatis 的多个模块中都有所涉及, 在很多开源框架中也能看到其身影。
 

3.4.4 日志模块JDBC包类图

        Mybatis源码学习第一课---基础支撑层日志模块分析_第10张图片

           在MyBatis 的日志模块中有一个Jdbc包,它并不是将日志信息通过JDBC保存到数据库中, 而是通过 JDK 动态代理的方式,将 JDBC 操作通过指定的日志框架打印出来。这个功能通常在 开发阶段使用,它可以输出 SQL 语句、用户传入的绑定参数、 SQL 语句影响行数等等信息,对 调试程序来说是非常重要的。
        BaseJdbcLogger 是一个抽象类,它是 Jdbc 包下其他 Logger 类的父类如上图;

      BaseJdbcLogger 中核心宇段的含义如下:

  //保存preparestatment中常用的set方法(占位符赋值)
  protected static final Set SET_METHODS = new HashSet<>();
  //保存preparestatment中常用的执行sql语句的方法
  protected static final Set EXECUTE_METHODS = new HashSet<>();

  //保存preparestatment中set方法的键值对
  private final Map columnMap = new HashMap<>();

  //保存preparestatment中set方法的key值
  private final List columnNames = new ArrayList<>();
  //保存preparestatment中set方法的value值
  private final List columnValues = new ArrayList<>();

  protected Log statementLog;// 用 于输出日 志的 Log 对象
  protected int queryStack;//记录了 SQL 的层数, 用 于格式化输出 SQL

       在 BaseJdbcLogger 中定义了 SET_METHODS 和 EXECUTE_METHODS 两个 Set

static {
    SET_METHODS.add("setString");
    SET_METHODS.add("setNString");
    SET_METHODS.add("setInt");
    ......
    SET_METHODS.add("setObject");
    SET_METHODS.add("setNull");

    EXECUTE_METHODS.add("execute");
    EXECUTE_METHODS.add("executeUpdate");
    EXECUTE_METHODS.add("executeQuery");
    EXECUTE_METHODS.add("addBatch");
  }
   ConnectionLogger是 Connection 的代理类,在Connection 做了增强用了打印 sql语句和生成PreparedStatement。

       ConnectionLogger 继承了 BaseJdbcLogger 抽象类, 其中封装了 Connection 对象井同时实现 了 lnvocationHandler 接口。 ConnectionLogger. wlnstance()方法为会为其封装自q Connection 对象 创建相应的代理对象,具体代码如下:

public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
    InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
    ClassLoader cl = Connection.class.getClassLoader();
    return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
  }

ConnectionLogger.invoke() 方法是 代 理对象 的 核 心 方法,它为 prepareStatement()、 prepareCall()、 createStatement()等方法提供了代理,具体实现如下:
 

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {

 //真正的连接对象
  private final Connection connection;

  private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.connection = conn;
  }

  @Override
  //对连接的增强
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
    	//如果是从Obeject继承的方法直接忽略
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      //如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句
      //并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
         //打印sql语句日志
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }  
        //获得  PreparedStatement 对象    
        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);//打印sql语句
        }        
        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 {
       //其他方法则直接调用底层 Connection 对象的相应方法
        return method.invoke(connection, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

      PreparedStatementLogger 中封装了 PreparedStatement 对象,也继承了 BaseJdbcLogger 抽象类井实现InvocationHandler 接 口 。 PreparedStatementLogger.newlnstance() 方法 的 实 现 与 ConnectionLogger.newlnstance()方法类似, 这里不再赘述,感兴趣的读者可以参考源码。 PreparedStatementLogger.invoke() 方法会 为 E姐CUTE_METHODS 集合 中的 方法 、 SET_METHODS 集合中的方法、 getResultSet()等方法提供代理, 具体代码如下:
 

public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {

  private final PreparedStatement statement;

  private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.statement = stmt;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      //.如采调用的是从 Object 继承的方法,则直接调用,不做任何其他处理( 略 )
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }          
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);
        }
        clearColumnInfo();//清空 BaseJdbcLogger 中定义的三个 column*集合
        if ("executeQuery".equals(method.getName())) {
          //如果调用 executeQuery ()方法, 则为 ResultSet 创建代理对象
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          //不是 executeQuery ()方法则直接返回结果
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        //如果调用 SET_METHODS 集合中的方法, 则通过 setColumn ()方法记录到 BaseJdbcLogger 中定义
        //  的三个 column*集合
        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())) {
        ///如果调用 getResultSet ()方法,则为 ResultSet i1J 建代理对象
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else if ("getUpdateCount".equals(method.getName())) {
        //如果调用 getUpdateCount ( )方法,则通过 日志框架输出其结果
        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);
    }
  }

         StatementLogger 的实现与 PreparedStatement类似, 不再赘述,请读者参考源码学习 。 ResultSetLogger 中封装了 ResultSet 对象,也继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口 。 ResultSetLogger 中定义的宇段如下:

 

public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {

  private static Set BLOB_TYPES = new HashSet<>();//超大字段类型
  private boolean first = true;//是否是返回结果第一行
  private int rows;//统计行数
  private final ResultSet rs;//真正的ResultSet类
  private final Set blobColumns = new HashSet<>();//记录了超大字段的列编号

  static {
    BLOB_TYPES.add(Types.BINARY);
    BLOB_TYPES.add(Types.BLOB);
    BLOB_TYPES.add(Types.CLOB);
    BLOB_TYPES.add(Types.LONGNVARCHAR);
    BLOB_TYPES.add(Types.LONGVARBINARY);
    BLOB_TYPES.add(Types.LONGVARCHAR);
    BLOB_TYPES.add(Types.NCLOB);
    BLOB_TYPES.add(Types.VARBINARY);
  }
  
  private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.rs = rs;
  }

  @Override
  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())) {//针对 ResultSet.next ()方法的处理
        if (((Boolean) o)) {/// /是否还存在下一行数据
          rows++;//统计行数加1
          if (isTraceEnabled()) {
            ResultSetMetaData rsmd = rs.getMetaData();
            final int columnCount = rsmd.getColumnCount();
            if (first) {//如果是第一行数据,则输出表头
              first = false;
              //除了输出表头,还会填充 blobColurnns 集合,记录超大类型的列
              printColumnHeaders(rsmd, columnCount);
            }
            ///输出该行记录,注意会过滤掉blobColurnns 中记录的列,这些列的数据较大,不会输出到日志
            printColumnValues(columnCount);
          }
        } else {
          ///遍历完 ResultSet 之后 ,会输出总函数
          debug("     Total: " + rows, false);
        }
      }
      //清空 BaseJdbcLogger 中的 column*集合
      clearColumnInfo();
      return o;
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

 

你可能感兴趣的:(源码分析)