1. 项目概述嵌入式系统中的条件控制与高效数据获取在嵌入式开发尤其是基于MC68341这类经典微控制器的项目中我们常常面临两个核心挑战如何让程序根据瞬息万变的系统状态如传感器读数、按键事件、通信标志做出快速、准确的分支决策以及如何在有限的存储空间和计算能力下高效地处理复杂的非线性数据转换比如将ADC原始值转换为物理量或者实现一个非线性的控制曲线。这两个挑战的答案就藏在处理器的指令集深处——条件测试与查表插值TBL指令。条件测试是程序控制流的基石。它不像高级语言中的if-else那样直观而是直接与处理器的条件码寄存器CCR的各个状态位如进位C、零Z、负N、溢出V挂钩。通过一条简单的条件分支指令如BCC、BNE程序就能基于一次算术或逻辑运算的结果决定是顺序执行还是跳转这是实现任何复杂逻辑判断的基础。而TBL指令则是将“空间换时间”这一思想发挥到极致的利器。它允许我们在内存中预先存储一个函数或映射关系的离散采样点即“表”当输入一个介于这些采样点之间的值时TBL指令能自动进行线性插值计算快速返回一个近似结果。这对于实现三角函数、对数、温度补偿曲线等计算密集型任务在缺乏硬件浮点单元的MCU上是提升性能的关键手段。本文将深入拆解MC68341手册中关于这两部分的核心内容。我不会止步于翻译手册而是结合我多年在8/16位MCU上“摸爬滚打”的经验带你理解为什么要这样设计如何在项目中实际应用它们以及在实际编程中会遇到哪些“坑”和应对技巧。无论你是正在维护一个古老的68341项目还是想深入理解嵌入式底层编程的精髓这篇文章都将提供从原理到实战的完整视角。2. 条件测试程序决策的微观逻辑2.1 条件码寄存器CCR与状态位解析在深入条件测试之前我们必须先理解它的裁判——条件码寄存器。在MC68341以及大多数68000家族处理器中CCR是状态寄存器SR的低8位。它就像程序运行时的“仪表盘”实时反映上一次算术或逻辑操作的结果。几个关键位决定了程序流的走向C进位位指示无符号数运算的最高位发生了进位加法或借位减法。这对于处理多精度算术比如64位加法用32位寄存器实现至关重要。Z零位当操作结果的所有位都为0时置位。这是判断两个数是否相等最直接的标志。N负位/符号位设置为与结果的最高位符号位相同。用于判断有符号数的正负。V溢出位指示有符号数运算的结果超出了寄存器能表示的范围。例如两个正数相加得到了负数或两个负数相加得到了正数V位就会被置位。注意很多初学者会混淆C进位和V溢出。简单来说C关心的是无符号数的“位溢出”而V关心的是有符号数的“值溢出”。例如0xFF 0x01在8位运算中结果0x00会产生进位C1但对有符号数-1 1 0而言并未溢出V0。每一次ADD、SUB、CMP、MOVE到CCR、AND、OR等指令执行后这些标志位都会根据结果被更新。后续的条件分支指令如BCC、BEQ正是通过检查这些位的组合状态来决定是否跳转。2.2 条件测试助记符与逻辑表达式详解MC68341手册中的表5-12是理解条件测试的钥匙。它不仅仅是一个列表更是一套完整的逻辑判断体系。我们将其拆解并加入实际编程语义助记符条件编码逻辑测试 (基于CCR)编程语义常用于...TTrue00001无条件执行总是跳转FFalse00010从不执行占位或特殊用途HIHigh0010C • Z无符号数大于 (C0且Z0)LSLow or Same0011C Z无符号数小于或等于 (C1或Z1)CCCarry Clear0100C无符号数比较中“大于或等于”加法无进位/减法无借位CSCarry Set0101C无符号数比较中“小于”加法有进位/减法有借位NENot Equal0110Z结果不等于零两数不相等EQEqual0111Z结果等于零两数相等VCOverflow Clr1000V有符号数运算未溢出VSOverflow Set1001V有符号数运算溢出PLPlus1010N有符号数结果为正或零 (N0)MIMinus1011N有符号数结果为负 (N1)GEGreater or Eq1100N • V N • V有符号数大于或等于 (N和V相同)LTLess Than1101N • V N • V有符号数小于 (N和V不同)GTGreater Than1110N • V • Z N • V • Z有符号数大于 (Z0且N和V相同)LELess or Equal1111Z N • V N • V有符号数小于或等于 (Z1或N和V不同)核心逻辑解析HI/LS vs GT/LE这是最容易出错的地方。HI和LS用于无符号数比较而GT和LE用于有符号数比较。在C语言中if (a b)如果a和b是unsigned int编译后很可能对应BHI如果是int则对应BGT。用错会导致比较结果完全错误。GE/LT的逻辑N • V N • V这个表达式等价于“N异或V的结果为0”。为什么因为(N•V)表示N和V都为1(N•V)表示N和V都为0这两种情况都满足NV。当有符号数运算(A-B)的结果N和V相同时说明结果可靠地反映了大小关系且结果非负N0或为负N1但未溢出因此A B。GT/LE的Z位作用GT在GE的基础上增加了Z0的条件即排除了相等的情况。LE则是LT或EQ的并集。2.3 条件指令的实战应用与陷阱规避理解了条件码我们来看如何用它们控制程序。除了最常用的Bcc条件分支还有DBcc条件递减循环、Scc条件置位字节和TRAPcc条件陷阱。1. 高效循环与边界检查DBcc指令是编写紧凑循环的神器。它先进行条件测试如果条件为假cc不满足则对指定的数据寄存器进行减1操作若结果不为-1则进行相对跳转。MOVE.W #100-1, D0 ; 循环计数器初始化为99循环100次 LOOP_START: ; ... 循环体代码 ... DBF D0, LOOP_START ; 当D0减到-1时跳出循环这里DBF是DBRA的别名条件为FFalse意味着总是先执行减1和跳转直到D0变为-1。这比用SUBQ和Bcc两条指令实现更高效。2. 条件置位的妙用Scc指令可以根据条件测试结果将目标操作数的整个字节设置为全1$FF或全0$00。这在实现布尔标志或小型查找表时非常有用。CMP.B #100, D1 ; 比较D1与100 SGT D2 ; 如果D1100有符号则D2$FF否则D2$00 ; 现在D2可以作为一个布尔值使用3. 实战避坑指南指令执行不影响CCRMOVE指令除非目标为CCR/SR、LEA、PEA、JMP、JSR等指令不影响CCR。如果你在CMP之后使用了这些指令再执行Bcc分支判断的依据可能已经不是你以为的那个比较结果了。务必确保条件分支紧跟在影响CCR的指令之后。TRAPcc的独特之处手册提到FFalse条件对Bcc不可用但TRAPcc可用。TRAPF意味着“永不陷阱”看似无用但在某些调试或特定协议场景下可以作为预留的指令槽或用于动态修改代码将TRAPF在内存中改为TRAPxx来动态激活陷阱# 1. 概述本文我们来分享 MyBatis 的日志模块对应logging包。如下图所示在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中简单介绍了这个模块如下无论在开发测试环境中还是在线上生产环境中日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等。MyBatis 作为一个设计优良的框架除了提供详细的日志输出信息还要能够集成多种日志框架其日志模块的一个主要功能就是集成第三方日志框架。本文涉及的类如下图所示下面我们逐小节来分享。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); }logConstructor静态属性使用的 Log 的构造方法。在1处基于尝试的方式初始化logConstructor属性。而尝试的顺序就是使用 Log 的实现类的优先级。也就是说如果多个 Log 的实现类同时存在的情况下按照 Slf4J Commons Logging Log4J2 Log4J Jdk Logging No Logging的顺序选择对应的 Log 实现类。#tryImplementation(Runnable runnable)方法尝试初始化logConstructor属性。代码如下// LogFactory.java private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }如果logConstructor为空则执行runnable进行初始化。如果logConstructor非空则忽略。#useSlf4jLogging()方法尝试使用 Slf4J 。代码如下// LogFactory.java public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); }在该方法内部会调用#setImplementation(Class? extends Log implClass)方法尝试初始化logConstructor。代码如下// 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); } }通过反射的方式创建implClass对应的 Log 对象。如果创建成功则意味着implClass存在可以使用。否则会抛出异常。其它#useCommonsLogging()、#useLog4J2Logging()、#useLog4JLogging()、#useJdkLogging()、#useNoLogging()方法代码类似就不重复解析了。2.2 getLog#getLog(...)方法获得 Log 对象。代码如下// 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. 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 框架的接口基本一致。3.1 Log4jImplorg.apache.ibatis.logging.log4j.Log4jImpl实现 Log 接口Log4j 实现类。代码如下// Log4jImpl.java public class Log4jImpl implements Log { /** * Log 对象 */ private final Log log; public Log4jImpl(String clazz) { // 获得 Log 对象 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.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); } }在构造方法中log属性通过org.apache.log4j.Logger#getLogger(String name)方法获得 Log 对象。所以最终使用的 Log 实现类是 Log4j 提供的。其它实现类和 Log4jImpl 的思路一致所以本文就不重复解析了。感兴趣的胖友可以自己看看。4. 代理在logging包中我们可以看到jdbc包基于 JDBC 调试打印执行的 SQL 等等。这块内容我们在后续的文章中详细解析。而jdbc包中的类会调用logging包中的类打印日志。但是实际上logging包中的类使用的是代理模式打印的日志是交给commons-logging、log4j2等等日志框架。可能这么说有点抽象我们直接看一个例子。BaseJdbcLogger 类// BaseJdbcLogger.java public abstract class BaseJdbcLogger { /** * Log 对象 */ protected Log log; // ... 省略其它属性 public BaseJdbcLogger(Log log) { this.log log; } }在 BaseJdbcLogger 中有个log属性。而它的实现类例如 ConnectionLogger // ConnectionLogger.java public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler { /** * Connection 对象 */ private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog) { super(statementLog); // 设置 log 属性 this.connection conn; } Override public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { // ... 省略代码 } }在 ConnectionLogger 中虽然log属性但是通过构造方法传入的statementLog参数进行设置。那么这个statementLog参数是怎么创建的呢在org.apache.ibatis.logging.jdbc.ConnectionLogger#newInstance方法代码如下// ConnectionLogger.java public static Connection newInstance(Connection conn, Log statementLog) { InvocationHandler handler new ConnectionLogger(conn, statementLog); ClassLoader cl Connection.class.getClassLoader(); return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler); }通过LogFactory#getLog方法获得 Log 对象。代码如下// 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); } }这样log属性最终会是logConstructor创建的 Log 对象。 可能胖友会问为什么要这么绕呢直接使用 Log 对应的实现类不就好了么从目前来看确实是这样。不过在commons-logging包中提供了org.apache.commons.logging.impl.Jdk14Logger类可以适配java.util.logging。所以通过这样的方式可以更好的适配多种日志框架。5. 适配器在logging包中还有commons-logging、log4j2、slf4j等等包里面都是适配器。例如说commons-logging包下的JakartaCommonsLoggingImpl类代码如下// JakartaCommonsLoggingImpl.java public class JakartaCommonsLoggingImpl implements Log { private final Log log; public JakartaCommonsLoggingImpl(String clazz) { // 获得 commons-logging Log 对象 log LogFactory.getLog(clazz); } 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); } }在构造方法中log属性通过org.apache.commons.logging.LogFactory#getLog(String name)方法获得commons-logging的 Log 对象。所以最终使用的 Log 实现类是commons-logging提供的。其它适配器和 JakartaCommonsLoggingImpl 的思路一致所以本文就不重复解析了。感兴趣的胖友可以自己看看。6. 总结总的来说logging包整体结构如下FROM 《Mybatis3.3.x技术内幕四五鼠闹东京之执行器Executor设计原本》最上层是 Log 接口。接着是 Log 接口的适配器例如 Log4jImpl、Slf4jImpl 等等。接着是 Log 接口的代理例如 ConnectionLogger、PreparedStatementLogger 等等。在jdbc包下我们会详细解析。最后是 LogFactory 工厂负责创建对应的 Log 对象。
嵌入式MCU条件测试与查表插值指令的底层原理与应用
发布时间:2026/6/23 8:38:01
1. 项目概述嵌入式系统中的条件控制与高效数据获取在嵌入式开发尤其是基于MC68341这类经典微控制器的项目中我们常常面临两个核心挑战如何让程序根据瞬息万变的系统状态如传感器读数、按键事件、通信标志做出快速、准确的分支决策以及如何在有限的存储空间和计算能力下高效地处理复杂的非线性数据转换比如将ADC原始值转换为物理量或者实现一个非线性的控制曲线。这两个挑战的答案就藏在处理器的指令集深处——条件测试与查表插值TBL指令。条件测试是程序控制流的基石。它不像高级语言中的if-else那样直观而是直接与处理器的条件码寄存器CCR的各个状态位如进位C、零Z、负N、溢出V挂钩。通过一条简单的条件分支指令如BCC、BNE程序就能基于一次算术或逻辑运算的结果决定是顺序执行还是跳转这是实现任何复杂逻辑判断的基础。而TBL指令则是将“空间换时间”这一思想发挥到极致的利器。它允许我们在内存中预先存储一个函数或映射关系的离散采样点即“表”当输入一个介于这些采样点之间的值时TBL指令能自动进行线性插值计算快速返回一个近似结果。这对于实现三角函数、对数、温度补偿曲线等计算密集型任务在缺乏硬件浮点单元的MCU上是提升性能的关键手段。本文将深入拆解MC68341手册中关于这两部分的核心内容。我不会止步于翻译手册而是结合我多年在8/16位MCU上“摸爬滚打”的经验带你理解为什么要这样设计如何在项目中实际应用它们以及在实际编程中会遇到哪些“坑”和应对技巧。无论你是正在维护一个古老的68341项目还是想深入理解嵌入式底层编程的精髓这篇文章都将提供从原理到实战的完整视角。2. 条件测试程序决策的微观逻辑2.1 条件码寄存器CCR与状态位解析在深入条件测试之前我们必须先理解它的裁判——条件码寄存器。在MC68341以及大多数68000家族处理器中CCR是状态寄存器SR的低8位。它就像程序运行时的“仪表盘”实时反映上一次算术或逻辑操作的结果。几个关键位决定了程序流的走向C进位位指示无符号数运算的最高位发生了进位加法或借位减法。这对于处理多精度算术比如64位加法用32位寄存器实现至关重要。Z零位当操作结果的所有位都为0时置位。这是判断两个数是否相等最直接的标志。N负位/符号位设置为与结果的最高位符号位相同。用于判断有符号数的正负。V溢出位指示有符号数运算的结果超出了寄存器能表示的范围。例如两个正数相加得到了负数或两个负数相加得到了正数V位就会被置位。注意很多初学者会混淆C进位和V溢出。简单来说C关心的是无符号数的“位溢出”而V关心的是有符号数的“值溢出”。例如0xFF 0x01在8位运算中结果0x00会产生进位C1但对有符号数-1 1 0而言并未溢出V0。每一次ADD、SUB、CMP、MOVE到CCR、AND、OR等指令执行后这些标志位都会根据结果被更新。后续的条件分支指令如BCC、BEQ正是通过检查这些位的组合状态来决定是否跳转。2.2 条件测试助记符与逻辑表达式详解MC68341手册中的表5-12是理解条件测试的钥匙。它不仅仅是一个列表更是一套完整的逻辑判断体系。我们将其拆解并加入实际编程语义助记符条件编码逻辑测试 (基于CCR)编程语义常用于...TTrue00001无条件执行总是跳转FFalse00010从不执行占位或特殊用途HIHigh0010C • Z无符号数大于 (C0且Z0)LSLow or Same0011C Z无符号数小于或等于 (C1或Z1)CCCarry Clear0100C无符号数比较中“大于或等于”加法无进位/减法无借位CSCarry Set0101C无符号数比较中“小于”加法有进位/减法有借位NENot Equal0110Z结果不等于零两数不相等EQEqual0111Z结果等于零两数相等VCOverflow Clr1000V有符号数运算未溢出VSOverflow Set1001V有符号数运算溢出PLPlus1010N有符号数结果为正或零 (N0)MIMinus1011N有符号数结果为负 (N1)GEGreater or Eq1100N • V N • V有符号数大于或等于 (N和V相同)LTLess Than1101N • V N • V有符号数小于 (N和V不同)GTGreater Than1110N • V • Z N • V • Z有符号数大于 (Z0且N和V相同)LELess or Equal1111Z N • V N • V有符号数小于或等于 (Z1或N和V不同)核心逻辑解析HI/LS vs GT/LE这是最容易出错的地方。HI和LS用于无符号数比较而GT和LE用于有符号数比较。在C语言中if (a b)如果a和b是unsigned int编译后很可能对应BHI如果是int则对应BGT。用错会导致比较结果完全错误。GE/LT的逻辑N • V N • V这个表达式等价于“N异或V的结果为0”。为什么因为(N•V)表示N和V都为1(N•V)表示N和V都为0这两种情况都满足NV。当有符号数运算(A-B)的结果N和V相同时说明结果可靠地反映了大小关系且结果非负N0或为负N1但未溢出因此A B。GT/LE的Z位作用GT在GE的基础上增加了Z0的条件即排除了相等的情况。LE则是LT或EQ的并集。2.3 条件指令的实战应用与陷阱规避理解了条件码我们来看如何用它们控制程序。除了最常用的Bcc条件分支还有DBcc条件递减循环、Scc条件置位字节和TRAPcc条件陷阱。1. 高效循环与边界检查DBcc指令是编写紧凑循环的神器。它先进行条件测试如果条件为假cc不满足则对指定的数据寄存器进行减1操作若结果不为-1则进行相对跳转。MOVE.W #100-1, D0 ; 循环计数器初始化为99循环100次 LOOP_START: ; ... 循环体代码 ... DBF D0, LOOP_START ; 当D0减到-1时跳出循环这里DBF是DBRA的别名条件为FFalse意味着总是先执行减1和跳转直到D0变为-1。这比用SUBQ和Bcc两条指令实现更高效。2. 条件置位的妙用Scc指令可以根据条件测试结果将目标操作数的整个字节设置为全1$FF或全0$00。这在实现布尔标志或小型查找表时非常有用。CMP.B #100, D1 ; 比较D1与100 SGT D2 ; 如果D1100有符号则D2$FF否则D2$00 ; 现在D2可以作为一个布尔值使用3. 实战避坑指南指令执行不影响CCRMOVE指令除非目标为CCR/SR、LEA、PEA、JMP、JSR等指令不影响CCR。如果你在CMP之后使用了这些指令再执行Bcc分支判断的依据可能已经不是你以为的那个比较结果了。务必确保条件分支紧跟在影响CCR的指令之后。TRAPcc的独特之处手册提到FFalse条件对Bcc不可用但TRAPcc可用。TRAPF意味着“永不陷阱”看似无用但在某些调试或特定协议场景下可以作为预留的指令槽或用于动态修改代码将TRAPF在内存中改为TRAPxx来动态激活陷阱# 1. 概述本文我们来分享 MyBatis 的日志模块对应logging包。如下图所示在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中简单介绍了这个模块如下无论在开发测试环境中还是在线上生产环境中日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等。MyBatis 作为一个设计优良的框架除了提供详细的日志输出信息还要能够集成多种日志框架其日志模块的一个主要功能就是集成第三方日志框架。本文涉及的类如下图所示下面我们逐小节来分享。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); }logConstructor静态属性使用的 Log 的构造方法。在1处基于尝试的方式初始化logConstructor属性。而尝试的顺序就是使用 Log 的实现类的优先级。也就是说如果多个 Log 的实现类同时存在的情况下按照 Slf4J Commons Logging Log4J2 Log4J Jdk Logging No Logging的顺序选择对应的 Log 实现类。#tryImplementation(Runnable runnable)方法尝试初始化logConstructor属性。代码如下// LogFactory.java private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }如果logConstructor为空则执行runnable进行初始化。如果logConstructor非空则忽略。#useSlf4jLogging()方法尝试使用 Slf4J 。代码如下// LogFactory.java public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); }在该方法内部会调用#setImplementation(Class? extends Log implClass)方法尝试初始化logConstructor。代码如下// 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); } }通过反射的方式创建implClass对应的 Log 对象。如果创建成功则意味着implClass存在可以使用。否则会抛出异常。其它#useCommonsLogging()、#useLog4J2Logging()、#useLog4JLogging()、#useJdkLogging()、#useNoLogging()方法代码类似就不重复解析了。2.2 getLog#getLog(...)方法获得 Log 对象。代码如下// 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. 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 框架的接口基本一致。3.1 Log4jImplorg.apache.ibatis.logging.log4j.Log4jImpl实现 Log 接口Log4j 实现类。代码如下// Log4jImpl.java public class Log4jImpl implements Log { /** * Log 对象 */ private final Log log; public Log4jImpl(String clazz) { // 获得 Log 对象 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.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); } }在构造方法中log属性通过org.apache.log4j.Logger#getLogger(String name)方法获得 Log 对象。所以最终使用的 Log 实现类是 Log4j 提供的。其它实现类和 Log4jImpl 的思路一致所以本文就不重复解析了。感兴趣的胖友可以自己看看。4. 代理在logging包中我们可以看到jdbc包基于 JDBC 调试打印执行的 SQL 等等。这块内容我们在后续的文章中详细解析。而jdbc包中的类会调用logging包中的类打印日志。但是实际上logging包中的类使用的是代理模式打印的日志是交给commons-logging、log4j2等等日志框架。可能这么说有点抽象我们直接看一个例子。BaseJdbcLogger 类// BaseJdbcLogger.java public abstract class BaseJdbcLogger { /** * Log 对象 */ protected Log log; // ... 省略其它属性 public BaseJdbcLogger(Log log) { this.log log; } }在 BaseJdbcLogger 中有个log属性。而它的实现类例如 ConnectionLogger // ConnectionLogger.java public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler { /** * Connection 对象 */ private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog) { super(statementLog); // 设置 log 属性 this.connection conn; } Override public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { // ... 省略代码 } }在 ConnectionLogger 中虽然log属性但是通过构造方法传入的statementLog参数进行设置。那么这个statementLog参数是怎么创建的呢在org.apache.ibatis.logging.jdbc.ConnectionLogger#newInstance方法代码如下// ConnectionLogger.java public static Connection newInstance(Connection conn, Log statementLog) { InvocationHandler handler new ConnectionLogger(conn, statementLog); ClassLoader cl Connection.class.getClassLoader(); return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler); }通过LogFactory#getLog方法获得 Log 对象。代码如下// 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); } }这样log属性最终会是logConstructor创建的 Log 对象。 可能胖友会问为什么要这么绕呢直接使用 Log 对应的实现类不就好了么从目前来看确实是这样。不过在commons-logging包中提供了org.apache.commons.logging.impl.Jdk14Logger类可以适配java.util.logging。所以通过这样的方式可以更好的适配多种日志框架。5. 适配器在logging包中还有commons-logging、log4j2、slf4j等等包里面都是适配器。例如说commons-logging包下的JakartaCommonsLoggingImpl类代码如下// JakartaCommonsLoggingImpl.java public class JakartaCommonsLoggingImpl implements Log { private final Log log; public JakartaCommonsLoggingImpl(String clazz) { // 获得 commons-logging Log 对象 log LogFactory.getLog(clazz); } 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); } }在构造方法中log属性通过org.apache.commons.logging.LogFactory#getLog(String name)方法获得commons-logging的 Log 对象。所以最终使用的 Log 实现类是commons-logging提供的。其它适配器和 JakartaCommonsLoggingImpl 的思路一致所以本文就不重复解析了。感兴趣的胖友可以自己看看。6. 总结总的来说logging包整体结构如下FROM 《Mybatis3.3.x技术内幕四五鼠闹东京之执行器Executor设计原本》最上层是 Log 接口。接着是 Log 接口的适配器例如 Log4jImpl、Slf4jImpl 等等。接着是 Log 接口的代理例如 ConnectionLogger、PreparedStatementLogger 等等。在jdbc包下我们会详细解析。最后是 LogFactory 工厂负责创建对应的 Log 对象。