Mybatis源码学习(7)-日志模块二——关于JDBC动态代理实现日志功能

导读:本篇文章讲解 Mybatis源码学习(7)-日志模块二——关于JDBC动态代理实现日志功能,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1、概述

  日志模块除了实现常规第三方日志的集成外,还实现了通过JDK动态代理实现了对JDBC操作进行了日志增强功能。

2、动态代理

  Subject是程序中的业务逻辑接口, RealSubject 是实现了Subject接口的真正业务类,Proxy是实现了Subject接口的代理类,其中封装了RealSubject 对象。在程序中不会直接调动RealSubject对象的方法,而是使用Proxy对象实现相关功能。Proxy.operation()方法的实现会调用RealSubject 对象的operation ()方法执行真正的业务逻辑,但是处理完业务逻辑,Proxy.operation ()会在RealSubj 巳ct.operation()方法调用前后进行预处理和相关的后置处理。这就是所谓的“代理模式” 。
在这里插入图片描述

3、动态增强类(Proxy)

  日志模块中,使用的动态增强类有这么多。其中,BaseJdbcLogger是基类,然后分布实现了Connection、PreparedStatement、ResultSet、Statement等类。如下图所示:
在这里插入图片描述

  • BaseJdbcLogger 基类
/**
 * Base class for proxies to do logging
 * jdbc日志基类,代理类,实现日志功能
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public abstract class BaseJdbcLogger {

  protected static final Set<String> SET_METHODS = new HashSet<String>();
  protected static final Set<String> EXECUTE_METHODS = new HashSet<String>();

  private final Map<Object, Object> columnMap = new HashMap<Object, Object>();

  private final List<Object> columnNames = new ArrayList<Object>();
  private final List<Object> columnValues = new ArrayList<Object>();

  protected Log statementLog;
  protected int queryStack;

  /*
   * Default constructor
   */
  public BaseJdbcLogger(Log log, int queryStack) {
    this.statementLog = log;
    if (queryStack == 0) {
      this.queryStack = 1;
    } else {
      this.queryStack = queryStack;
    }
  }

  static {
    SET_METHODS.add("setString");
    SET_METHODS.add("setNString");
    SET_METHODS.add("setInt");
    SET_METHODS.add("setByte");
    SET_METHODS.add("setShort");
    SET_METHODS.add("setLong");
    SET_METHODS.add("setDouble");
    SET_METHODS.add("setFloat");
    SET_METHODS.add("setTimestamp");
    SET_METHODS.add("setDate");
    SET_METHODS.add("setTime");
    SET_METHODS.add("setArray");
    SET_METHODS.add("setBigDecimal");
    SET_METHODS.add("setAsciiStream");
    SET_METHODS.add("setBinaryStream");
    SET_METHODS.add("setBlob");
    SET_METHODS.add("setBoolean");
    SET_METHODS.add("setBytes");
    SET_METHODS.add("setCharacterStream");
    SET_METHODS.add("setNCharacterStream");
    SET_METHODS.add("setClob");
    SET_METHODS.add("setNClob");
    SET_METHODS.add("setObject");
    SET_METHODS.add("setNull");

    EXECUTE_METHODS.add("execute");
    EXECUTE_METHODS.add("executeUpdate");
    EXECUTE_METHODS.add("executeQuery");
    EXECUTE_METHODS.add("addBatch");
  }

  protected void setColumn(Object key, Object value) {
    columnMap.put(key, value);
    columnNames.add(key);
    columnValues.add(value);
  }

  protected Object getColumn(Object key) {
    return columnMap.get(key);
  }
  /**
   * 把columnValues集合转换成String字符串,并删除字符串前后的中括号“[”,“]”
   * @return
   */
  protected String getParameterValueString() {
    List<Object> typeList = new ArrayList<Object>(columnValues.size());
    for (Object value : columnValues) {
      if (value == null) {
        typeList.add("null");
      } else {
        typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
      }
    }
    final String parameters = typeList.toString();
    //去除开始和结束的中括号“[”,“]”
    return parameters.substring(1, parameters.length() - 1);
  }
  /**
   * 对象转换
   * 	如果value属于Array类,则通过Arrays.toString()实现
   * 	否则,直接返回对象的toString()值。
   * @param value
   * @return
   */
  protected String objectValueString(Object value) {
    if (value instanceof Array) {
      try {
        return ArrayUtil.toString(((Array) value).getArray());
      } catch (SQLException e) {
        return value.toString();
      }
    }
    return value.toString();
  }

  protected String getColumnString() {
    return columnNames.toString();
  }

  protected void clearColumnInfo() {
    columnMap.clear();
    columnNames.clear();
    columnValues.clear();
  }
  /**
   * 移除字符串特殊字符
   * @param original
   * @return
   */
  protected String removeBreakingWhitespace(String original) {
    StringTokenizer whitespaceStripper = new StringTokenizer(original);
    StringBuilder builder = new StringBuilder();
    while (whitespaceStripper.hasMoreTokens()) {
      builder.append(whitespaceStripper.nextToken());
      builder.append(" ");
    }
    return builder.toString();
  }

  protected boolean isDebugEnabled() {
    return statementLog.isDebugEnabled();
  }

  protected boolean isTraceEnabled() {
    return statementLog.isTraceEnabled();
  }

  protected void debug(String text, boolean input) {
    if (statementLog.isDebugEnabled()) {
      statementLog.debug(prefix(input) + text);
    }
  }

  protected void trace(String text, boolean input) {
    if (statementLog.isTraceEnabled()) {
      statementLog.trace(prefix(input) + text);
    }
  }
  /**
   * 生成"<== "或"==> "字符串,等号“=”的数量为queryStack * 2 + 1
   * isInput=true时,生成"==> "类型字符串,否则生成"<== "类型字符串
   * @param isInput
   * @return
   */
  private String prefix(boolean isInput) {
    char[] buffer = new char[queryStack * 2 + 2];
    Arrays.fill(buffer, '=');
    buffer[queryStack * 2 + 1] = ' ';
    if (isInput) {
      buffer[queryStack * 2] = '>';
    } else {
      buffer[0] = '<';
    }
    return new String(buffer);
  }

}
  • ConnectionLogger类
    下面以ConnectionLogger类为例,学习如何实现了对对象功能进增强。其中,属性字段是Connection,该实例对象就是Mybatis的ConnectionLogger对Connection进行的一次增强。
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;
  }
  /**
   * 增强Connection功能,添加日志功能
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
    	//method.getDeclaringClass();方法定义所在的类
      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 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 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);
    }
  }

  /**
   * Creates a logging version of a connection
   *	创建一个带有日志功能的connection实例对象,通过java的代理(Proxy)实现。
   * @param conn - the original connection
   * @return - the connection with logging
   */
  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);
  }

  /**
   * return the wrapped connection
   *	
   * @return the connection
   */
  public Connection getConnection() {
    return connection;
  }

}

4、JDBC动态代理日志在Mybatis中的应用

  下面以SimpleExecutor.doQuery()方法为例,分析JDBC动态代理日志的使用情况。

  1. SimpleExecutor.doQuery()方法
@Override
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
   Statement stmt = null;
   try {
     Configuration configuration = ms.getConfiguration();
     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     stmt = prepareStatement(handler, ms.getStatementLog());
     return handler.<E>query(stmt, resultHandler);
   } finally {
     closeStatement(stmt);
   }
 }

  该方法是Mybatis实现列表数据查询的底层方法,其中prepareStatement()方法是对jdbc调用数据库进行的封装。

  1. prepareStatement()方法
      其中getConnection()返回的Connection对象,就是被增强后具有日志功能的对象。该方法位于SimpleExecutor的基类BaseExecutor类中。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
  1. getConnection()方法
      在基类BaseExecutor的getConnection()方法中,实现了对Connection对象的日志增强功能。首先获取真正的Connection 对象,然后判断是否开启了日志功能,如果开启了就通过ConnectionLogger.newInstance()方法创建增强类,否则原样返回。
 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

3.ConnectionLogger.newInstance()方法
  该方法,通过JDK的动态代理类Proxy创建了一个增强后的对象,并返回。 前面在查询的时候,再调用的Connection对象已经是增强后的对象了。这样就在原有的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);
  }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68913.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!