MyBatis日志模块解析:集成第三方日志框架与SQL日志打印

MyBatis日志模块解析:集成第三方日志框架与SQL日志打印 1. 项目背景与核心挑战在UI自动化测试领域视觉回归测试一直是保证产品质量的重要环节。传统基于像素对比的方法在实际应用中经常面临一个棘手问题——误报率居高不下。我们团队最近接手的一个电商项目就遇到了典型场景每次版本更新后平均35%的视觉差异报告都是无效告警这些误报主要来自以下方面动态内容区域如推荐商品轮播图合法UI微调如间距1-2px的合规调整浏览器渲染差异不同内核的文本抗锯齿表现环境因素截图时光线条件波动这些狼来了式的误报不仅消耗工程师大量排查时间更严重降低了团队对自动化测试结果的信任度。为此我们决定构建一个能理解语义级差异的智能检测系统目标是将误报率控制在5%以内。2. 技术选型与方案设计2.1 模型架构选择经过对比实验我们最终选择ResNet-50作为基础架构主要基于以下考量残差连接优势对于需要逐层比对UI元素的场景残差结构能有效缓解深层网络梯度消失问题保留更多细节特征计算效率平衡相比VGG等架构在保持较好准确率的同时推理速度提升40%实测单图处理时间120ms迁移学习友好ImageNet预训练权重提供了良好的特征提取基础模型输入层调整为接受双通道图片基准图测试图在特征提取阶段采用共享权重的双分支结构最后通过自定义的差异评估头输出差异概率。2.2 数据准备策略构建高质量训练数据集是本项目的关键难点我们采用了三级数据增强方案# 正样本生成示例真实差异 def generate_real_diff(base_img, element_library): target random.choice(element_library) return cv2.seamlessClone(target, base_img, mask, (x,y), cv2.NORMAL_CLONE) # 负样本生成示例非语义差异 def generate_fake_diff(img): transformations [ lambda x: cv2.GaussianBlur(x, (3,3), 0), lambda x: random_shift(x, max_pixels2), lambda x: adjust_gamma(x, 0.9) ] return random.choice(transformations)(img)最终构建的数据集包含15,000组真实业务UI截图对20,000组合成差异# 1. 概述本文我们来分享 MyBatis 的日志模块对应logging包。如下图所示logging包在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中简单介绍了这个模块如下无论在开发测试环境中还是在线上生产环境中日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等。MyBatis 作为一个设计优良的框架除了提供详细的日志输出信息还要能够集成多种日志框架其日志模块的一个主要功能就是集成第三方日志框架。本文涉及的类如下图所示从图的上面部分我们可以看到非常多的 Logger 类的实现分别对应我们常用的日志框架 Log4j、Slf4j 这就是 MyBatis 对这些日志框架的适配。从图的下面部分我们可以看到 BaseJdbcLogger 以及其四个子类这个不是将日志框架集成到 MyBatis 中而是 MyBatis 通过 JDK 动态代理的方式将 JDBC 的操作打印到日志中即我们正在使用的 SQL 日志。下面让我们开始干源码吧。2. LogFactoryorg.apache.ibatis.logging.LogFactoryLog 工厂类。2.1 构造方法// LogFactory.java /** * Marker to be used by logging implementations that support markers */ public static final String MARKER MYBATIS; /** * 使用的 Log 的构造方法 */ private static Constructor? extends Log logConstructor; static { // 1 逐个尝试判断使用哪个 Log 的实现类即初始化 logConstructor 属性 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }1处通过#tryImplementation(Runnable runnable)方法逐个尝试判断使用哪个 Log 的实现类即初始化logConstructor属性。代码如下// LogFactory.java private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }当logConstructor为空时执行runnable的方法。那么runnable是怎么样的呢以#useSlf4jLogging()方法举例子。代码如下// LogFactory.java public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } private static void setImplementation(Class? extends Log implClass) { try { // 获得参数为 String 的构造方法 Constructor? extends Log candidate implClass.getConstructor(String.class); // 创建 Log 对象 Log log candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) { log.debug(Logging initialized using implClass adapter.); } // 创建成功意味着可以使用设置为 logConstructor logConstructor candidate; } catch (Throwable t) { throw new LogException(Error setting Log implementation. Cause: t, t); } }其它#useXXXLogging()方法也是类似的逻辑。2.2 getLog#getLog(...)静态方法获得 Log 对象。代码如下// LogFactory.java 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); } } public static Log getLog(Class? clazz) { return getLog(clazz.getName()); }2.3 小结LogFactory 负责 MyBatis 使用的日志框架的创建。通过这样的方式可以非常方便的切换日志框架。那么MyBatis 是如何集成这些日志框架的呢答案见 「3. Log」 。3. Logorg.apache.ibatis.logging.LogMyBatis Log 接口。代码如下// Log.java 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); }定义了 Log 的接口方法。Log 的实现类如下图所示Log 的实现类每个实现类代码都非常简单都是调用对应的日志框架的 Logger 的相应方法。以 Slf4jImpl 举例子// Slf4jImpl.java public class Slf4jImpl implements Log { private Log log; public Slf4jImpl(String clazz) { Logger logger LoggerFactory.getLogger(clazz); 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 | NoSuchMethodException e) { // fail-back to Slf4jLoggerImpl } } // Logger is not LocationAwareLogger or slf4j version 1.6 log new Slf4jLoggerImpl(logger); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }在构造方法中创建org.slf4j.Logger对象传入的clazz参数实际就是每个类的全类名。这样这个 Logger 对象就会绑定到对应的类上。在实现的方法中调用对应的 Logger 对象的相应方法。其它实现类也是类似的逻辑。4. BaseJdbcLogger在文章的开头我们已经提到 BaseJdbcLogger 以及其四个子类这个不是将日志框架集成到 MyBatis 中而是 MyBatis 通过 JDK 动态代理的方式将 JDBC 的操作打印到日志中即我们正在使用的 SQL 日志。4.1 BaseJdbcLoggerorg.apache.ibatis.logging.jdbc.BaseJdbcLogger实现 java.lang.reflect.InvocationHandler 接口BaseJdbcLogger 抽象类负责设置日志的参数和将方法调用传递给真正的被代理类。4.1.1 构造方法// BaseJdbcLogger.java /** * Statement 的常用方法 */ protected static final SetString SET_METHODS; /** * Statement 的常用方法 */ protected static final SetString EXECUTE_METHODS new HashSet(); /** * 保存 PreparedStatement 参数和值映射的键值对 */ private final MapObject, Object columnMap new HashMap(); /** * 保存 PreparedStatement 参数 */ private final ListObject columnNames new ArrayList(); /** * 保存 PreparedStatement 参数值 */ private final ListObject columnValues new ArrayList(); /** * Log 对象 */ protected final Log statementLog; /** * ResultSet 日志的层级。默认是 {link ResultSet#CLOSE_CURSORS_AT_COMMIT} */ protected final int resultSetLogLevel; static { SET_METHODS new HashSet(); SET_METHODS.add(setString); 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(setClob); SET_METHODS.add(setObject); SET_METHODS.add(setNull); EXECUTE_METHODS.add(execute); EXECUTE_METHODS.add(executeUpdate); EXECUTE_METHODS.add(executeQuery); EXECUTE_METHODS.add(addBatch); } public BaseJdbcLogger(Log log, int queryStack) { this.statementLog log; // 计算 resultSetLogLevel if (queryStack 0) { this.resultSetLogLevel Log.DEBUG; } else { this.resultSetLogLevel Log.TRACE; } }SET_METHODS静态属性Statement 的常用方法。EXECUTE_METHODS静态属性Statement 的常用方法。columnMap属性保存 PreparedStatement 参数和值映射的键值对。columnNames属性保存 PreparedStatement 参数。columnValues属性保存 PreparedStatement 参数值。statementLog属性Log 对象。resultSetLogLevel属性ResultSet 日志的层级。默认是Log.DEBUG。如果是嵌套的查询则为Log.TRACE。这个属性在 「4.4 ResultSetLogger」 会看到具体用途。4.1.2 invoke#invoke(Object proxy, Method method, Object[] params)实现方法代理方法。代码如下// BaseJdbcLogger.java 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); } // 处理 set 方法 if (SET_METHODS.contains(method.getName())) { if (setNull.equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } return null; // 处理 execute 方法 } else if (EXECUTE_METHODS.contains(method.getName())) { if (executeQuery.equals(method.getName())) { return executeQuery(proxy, method, params); } else if (executeUpdate.equals(method.getName())) { return executeUpdate(proxy, method, params); } else if (execute.equals(method.getName())) { return execute(proxy, method, params); } else if (addBatch.equals(method.getName())) { return addBatch(proxy, method, params); } } // 其它方法直接调用 return method.invoke(proxy, params); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }可以处理的方法类型分成三种情况第一种从 Object 继承的方法直接调用不做拦截。第二种SET_METHODS方法调用#setColumn(Object key, Object value)方法记录参数到columnMap、columnNames、columnValues中。代码如下// BaseJdbcLogger.java private void setColumn(Object key, Object value) { columnMap.put(key, value); columnNames.add(key); columnValues.add(value); }x第三种EXECUTE_METHODS方法调用对应的#executeQuery(...)、#executeUpdate(...)、#execute(...)、#addBatch(...)方法。代码如下// BaseJdbcLogger.java protected Object executeQuery(Object proxy, Method method, Object[] params) throws Throwable { // 打印日志 if (statementLog.isDebugEnabled()) { debug(Executing: removeBreakingWhitespace((String) params[0]), true); } // 执行方法 Object result method.invoke(proxy, params); // 打印日志 if (result instanceof ResultSet) { ResultSet rs (ResultSet) result; return ResultSetLogger.newInstance(rs, statementLog, resultSetLogLevel); } else { return result; } } protected Object executeUpdate(Object proxy, Method method, Object[] params) throws Throwable { // 打印日志 if (statementLog.isDebugEnabled()) { debug(Executing: removeBreakingWhitespace((String) params[0]), true); debug(Parameters: getParameterValueString(), true); } // 执行方法 Object result method.invoke(proxy, params); // 打印日志 if (statementLog.isDebugEnabled()) { debug( Updates: result, true); } return result; } protected Object execute(Object proxy, Method method, Object[] params) throws Throwable { // 打印日志 if (statementLog.isDebugEnabled()) { debug(Executing: removeBreakingWhitespace((String) params[0]), true); debug(Parameters: getParameterValueString(), true); } // 执行方法 Object result method.invoke(proxy, params); // 打印日志 if (statementLog.isDebugEnabled()) { debug( Result: result, true); } return result; } protected Object addBatch(Object proxy, Method method, Object[] params) throws Throwable { // 打印日志 if (statementLog.isDebugEnabled()) { debug(Executing: removeBreakingWhitespace((String) params[0]), true); debug(Parameters: getParameterValueString(), true); } // 执行方法 Object result method.invoke(proxy, params); // 打印日志 if (statementLog.isDebugEnabled()) { debug( Updates: result, true); } return result; }在执行方法的前后打印相应的日志。当然前提是开启了DEBUG日志级别。对于#executeQuery(...)方法会创建 ResultSetLogger 对象对 ResultSet 返回结果进行包装。详细解析见 「4.4 ResultSetLogger」 。第四种其它方法直接调用。4.1.3 其它方法// BaseJdbcLogger.java protected String getParameterValueString() { ListObject typeList new ArrayList(columnValues.size()); ListObject valueList new ArrayList(columnValues.size()); // 遍历 columnValues 数组 for (int i 0; i columnValues.size(); i) { Object value columnValues.get(i); if (value null) { typeList.add(null); valueList.add(null); } else { typeList.add(value.getClass().getName()); valueList.add(value); } } // 拼接参数 StringBuilder output new StringBuilder(); for (int i 0; i valueList.size(); i) { if (i 0) { output.append(, ); } // 拼接类型#值 output.append(typeList.get(i)) .append(() .append(valueList.get(i)) .append()); } return output.toString(); } 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); } } private String prefix(boolean isInput) { char[] buffer new char[isInput ? 2 : 3]; Arrays.fill(buffer, ); buffer[isInput ? 1 : 2] ; return new String(buffer) ; } protected void clearColumnInfo() { columnMap.clear(); columnNames.clear(); columnValues.clear(); } 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(); }4.2 ConnectionLoggerorg.apache.ibatis.logging.jdbc.ConnectionLogger继承 BaseJdbcLogger 抽象类Connection 的日志增强。代码如下// ConnectionLogger.java public final class ConnectionLogger extends BaseJdbcLogger { /** * Connection 对象 */ 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 { // 从 Object 继承的方法直接调用不做拦截 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); // 创建 PreparedStatementLogger 对象 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); // 创建 PreparedStatementLogger 对象 stmt PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; // 创建 Statement 方法 } else if (createStatement.equals(method.getName())) { // 执行方法 Statement stmt (Statement) method.invoke(connection, params); // 创建 StatementLogger 对象 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. * * param conn - the original connection * param statementLog - the statement log * param queryStack - the query stack * 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; } }可以处理的方法类型分成三种情况第一种从 Object 继承的方法直接调用不做拦截。第二种prepareStatement方法打印 SQL 语句创建 PreparedStatementLogger 对象。第三种prepareCall方法打印 SQL 语句创建 PreparedStatementLogger 对象。第四种createStatement方法创建 StatementLogger 对象。第五种其它方法直接调用。#newInstance(Connection conn, Log statementLog, int queryStack)静态方法创建 Connection 的代理对象。代码如下// ConnectionLogger.java 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); }通过 JDK 动态代理的方式创建代理 Connection 对象。这样在调用#prepareStatement(...)、#prepareCall(...)、#createStatement(...)方法时会被拦截进行日志的打印。4.3 StatementLoggerorg.apache.ibatis.logging.jdbc.StatementLogger继承 BaseJdbcLogger 抽象类Statement 的日志增强。代码如下// StatementLogger.java public final class StatementLogger extends BaseJdbcLogger { /** * Statement 对象 */ private final Statement statement; private StatementLogger(Statement 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); } // 清空 columnMap、columnNames、columnValues clearColumnInfo(); if (executeQuery.equals(method.getName())) { 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())) { 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); } } /** * Creates a logging version of a statement. * * param stmt - the statement * param statementLog - the statement log * param queryStack - the query stack * return - the proxy */ public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) { InvocationHandler handler new StatementLogger(stmt, statementLog, queryStack); ClassLoader cl Statement.class.getClassLoader(); return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler); } /** * return the wrapped statement. * * return the statement */ public Statement getStatement() { return statement; } }可以处理的方法类型分成三种情况第一种从 Object 继承的方法直接调用不做拦截。第二种EXECUTE_METHODS方法打印参数执行方法。如果是查询方法会创建 ResultSetLogger 对象。第三种SET_METHODS方法记录设置参数执行方法。第四种getResultSet方法执行方法如果是查询方法会创建 ResultSetLogger 对象。第五种getUpdateCount方法执行方法打印影响行数。第六种其它方法直接调用。#newInstance(Statement stmt, Log statementLog, int queryStack)静态方法创建 Statement 的代理对象。代码如下// StatementLogger.java public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) { InvocationHandler handler new StatementLogger(stmt, statementLog, queryStack); ClassLoader cl Statement.class.getClassLoader(); return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler); }通过 JDK 动态代理的方式创建代理 Statement 对象。这样在调用#execute(...)、#executeQuery(...)、#executeUpdate(...)、#addBatch(...)、#getResultSet()等方法时会被拦截进行日志的打印。4.4 ResultSetLoggerorg.apache.ibatis.logging.jdbc.ResultSetLogger继承 BaseJdbcLogger 抽象类ResultSet 的日志增强。代码如下// ResultSetLogger.java public final class ResultSetLogger extends BaseJdbcLogger { /** * ResultSet 对象 */ private final ResultSet rs; /** * 读取行数 */ private int rows; /** * 第一行可能有数据可能没有数据 */ private boolean first true; 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 { // 从 Object 继承的方法直接调用不做拦截 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } // 执行方法 Object o method.invoke(rs, params); // 如果执行的是 next 方法 if (next.equals(method.getName())) { // 返回 true 说明有记录 if ((Boolean) o) { // 增加 rows rows; // 如果是 DEBUG 级别并且是第一条则打印表头 if (isTraceEnabled()) { ResultSetMetaData rsmd rs.getMetaData(); final int columnCount rsmd.getColumnCount(); if (first) { first false; // 打印列头 printColumnHeaders(rsmd, columnCount); } // 打印该行的记录 printColumnValues(columnCount); } // 返回 false 说明无记录 } else { // 如果是 DEBUG 级别并且非第一条则打印总行数 debug( Total: rows, false); } } // 清空 columnMap、columnNames、columnValues clearColumnInfo(); return o; } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException { StringBuilder row new StringBuilder(); row.append( Columns: ); for (int i 1; i columnCount; i) { if (i 1) { row.append(, ); } row.append(rsmd.getColumnLabel(i)); } trace(row.toString(), false); } private void printColumnValues(int columnCount) { StringBuilder row new StringBuilder(); row.append( Row: ); for (int i 1; i columnCount; i) { if (i 1) { row.append(, ); } try { if (rs.getObject(i) ! null) { row.append(rs.getString(i)); } else { row.append(NULL); } } catch (SQLException e) { row.append(Cannot read value: e.getMessage()); } } trace(row.toString(), false); } /** * Creates a logging version of a ResultSet. * * param rs - the ResultSet to proxy * param statementLog - the statement log * param queryStack - the query stack * return - the ResultSet with logging */ public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) { InvocationHandler handler new ResultSetLogger(rs, statementLog, queryStack); ClassLoader cl ResultSet.class.getClassLoader(); return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler); } /** * Get the wrapped result set. * * return the resultSet */ public ResultSet getResultSet() { return rs; } }可以处理的方法类型分成三种情况第一种从 Object 继承的方法直接调用不做拦截。第二种next方法执行方法后打印该行记录。如果是第一条记录则打印表头。第三种其它方法直接调用。#newInstance(ResultSet rs, Log statementLog, int queryStack)静态方法创建 ResultSet 的代理对象。代码如下// ResultSetLogger.java public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) { InvocationHandler handler new ResultSetLogger(rs, statementLog, queryStack); ClassLoader cl ResultSet.class.getClassLoader(); return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler); }通过 JDK 动态代理的方式创建代理 ResultSet 对象。这样在调用#next()方法时会被拦截进行日志的打印。4.5 PreparedStatementLoggerorg.apache.ibatis.logging.jdbc.PreparedStatementLogger继承 BaseJdbcLogger 抽象类PreparedStatement 的日志增强。代码如下// PreparedStatementLogger.java public final class PreparedStatementLogger extends BaseJdbcLogger { /** * PreparedStatement 对象 */ 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 {