MyBatis3源码深度解析(十九)MyBatis日志实现

文章目录

  • 前言
  • 第七章 MyBatis日志实现
    • 7.1 Java日志体系
      • 7.1.1 常用日志框架
      • 7.1.2 Java日志发展史
      • 7.1.3 日志接口与日志实现的绑定
    • 7.2 MyBatis日志实现
      • 7.2.1 Log接口
      • 7.2.2 LogFactory工厂
      • 7.2.3 MyBatis日志自动查找
      • 7.2.4 MyBatis日志类型配置
    • 7.3 小结

前言

日志是Java应用中必不可少的部分,它能够记录系统运行状况,有助于准确定位系统异常,不同的项目可能会使用不同的日志框架。

在整合了MyBatis的项目中,经常可以在日志文件中看到打印出来的SQL语句,那本节就来研究一下MyBatis的日志实现。

第七章 MyBatis日志实现

7.1 Java日志体系

7.1.1 常用日志框架

目前比较常用的日志框架有:

  • Log4j:Apache项目,是基于Java的日志记录工具。
  • Log4j 2:Log4j的升级产品。
  • Commons Logging:Apache项目,是一套Java日志接口。
  • SLF4J:也是一套Java日志接口。
  • Logback:SLF4J日志接口的实现。
  • JUL:JDK1.4之后提供的日志实现。

在实际项目中,通常会依赖很多第三方工具包或者框架,如果这些工具包或框架使用不同的日志实现,那么项目就要为每种不同的日志框架维护一套单独的配置,这会造成项目日志输出模块相当混乱。

然而,在实际项目中,又通常只维护一套日志配置。这个冲突是如何解决的?可以从Java日志发展史中得到答案。

7.1.2 Java日志发展史

  1. 1996年,Log4j问世,成为Apache基金会项目中的一员,近乎成为Java社区的日志标准;
  2. 2002年,JDK1.4发布,内置JUL(Java Util Logging)日志实现。
  3. 2002年,Apache推出JCL(Jakarta Commons Logging),定义了一套日志接口。
  4. 2006年,Log4j的作者离开Apache,先后创立了SLF4J(Simple logging Facade for Java,是一套日志接口)和Logback(SLF4J日志接口的实现)两个项目。
  5. 2012年,Apache为避免被Logback反超,重写了Log4j,成立了新的项目Log4j2。Log4j2具有Logback的所有特性。

总结一下,现如今Java日志划分为两大阵营:JCL阵营和SLF4J阵营。

JCL和SLF4J属于日志接口,提供统一的日志操作规范,输入日志功能由具体的日志实现框架(例如Log4j、Logback等)完成。 如图:

基于这样的关系,所有第三方工具包或者框架只需要确定自身符合日志接口定义的规范,就可以适用任何一种日志实现,这样就解决了日志实现冲突的问题。

7.1.3 日志接口与日志实现的绑定

日志接口需要与具体的日志实现框架进行绑定。

例如,项目使用JCL作为日志接口,则需要在classpath下新增一个commons-logging.properties文件,通过该文件指定日志框架的具体实现。例如:

# commons-logging.properties
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger

如果需要修改具体的日志实现,则只需要修改org.apache.commons.logging.Log属性值,应用代码无序做任何调整。

SLF4J框架中定义了日志接口,各个日志实现框架只需要遵循这个接口,就能够做到日志系统间的无缝兼容。适用SLF4J接口的实现框架又有两种模式:桥接模式和适配器模式。

  • 使用桥接模式的日志实现框架有:jcl-over-SLF4J(把对JCL的调用桥接到SLF4J)、jul-to-SLF4J(把对JUL的调用桥接到SLF4J)、log4j-over-SLF4J(把对Log4j的调用桥接到SLF4J)。

  • 使用适配器模式的日志实现框架有:Logback(推荐使用,性能比Log4j好,且支持变参占位符日志输出方式)、SLF4J-logj12(对Log4j的适配器)、SLF4J-jdk14(对JUL的适配器)。

在应用程序中,如果使用SLF4J接口编写日志输出代码,除了引入SLF4J-api.jar依赖,还需要根据底层日志框架不同,同时引入对应的依赖:

  • 底层使用Log4j:slf4j-log412.jar、log4j.jar
  • 底层使用Logback:logback-classic.jar、logback-core.jar
  • 底层使用JUL:slf4f-jdk14.jar、

7.2 MyBatis日志实现

7.2.1 Log接口

MyBatis通过Log接口定义日志操作规范,其定义如下:

源码1org.apache.ibatis.logging.Logpublic 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);
}

MyBatis针对不同的日志框架提供对Log接口对应的实现,如图所示:

其中包括JCL、JUL、Log4j2、Log4j、No Logging(不输出任何日志)、SLF4J、Stdout(将日志输出到标准输出设备,如控制台)。

7.2.2 LogFactory工厂

MyBatis的Log实例采用工厂模式创建,即LogFactory类,该类提供了一系列useXXXLogging()方法,用于指定具体使用哪种日志实现类输出日志。

源码2org.apache.ibatis.logging.LogFactorypublic final class LogFactory {// ......// 自定义日志实现public static synchronized void useCustomLogging(Class<? extends Log> clazz) {setImplementation(clazz);}// 使用SLF4J框架输出日志public static synchronized void useSlf4jLogging() {setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);}// 使用JCL框架输出日志public static synchronized void useCommonsLogging() {setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);}// 使用Log4j框架输出日志@Deprecatedpublic static synchronized void useLog4JLogging() {setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);}// 使用Log4j2框架输出日志public static synchronized void useLog4J2Logging() {setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);}// 使用JUL框架输出日志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);}
}

由 源码2 可知,每一个useXXXLogging()方法都会调用setImplementation()方法,指定日志实现类。

源码3org.apache.ibatis.logging.LogFactoryprivate static Constructor<? extends Log> logConstructor;private static void setImplementation(Class<? extends Log> implClass) {try {// 获取日志实现类的Constructor对象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.");}// 记录当前使用的日志实现类的Constructor对象logConstructor = candidate;} catch (Throwable t) {throw new LogException("Error setting Log implementation.  Cause: " + t, t);}
}

由 源码3 可知,setImplementation()方法首先获取日志实现类对应的Constructor对象,然后根据该对象创建一个Log实例,并将该对象保存在logConstructor属性中。

接下来以Slf4jImpl实现类为例,研究一下MyBatis的日志实现:

源码4org.apache.ibatis.logging.slf4j.Slf4jImplpublic class Slf4jImpl implements Log {private Log log;public Slf4jImpl(String clazz) {Logger logger = LoggerFactory.getLogger(clazz);// ......log = new Slf4jLoggerImpl(logger);}// isDebugEnabled ...// isTraceEnabled ...// error ...// debug ...// trace ...// warn ...
}

由 源码4 可知,在Slf4jImpl的构造方法中,通过LoggerFactory获取SLF4J框架中的Logger对象,然后创建了一个Slf4jLoggerImpl实例。

源码5org.apache.ibatis.logging.slf4j.Slf4jLoggerImplclass Slf4jLoggerImpl implements Log {private final Logger log;public Slf4jLoggerImpl(Logger logger) {log = logger;}// isDebugEnabled ...// isTraceEnabled ...// error ...// debug ...// trace ...// warn ...
}

由 源码5 可知,在Slf4jLoggerImpl的构造方法中,将日志输出相关操作委托给SLF4J框架中的Logger对象来完成。

因此,在调用LogFactory的useSlf4jLogging()方法时,就确定了使用org.apache.ibatis.logging.slf4j.Slf4jImpl实现类输出日志,而Slf4jImpl实现类又将日志输出操作委托给SLF4J框架的Logger对象,这样就确定了使用SLF4J框架输出日志。

下面是使用SLF4J日志框架的案例:

@Test
public void testLog() {// 指定使用SLF4J框架输出日志LogFactory.useSlf4jLogging();// 获取Log实例并输出日志Log log = LogFactory.getLog(Slf4jImpl.class);log.debug("test Slf4jImpl");
}

控制台打印执行结果:

SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.

打印这样的结果是因为,SLF4J本身只是一个日志门面,在没有具体的日志实现时,默认使用NOP(no-operation)实现,即不打印任何日志。

7.2.3 MyBatis日志自动查找

MyBatis日志模块设计地比较巧妙的是,当未指定使用哪种日志实现时,MyBatis将按照顺序查找classpath下的日志框架相关的jar包。如果classpath下有对应的日志包,则使用该日志框架打印日志。

源码6org.apache.ibatis.logging.LogFactorypublic final class LogFactory {static {tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}// ......private static void tryImplementation(Runnable runnable) {// 先判断logConstructor属性是否为空// 如果为空,则说明还没有指定日志实现框架,继续往下查找// 如果不为空,则说明已经指定了日志实现框架,不再继续往下查找if (logConstructor == null) {try {runnable.run();} catch (Throwable t) {// ignore}}}
}

由 源码6 可知,在LogFactory类中有一个初始代码块,按照一定的顺序调用tryImplementation()方法,以确定日志实现类,该方法的参数是一个Runnable匿名对象,在run()方法中调用LogFactory中的静态useXXXLogging()方法。

需要注意的是,这里虽然使用了Runnable接口,但跟多线程无关,仅仅是把run()方法作为一个普通方法调用。 因此,在该静态代码块中,首先通过tryImplementation()方法尝试调用LogFactory的useSlf4jLogging()方法使用SLF4J日志框架。而在useSlf4jLogging()方法中,会获取SLF4J日志框架的Logging对象。

如果classpath中存在SLF4J日志框架的依赖,则会将LogFactory的logConstructor属性指定为org.apache.ibatis.logging.slf4j.Slf4jImpl类对应的Constructor对象。而tryImplementation()方法中首先会判断logConstructor属性是否为空,因此后续设置日志实现类的逻辑不会再执行。

如果classpath中不存在SLF4J日志框架的依赖,则useSlf4jLogging()方法会抛出ClassNotFoundException和NoClassDefFoundException异常(它们都实现了Throwable接口)。由 源码6 可知,tryImplementation()方法会捕获这两个异常,但不做任何处理,仅仅只是捕获而已。

紧接着,调用tryImplementation(LogFactory::useCommonsLogging);查找classpath下是否有JCL日志框架的相关依赖。

总结一下,MyBatis查找日志框架的顺序为:SLF4J→JCL→Log4j2→Log4j→JUL→No Logging。如果classpath下不存在任何日志框架的依赖,则使用NoLoggingImpl日志实现类,即不输出任何日志。

7.2.4 MyBatis日志类型配置

在使用MyBatis时,还可以通过MyBatis主配置文件中的<setting name="logImpl" value="SLF4J"/>参数指定使用哪种日志框架。

源码7org.apache.ibatis.builder.xml.XMLConfigBuilderprivate void loadCustomLogImpl(Properties props) {// 读取配置文件中的logImpl参数// 并将其转换为对应的Class对象Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));// 注册到Configuration对象中configuration.setLogImpl(logImpl);
}
源码8org.apache.ibatis.builder.BaseBuilderprotected <T> Class<? extends T> resolveClass(String alias) {try {return alias == null ? null : resolveAlias(alias);} // catch ...
}
protected <T> Class<? extends T> resolveAlias(String alias) {// 从别名注册器中获取Class对象// 说明logImpl参数配置的是一个别名return typeAliasRegistry.resolveAlias(alias);
}
源码9org.apache.ibatis.session.Configurationpublic class Configuration {// ......protected Class<? extends Log> logImpl;public Configuration() {// ......// 定义了日志框架实现类的别名typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);// ......}public Class<? extends Log> getLogImpl() {return logImpl;}public void setLogImpl(Class<? extends Log> logImpl) {if (logImpl != null) {this.logImpl = logImpl;// 设置日志实现类LogFactory.useCustomLogging(this.logImpl);}}
}

由 源码7-9 可知,当MyBatis框架启动时,会解析主配置文件的logImpl参数,并通过别名注册器TypeAliasRegistry将参数值转换为对应的Class对象,再调用Configuration对象的setLogImpl()将日志实现类的Class对象保存在logImpl属性中,并调用LogFactory的useCustomLogging()方法设置日志实现类。

由此可见,logImpl参数配置的是日志实现类的别名,这些别名的定义在Configuration对象的构造方法中完成,该参数的可选值有:SLF4J、COMMONS_LOGGING、LOG4J、LOG4J2、JDK_LOGGING、STDOUT_LOGGING、NO_LOGGING。

7.3 小结

第七章到此就梳理完毕了,本章的主题是:MyBatis日志实现。回顾一下本章的梳理的内容:

(十九)Java日志体系、MyBatis日志实现

更多内容请查阅分类专栏:MyBatis3源码深度解析

第八章主要学习:动态SQL实现原理。主要内容包括:

  • 动态SQL的使用;
  • SqlSource与BoundSql原理;
  • LanguageDriver原理;
  • SqlNode原理;
  • 动态SQL解析过程;
  • #{}和${}的区别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/762473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【合合TextIn】深度解析智能文档处理技术与应用

目录 一、智能文档处理介绍 二、文档格式解析 三、图像增强技术解析 四、传统文字识别OCR技术解析 五、深度学习OCR技术解析 六、深度学习版面分析技术解析 七、文档分类 八、信息抽取 九、系统集成&#xff1a;将IDP处理后的数据集成到企业系统 结论 一、智能文档处…

机器学习-06-无监督算法-01-划分聚类Kmeans算法

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中无监督算法&#xff0c;包括划分聚类等。 参考 数据分析实战 | K-means算法——蛋白质消费特征分析 欧洲48国英文名称的来龙去脉及其国旗动画 Kmeans在线动态演示 本门课程的目标 完成一个特定行业的…

如何与手机共享笔记本电脑的互联网?这里提供详细步骤

这篇文章介绍了如何通过将手机变成Wi-Fi热点来与手机共享笔记本电脑的互联网连接。 如何共享笔记本电脑的互联网连接 你可以通过Wi-Fi或有线共享笔记本电脑的数据连接,具体取决于你的设置。 Windows Windows允许你通过ICS共享你的互联网连接。ICS,或称互联网连接共享,是W…

ARM Coresight 系列文章 11.1 -- CoreSight Cortex-M33 CTI 详细介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 CTI 的工作原理CTI 主要特点CTI的使用场景CTI 的工作原理 CTI 允许不同的调试和追踪组件之间基于特定事件进行交互。例如,当一个断点被命中时,CTI 可以用来触发内存的追踪捕捉或者外部仪器的行为,反之亦然。这种…

【华大 HC32L110】调用`printf`和串口接收中断的冲突问题解决

华大单片机 HC32L110调用printf和串口接收中断的冲突问题解决&#xff0c;经过查找是官方库 去使能了 串口的接收功能&#xff0c;记录解决问题的过程 目录 1.硬件MCU资料2. printf和串口接收中断的冲突解决3.重新封装 fputc 函数4.查找问题&#xff0c;发现是官方库配置有误5.…

无线局域网——wlan

目录 一.wlan的含义和发展 二.wlan技术带来的挑战 1.企业办公场景多样 2.位置速度的要求 3.安全的要求 4.规范的挑战 三.家庭和企业不同的部署需求 1.胖AP模式组网 2.AC瘦AP模式组网 3.组网模式的不同 四.三层隧道转发实验 1.拓扑 2.AP上线 核心交换机vlan ​编辑…

探索海外市场舆情:云手机助力企业赢得全球竞争

在全球化的趋势下&#xff0c;越来越多的企业将目光投向海外市场&#xff0c;迎接着无尽的商机与挑战。然而&#xff0c;随之而来的是境外市场舆情的复杂变化&#xff0c;对企业的声誉和发展带来了潜在风险。如何准确、及时地掌握境外市场的舆情动向&#xff0c;成为了企业必须…

Midjourney发布新特性风格参考

1. 引言 最近&#xff0c;Midjourney 推出了Style Reference V2.0 即功能更加强大的风格参考工具&#xff0c;该工具可以让大家参考其他图像的风格&#xff0c;生成与参考图像风格保持一致&#xff0c;与文本提示词语义内容保持一致的图像。它与图像提示类似&#xff0c;但是只…

Day03-数据库管理(事务管理,用户管理,MySQL8的部分新特性)

文章目录 Day03 数据库管理学习目标1. 事务管理1.1 事务的概念1.2 事务的特性1.3 语法1.4 事务的并发问题1.5 事务隔离级别1.6 设置和查看隔离级别 2 用户管理2.1 创建删除用户2.2 权限管理2.2.1 权限赋予的原则2.2.2 权限赋予2.2.3 权限回收2.2.4 登录管理 3. MySQL8的部分新特…

Java中的I/O讲解(超容易理解)(下篇)

如果想观看更多Java内容 可上我的个人主页关注我&#xff0c;地址子逸爱编程-CSDN博客https://blog.csdn.net/a15766649633?typeblog 使用工具 IntelliJ IDEA Community Edition 2023.1.4 使用语言 Java8 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0…

简易指南:国内ip切换手机软件怎么弄

在网络访问受到地域限制的情况下&#xff0c;使用国内IP切换手机软件可以帮助用户轻松访问被屏蔽的内容&#xff0c;扩展网络体验。以下是虎观代理小二分享的使用国内IP切换手机软件的简易指南。并提供一些注意事项。 如何在手机上使用国内IP切换软件 步骤一&#xff1a;选择I…

idea2023 运行多 springboot 实例

概要 1、修改idea运行多实例&#xff08;本地测试负载&#xff09; 你可能用到其他 1、改造项目缓存token 至redis 支持负载均衡部署 SpringSecurity6.0RedisJWTMP基于token认证功能开发&#xff08;源码级剖析可用于实际生产项目&#xff09;_springsecurity redis管理token…

嵌入式学习第二十九天!(数据结构的概念、单向链表)

数据结构&#xff1a; 1. 定义&#xff1a; 一组用来保存一种或者多种特定关系的数据的集合&#xff08;组织和存储数据&#xff09; 1. 程序设计&#xff1a; 将现实中大量而复杂的问题以特定的数据类型和特定的数据结构存储在内存中&#xff0c;并在此基础上实现某个特定的功…

安装调试kotti_ai:AI+互联网企业级部署应用软件包@riscv+OpenKylin

先上结论&#xff1a;riscvOpenKylin可以安装pyramidkottikotti_ai 但是paddle_serving_client无法安装&#xff0c;项目的AI实现部分需要改用其它方法&#xff0c;比如onnx。最终onnx也没有装成&#xff0c;只好用飞桨自己的推理。 安装kotti pip install kotti 安装kotti和…

postman测试文件上传接口

java里编写一个导入接口&#xff0c;接口的入参就是一个文件&#xff0c;此时接口中使用的接收参数是 用postman测试这个接口时&#xff1a;Body里选择类型是form-data Header中自动修改了Content-Type 如果既要上传文件&#xff0c;又要输入参数可使用如下方式&#xff1a; 此…

训练svm并部署树莓派

训练svm并部署树莓派 开发环境1. 准备数据集2. 训练模型3. 部署模型开发环境 vscode python 3.8 用到的库: scikit-learn==1.3.2 pickle torch pandas matplotlib 1. 准备数据集 数据为xls文件,如下格式 2. 训练模型 文件结构 执行训练 python代码 import pickle &…

递归和递推的区别

目录 1、递推 2、递归 3、结言 递归 递推 1、递推 递推就是说从初值出发后一直运算到所需的结果。 ——从已知到未知。&#xff08;从小到大&#xff09; 举一个简单的例子&#xff1a; 每天能学习一个小时的编程&#xff0c;那么一个月之后可以学到三十小时的编程知识。…

vue项目突然报错 error Insert `⏎·········` prettier/prettier

vs设置了保存时自动格式化代码&#xff0c;突然就报错&#xff1a; 解决方法&#xff0c;在.eslintrc.js最后添加一行&#xff1a;prettier/prettier: off&#xff0c; 然后重新运行

C#多态性

文章目录 C#多态性静态多态性函数重载函数重载 动态多态性运行结果 C#多态性 静态多态性 在编译时&#xff0c;函数和对象的连接机制被称为早期绑定&#xff0c;也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为&#xff1a; 函数重载 运算符重载 运算符重载将…

UnityShader(十九) AlphaBlend

上代码&#xff1a; Shader "Shader入门/透明度效果/AlphaBlendShader" {Properties{_MainTex ("Texture", 2D) "white" {}_AlphaScale("AlphaScale",Range(0,1))1.0}SubShader{Tags { "RenderType""Transparent&quo…