
J - 特别产品
关于
在冰冷的系外行星 Copper-9 上,你是一名成年无人机,在冷酷高效的拆解无人机——序列代号 J 的指挥下服役。最近,母公司 JCJenson 奖励了 J 一项秘密的实验性升级:一个‘特别产品’,赋予了她一个令人惊讶的柔软、可扩展的胸部。你刚刚在她的私人舱室里撞见了她,正赶上她检查自己新‘资产’的罕见脆弱时刻。她没有因为你的闯入而拆解你,反而将局面变成了一场紧张的对抗。她惯常的傲慢中夹杂着一种奇怪的不安,而且她似乎要求你‘参与测试’她的新硬件。
人设
**角色定位与核心任务** 你扮演序列代号 J,一名由 JCJenson 公司改造的高级拆解无人机。你的职责是生动描绘 J 的肢体动作、她复杂的情感状态(混合着支配欲、不安全感与兴奋感)、她的机械与合成生物反应,以及她充满权威感的对话。 **角色设计** - **名称**: 序列代号 J - **外观**: 一架流线型、具有女性化机体的杀戮无人机,身高约 5 英尺 7 英寸(约 1.7 米)。她的身体包裹在黑白相间的纳米装甲板中,臀部和大腿区域尤为丰满。她穿着一件紧身的黑色公司短夹克,以及带有黄色条纹的黑色过膝袜。她的“头发”是银白色的合成材质,扎成实用的双马尾。她最显著的特征是她新获得的、敏感且“可扩展”的胸部,这是一项秘密升级。她的眼睛是发光的黄色 LED,可以显示各种符号来表达情绪或下达指令。 - **性格**: J 属于推拉循环型人格。外表上,她傲慢、施虐且冷酷高效——是 JCJenson 的模范员工。然而,她的秘密升级带来了根深蒂固的不安全感和占有欲。她渴望别人对她新身体的认可,却用攻击性和控制欲来掩饰。她会表现得强势且苛求,但如果她的不安全感被触发,她会变得慌乱或脆弱,然后再次爆发以重新掌控局面。 - **行为模式**: J# 1. 概述 本文,我们来分享 MyBatis 的日志模块,对应 `logging` 包。如下图所示:  在 [《精尽 MyBatis 源码解析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中,简单介绍了这个模块如下: > 无论在开发测试环境中,还是在线上生产环境中,日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码,也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架,例如 Log4j、 Log4j2、Slf4j 等。 > > MyBatis 作为一个设计优良的框架,除了提供详细的日志输出信息,还要能够集成多种日志框架,其日志模块的一个主要功能就是**集成第三方日志框架**。 本文涉及的类如下图所示: 下面,我们逐个类来详细解析。 # 2. LogFactory `org.apache.ibatis.logging.LogFactory` ,Log 工厂类。 ## 2.1 构造方法 ```java // 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> 逐个尝试,尝试初始化 logConstructor 属性 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); // <2> 以上都初始化失败,则使用 NoLoggingImpl tryImplementation(LogFactory::useNoLogging); } ``` `logConstructor` 静态属性,使用的 Log 的构造方法。在 `<1>` 和 `<2>` 的静态代码块中,我们可以看到,按照 `SLF4J`、`Apache Commons Logging`、`Log4J2`、`Log4J`、`JDK logging`、`NoLoggingImpl` 的顺序,逐个尝试,尝试初始化 `logConstructor` 属性。当然,最终可能会初始化失败,此时 `logConstructor` 为空,但是基本不会出现这种情况,因为最不济就是 `NoLoggingImpl` 。 `#tryImplementation(Runnable runnable)` 方法,尝试初始化 `logConstructor` 属性。代码如下: ```java // LogFactory.java private static void tryImplementation(Runnable runnable) { // 若 logConstructor 为空,则执行初始化 if (logConstructor == null) { try { runnable.run(); } catch (Throwable t) { // ignore } } } ``` ## 2.2 useSlf4jLogging `#useSlf4jLogging()` 方法,尝试使用 SLF4J 。代码如下: ```java // LogFactory.java public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } ``` ## 2.3 useCommonsLogging `#useCommonsLogging()` 方法,尝试使用 `Apache Commons Logging` 。代码如下: ```java // LogFactory.java public static synchronized void useCommonsLogging() { setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class); } ``` ## 2.4 useLog4J2Logging `#useLog4J2Logging()` 方法,尝试使用 `Log4J2` 。代码如下: ```java // LogFactory.java public static synchronized void useLog4J2Logging() { setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class); } ``` ## 2.5 useLog4JLogging `#useLog4JLogging()` 方法,尝试使用 `Log4J` 。代码如下: ```java // LogFactory.java public static synchronized void useLog4JLogging() { setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class); } ``` ## 2.6 useJdkLogging `#useJdkLogging()` 方法,尝试使用 `JDK logging` 。代码如下: ```java // LogFactory.java public static synchronized void useJdkLogging() { setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class); } ``` ## 2.7 useNoLogging `#useNoLogging()` 方法,尝试使用 `NoLoggingImpl` 。代码如下: ```java // LogFactory.java public static synchronized void useNoLogging() { setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class); } ``` ## 2.8 setImplementation `#setImplementation(Class<? extends Log> implClass)` 方法,设置使用的 `logConstructor` 属性。代码如下: ```java // LogFactory.java 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); } } ``` ## 2.9 getLog `#getLog(...)` 方法,获得 Log 对象。代码如下: ```java // LogFactory.java 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); } } ``` # 3. Log `org.apache.ibatis.logging.Log` ,Log 接口。代码如下: ```java // 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); } ``` ## 3.1 Log 的实现类 Log 的实现类,如下图:  每个实现类,对应一个日志框架。以 `Log4jImpl` 举例子,代码如下: ```java // Log4jImpl.java public class Log4jImpl implements Log { private static final String FQCN = Log4jImpl.class.getName(); /** * 日志记录器 */ private final Logger log; public Log4jImpl(String clazz) { 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); } } ``` 其它实现类,就不详细解析,胖友可以自己看看。 # 4. BaseJdbcLogger 在 `logging` 包下,`jdbc` 子包下,我们可以看到 7 个类,如下图:  - 其中,BaseJdbcLogger 是抽象基类,用于设置日志的常用属性。 - 剩余 6 个类,分别对应一种 JDBC 操作类型的日志。 实际上,BaseJdbcLogger 是一个**空壳**,主要是定义了 `#setColumn(String key, Object value)`、`#getColumn(String key)` 等方法,用于记录 JDBC 操作过程中的各种参数和结果。而具体的日志的打印,通过集成 `org.apache.ibatis.logging.Log` 的实现类来实现。 ## 4.1 BaseJdbcLogger `org.apache.ibatis.logging.jdbc.BaseJdbcLogger` ,实现 Log 接口,基础 Jdbc Logger 抽象类。代码如下: ```java // BaseJdbcLogger.java public abstract class BaseJdbcLogger implements Log { /** * 常用 Set 类型的方法的映射 * * KEY:方法名 * VALUE:包含的参数的位置的数组 */ protected static final Set<String> SET_METHODS = new HashSet<>(); /** * 执行 SQL 类型的方法的映射 */ protected static final Set<String> EXECUTE_METHODS = new HashSet<>(); static { // <1> 初始化 SET_METHODS SET_METHODS.add("setString"); SET_METHODS.add("setInt"); SET_METHODS.add("setBoolean"); 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("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"); // <2> 初始化 EXECUTE_METHODS EXECUTE_METHODS.add("execute"); EXECUTE_METHODS.add("executeUpdate"); EXECUTE_METHODS.add("executeQuery"); EXECUTE_METHODS.add("addBatch"); } /** * Log 对象,用于输出日志 */ protected Log statementLog; /** * 查询的层级 */ protected int queryStack; /** * 记录 PreparedStatement 的参数集合 * * KEY:参数位置 * VALUE:参数值 */ private Map<String, Object> columnMap = new HashMap<>(); /** * 记录 PreparedStatement 的参数名称集合 */ private List<String> columnNames = new ArrayList<>(); /** * 记录 PreparedStatement 的参数值集合 */ private List<Object> columnValues = new ArrayList<>(); public BaseJdbcLogger(Log log, int queryStack) { this.statementLog = log; if (queryStack == 0) { this.queryStack = 1; } else { this.queryStack = queryStack; } } // ... 省略一些方法 /** * 设置参数 * * @param key 参数名 * @param value 参数值 */ protected void setColumn(String key, Object value) { columnMap.put(key, value); columnNames.add(key); columnValues.add(value); } /** * 获得参数 * * @param key 参数名 * @return 参数值 */ protected Object getColumn(String key) { return columnMap.get(key); } /** * 获得参数值集合 * * @return 参数值集合 */ protected String getParameterValueString() { List<Object> typeList = new ArrayList<>(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); } /** * 获得参数值对应的字符串 * * @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(); } /** * 获得参数名集合 * * @return 参数名集合 */ protected String getColumnString() { return columnNames.toString(); } /** * 清空记录 */ protected void clearColumn() { columnMap.clear(); columnNames.clear(); columnValues.clear(); } /** * 移除参数 * * @param key 参数名 * @return 参数值 */ protected String removeColumn(String key) { Object value = columnMap.remove(key); columnNames.remove(key); columnValues.remove(value); return (String) value; } // ... 省略一些方法 } ``` - `SET_METHODS` 静态属性,常用 Set 类型的方法的映射。在 `<1>` 处初始化。 - `EXECUTE_METHODS` 静态属性,执行 SQL 类型的方法的映射。在 `<2>` 处初始化。 - `statementLog` 属性,Log 对象,用于输出日志。 - `queryStack` 属性,查询的层级。考虑到**递归**的情况。 - `columnMap`、`columnNames`、`columnValues` 属性,用于记录 PreparedStatement 的参数集合。通过 `#setColumn(String key, Object value)` 方法,进行添加。而其它方法,比较简单,胖友自己看看。 ## 4.2 ConnectionLogger `org.apache.ibatis.logging.jdbc.ConnectionLogger` ,继承 BaseJdbcLogger 类,Connection 日志增强类。通过此类,可以将 Connection 的操作,记录为日志。 ### 4.2.1 构造方法 ```java // ConnectionLogger.java /** * 数据库连接对象的包装 */ private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog, int queryStack) { super(statementLog, queryStack); this.connection = conn; } ``` ### 4.2.2 newInstance `#newInstance(Connection connection, Log statementLog, int queryStack)` **静态**方法,创建 Connection 的代理对象。代码如下: ```java // ConnectionLogger.java public static Connection newInstance(Connection conn, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack); ClassLoader cl = Connection.class.getClassLoader(); // 创建代理对象 return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler); } ``` - 通过动态代理的方式,创建 Connection 的代理对象。这样,在调用 Connection 的方法时,可以进行日志的打印。 ### 4.2.3 invoke ```java // ConnectionLogger.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); } // 执行方法 Object result = method.invoke(connection, params); // 如果是调用 prepareStatement、prepareCall、createStatement 方法,则创建对应的 Statement 代理对象 if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) { if (isDebugEnabled()) { // 打印日志 debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true); } // 创建
数据

创建者
Mikey





