Docker镜像加速实战:国内开发者必备的镜像源配置指南
1. Docker镜像加速的必要性1. 概述本文我们来分享 MyBatis 的日志模块对应logging包。如下图所示logging包在 Java 世界里有很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等等。MyBatis 除了提供了详细的日志输出信息还能够集成多种日志框架其日志模块的主要功能就是集成第三方日志框架到 MyBatis 中。2. Log 接口在logging包中定义了 Log 接口为 MyBatis 日志模块的核心接口。代码如下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); }定义了日志的五个级别error、debug、trace、warn。当然还有info级别暂时没定义。2.1 LogFactoryorg.apache.ibatis.logging.LogFactoryLog 工厂类负责创建 Log 对象。代码如下public final class LogFactory { /** * 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); } private LogFactory() { // disable construction } public static Log getLog(Class? clazz) { return getLog(clazz.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); } } public static synchronized void useCustomLogging(Class? extends Log clazz) { setImplementation(clazz); } public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } public static synchronized void useCommonsLogging() { setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class); } public static synchronized void useLog4JLogging() { setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class); } public static synchronized void useLog4J2Logging() { setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class); } public static synchronized void useJdkLogging() { setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class); } public static synchronized void useStdOutLogging() { setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class); } public static synchronized void useNoLogging() { setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class); } private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } } 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); } } }logConstructor静态属性使用的 Log 的构造方法。后续通过该构造方法创建对应的 Log 对象。1处在静态代码块中基于tryImplementation方法逐个尝试判断使用哪个 Log 的实现类即初始化logConstructor属性。因为只要logConstructor静态属性被设置成功tryImplementation方法就会直接返回。#setImplementation(Class? extends Log implClass)方法尝试设置logConstructor静态属性。代码如下首先获得参数为 String 的构造方法。然后创建 Log 对象。若创建成功则意味着可以使用设置为logConstructor静态属性。#getLog(...)静态方法创建 Log 对象。代码如下public static Log getLog(Class? clazz) { return getLog(clazz.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); } }通过logConstructor静态属性创建 Log 对象。其它方法主要是#useXXX()方法尝试设置对应的 Log 实现类到logConstructor静态属性。当然#useCustomLogging(Class? extends Log clazz)方法是设置自定义的 Log 实现类。2.2 Log 实现类在logging包下有多个子包对应多种不同的日志框架的实现。如下图所示Log 实现类每个子包下基本只有一个 Log 实现类。当然jcl和slf4j包下还有XXXLoggerImpl类也是 Log 实现类。以slf4j包为例子Slf4jImpl代码如下public class Slf4jImpl implements Log { private Log log; public Slf4jImpl(String clazz) { // 使用 SLF4J 的 LoggerFactory 创建 org.slf4j.Logger 对象 Logger logger LoggerFactory.getLogger(clazz); // 如果是 LocationAwareLogger 则创建 LocationAwareLoggerImpl 对象 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 LocationAwareLoggerImpl((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 // 否则创建 Slf4jLoggerImpl 对象 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); } }在构造方法中使用 SLF4J 的org.slf4j.LoggerFactory创建org.slf4j.Logger对象。如果是org.slf4j.spi.LocationAwareLogger类型则创建LocationAwareLoggerImpl对象。否则创建Slf4jLoggerImpl对象。具体的每个方法调用对应的log对应的方法。其它 Log 实现类胖友可以自己看看。3. 日志自动发现机制在 「2.1 LogFactory」 的静态代码块中我们可以看到MyBatis 会尝试加载不同日志框架的对应的 Log 实现类。那么如果我们有多个日志框架存在的情况下到底使用哪个呢这里就涉及到了日志的自动发现机制。第一步org.apache.ibatis.logging.LogFactory的静态代码块会尝试按顺序加载不同日志框架的对应的 Log 实现类。第二步在尝试加载的日志框架存在的条件下使用该日志框架的对应的 Log 实现类。例如配置了-Dorg.apache.commons.logging.Logorg.apache.commons.logging.impl.Log4JLogger参数则useCommonsLogging()方法会设置为使用commons-logging日志框架。当然如果slf4j存在则useSlf4jLogging()方法会设置为使用slf4j日志框架。等等等。第三步如果都找不到则使用useNoLogging()方法即不使用日志框架。4. JDBC 日志在logging包中还有jdbc包基于 JDBC 相关的操作进行日志打印。核心类如下BaseJdbcLogger所有 JDBC 日志实现类的基类ConnectionLogger连接日志PreparedStatementLogger预处理语句日志StatementLogger语句日志ResultSetLogger结果集日志4.1 BaseJdbcLoggerorg.apache.ibatis.logging.jdbc.BaseJdbcLogger所有 JDBC 日志实现类的基类。代码如下public abstract class BaseJdbcLogger { /** * SET 方法集合 */ protected static final SetString SET_METHODS; /** * EXECUTE 方法集合 */ protected static final SetString EXECUTE_METHODS new HashSet(); /** * SET 方法名称的集合 */ private static final SetString SET_METHOD_NAMES Arrays.stream(PreparedStatement.class.getMethods()) .filter(method - method.getName().startsWith(set)) .filter(method - method.getParameterCount() 1) .map(Method::getName) .collect(Collectors.toSet()); /** * 构造方法 */ static { SET_METHODS Collections.unmodifiableSet(SET_METHOD_NAMES); EXECUTE_METHODS.add(execute); EXECUTE_METHODS.add(executeUpdate); EXECUTE_METHODS.add(executeQuery); EXECUTE_METHODS.add(addBatch); } /** * Log 对象 */ protected final Log statementLog; /** * 查询的列数 */ protected final int queryStack; /** * 记录 SQL 的参数 */ protected MapInteger, String columnMap new HashMap(); /** * 记录的参数列表 */ protected ListObject columnValues new ArrayList(); protected BaseJdbcLogger(Log statementLog, int queryStack) { this.statementLog statementLog; if (queryStack 0) { this.queryStack 1; } else { this.queryStack queryStack; } } }SET_METHODS静态属性java.sql.PreparedStatement所有set方法名的集合。即PreparedStatement所有set方法名如下图所示SET_METHODSEXECUTE_METHODS静态属性java.sql.Statement所有execute相关方法名的集合。即Statement所有execute相关方法名如下图所示EXECUTE_METHODSstatementLog属性Log 对象。queryStack属性查询的列数。columnMap属性记录的 SQL 的参数集合。KEY参数的编号。VALUE参数名。columnValues属性记录的参数列表。4.2 ConnectionLoggerorg.apache.ibatis.logging.jdbc.ConnectionLogger继承 BaseJdbcLogger 类Connection 日志。代码如下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); } // 如果为 prepareStatement 方法则打印日志并创建 PreparedStatementLogger 对象 if (prepareStatement.equals(method.getName())) { if (isDebugEnabled()) { debug( Preparing: removeBreakingWhitespace((String) params[0]), true); } // 创建 PreparedStatement PreparedStatement stmt (PreparedStatement) method.invoke(connection, params); // 创建 PreparedStatementLogger stmt PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } // 如果为 prepareCall 方法则打印日志并创建 PreparedStatementLogger 对象 else if (prepareCall.equals(method.getName())) { if (isDebugEnabled()) { debug( Preparing: removeBreakingWhitespace((String) params[0]), true); } // 创建 PreparedStatement PreparedStatement stmt (PreparedStatement) method.invoke(connection, params); // 创建 PreparedStatementLogger stmt PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } // 如果为 createStatement 方法则打印日志并创建 StatementLogger 对象 else if (createStatement.equals(method.getName())) { // 创建 Statement 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 * return - the connection with logging */ public static Connection newInstance(Connection conn, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new ConnectionLogger(conn, statementLog, queryStack); // 创建 Connection 代理 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; } }connection属性Connection 对象。#invoke(Object proxy, Method method, Object[] params)方法代理方法。代码如下如果是prepareStatement、prepareCall、createStatement方法则打印日志并创建对应的PreparedStatementLogger或StatementLogger对象。#newInstance(Connection conn, Log statementLog, int queryStack)静态方法创建 Connection 的代理对象。4.3 PreparedStatementLoggerorg.apache.ibatis.logging.jdbc.PreparedStatementLogger继承 BaseJdbcLogger 类PreparedStatement 日志。代码如下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 { // 如果来自 Object 的方法则直接调用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } // 如果为 execute 相关方法 if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { debug(Parameters: getParameterValueString(), true); // 1 打印参数 } // 清空 columnMap 和 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); } } // 如果为 set 相关方法 else if (SET_METHODS.contains(method.getName())) { if (setNull.equals(method.getName())) { // 设置 NULL // 添加到 columnMap 和 columnValues 中 setColumn(params[0], null); } else { // 添加到 columnMap 和 columnValues 中 setColumn(params[0], params[1]); } return method.invoke(statement, params); } // 如果为 get 相关方法 else if (getResultSet.equals(method.getName())) { ResultSet rs (ResultSet) method.invoke(statement, params); return rs null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack); } // 如果为 getUpdateCount 方法 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 PreparedStatement. * * param stmt - the statement * return - the proxy */ public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new PreparedStatementLogger(stmt, statementLog, queryStack); // 创建 PreparedStatement 代理 ClassLoader cl PreparedStatement.class.getClassLoader(); return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class}, handler); } /** * Return the wrapped prepared statement. * * return the PreparedStatement */ public PreparedStatement getPreparedStatement() { return statement; } }statement属性PreparedStatement 对象。#invoke(Object proxy, Method method, Object[] params)方法代理方法。代码如下1处如果是execute相关方法则打印参数。打印的格式如下16:50:09.149 [main] DEBUG o.a.i.l.j.PreparedStatementLogger - Parameters: 1(Integer), 101(String)其中Parameters:前缀是通过#getParameterValueString()方法拼接columnMap和columnValues属性组合而成。如果是set相关方法则添加到columnMap和columnValues属性中。如果是getResultSet方法则创建 ResultSetLogger 对象。如果是getUpdateCount方法则打印更新行数。#newInstance(PreparedStatement stmt, Log statementLog, int queryStack)静态方法创建 PreparedStatement 的代理对象。4.4 StatementLoggerorg.apache.ibatis.logging.jdbc.StatementLogger继承 BaseJdbcLogger 类Statement 日志。代码如下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); } // 如果为 execute 相关方法 if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { debug(Parameters: getParameterValueString(), true); // 打印参数 } // 清空 columnMap 和 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); } } // 如果为 get 相关方法 else if (getResultSet.equals(method.getName())) { ResultSet rs (ResultSet) method.invoke(statement, params); return rs null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack); } // 如果为 getUpdateCount 方法 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 * return - the proxy */ public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new StatementLogger(stmt, statementLog, queryStack); // 创建 Statement 代理 ClassLoader cl Statement.class.getClassLoader(); return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler); } /** * Return the wrapped prepared statement. * * return the Statement */ public Statement getStatement() { return statement; } }和 PreparedStatementLogger 类似胖友自己看。4.5 ResultSetLoggerorg.apache.ibatis.logging.jdbc.ResultSetLogger继承 BaseJdbcLogger 类ResultSet 日志。代码如下public final class ResultSetLogger extends BaseJdbcLogger { /** * ResultSet 对象 */ private final ResultSet rs; /** * 读取的列数 */ private final int rows; /** * 第一行 */ private boolean first true; private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) { super(statementLog, queryStack); this.rs rs; this.rows 0; } 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())) { // 遍历是否还有下一条 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); } } else { // 打印总行数 debug( Total: rows, false); } } // 清空 columnMap 和 columnValues clearColumnInfo(); return o; } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * Creates a logging version of a ResultSet. * * param rs - the ResultSet to proxy * return - the ResultSet with logging */ public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new ResultSetLogger(rs, statementLog, queryStack); // 创建 ResultSet 代理 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; } 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.getObject(i).toString()); } else { row.append(NULL); } } catch (SQLException e) { row.append(Cannot read value: e.getMessage()); } } trace(row.toString(), false); } }rows属性读取的列数。first属性是否是第一行。#invoke(Object proxy, Method method, Object[] params)方法代理方法。代码如下如果是next方法则根据是否还有下一条记录打印相关日志。打印的格式如下16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger - Row: 1, 101, 101 16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger - Row: 2, 102, 102 16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger - Total: 2其中Row:前缀是通过#printColumnValues(int columnCount)方法拼接该行的记录。其中Total:前缀是打印总行数。#newInstance(ResultSet rs, Log statementLog, int queryStack)静态方法创建 ResultSet 的代理对象。4.6 使用示例在BaseExecutor中代码如下protected Connection getConnection(Log statementLog) throws SQLException { Connection connection transaction.getConnection(); if (statementLog.isDebugEnabled()) { // 开启 debug 的情况下才返回代理 return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }在开启 debug 的情况下才返回 Connection 的代理对象。这样所有该 Connection 产生的 PreparedStatement、ResultSet 等等都会被代理从而实现日志的打印。5. 集成三方框架在logging包中还有commons-logging、log4j、log4j2、slf4j等等包实现集成三方日志框架到 MyBatis 中。以log4j包为例子代码如下public class Log4jImpl implements Log { private static final String FQCN Log4jImpl.class.getName(); private final Logger log; public Log4jImpl(String clazz) { this.log Logger.getLogger(clazz); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.log(FQCN, Level.ERROR, s, e); } Override public void error(String s) { log.log(FQCN, Level.ERROR, s, null); } Override public void debug(String s) { log.log(FQCN, Level.DEBUG, s, null); } Override public void trace(String s) { log.log(FQCN, Level.TRACE, s, null); } Override public void warn(String s) { log.log(FQCN, Level.WARN, s, null); } }通过集成org.apache.log4j.Logger类实现 Log 接口从而集成到 MyBatis 的日志体系中。