详解MyBatis日志如何做到兼容所有常用的日志框架

 更新时间:2020年11月10日 15:00:26   作者:双子孤狼  
这篇文章主要介绍了详解MyBatis日志如何做到兼容所有常用的日志框架,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

日志,在我们开发中是一个非常重要的话题,良好的日志打印可以帮助我们快速的定位问题,可能现在我们开发用到最多的日志框架就是slf4j了,但是日志还有其他很多优秀的框架,比如:Apache Common Log,Log4j,java.util.logging等。MyBatis作为一款优秀的ORM框架,定义了一套统一的日志接口供应用层调用,而底层却利用适配器模式兼容了我们上面所列出来的常用日志框架。

MyBatis日志分类

在介绍MyBatis的全局配置文件的时候,我们提到setting内有一个属性logImpl,可以配置的选项有:SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING。
这就说明MyBatis支持六种日志类型(NO_LOGGING是不打印日志)。我们看一下MyBatis的日志模块也可以很明显的看出六种日志类型:

在这里插入图片描述

它们的对应关系为:

日志属性 对应日志模块包名 实现方式
SLF4J slf4j 使用SLF4J日志框架实现
LOG4J log4j 使用Log4J日志框架实现(1.x版本)
LOG4J2 log4j2 使用Log4J日志框架实现(2.x版本)
JDK_LOGGING jdk14 使用java.util.logging实现
COMMONS_LOGGING commons 使用Apache Commons Logging实现
STDOUT_LOGGING stdout 使用System类实现
NO_LOGGING nologging 不打印日志

PS:需要注意的是,SLF4J并不是一个具体的日志框架,也就是我们不能单独只配置SLF4J而不引入其他任何具体的日志框架。

简单谈谈SLF4J

SLF4J:简单日记门面。(英文全称为simple logging Facade for Java),这个是用来为各种日志框架提供一个简单的统一的接口,这样使得我们在切换日志框架的时候可以直接替换jar包就可以了,而无需修改源代码。

logback我想大家都用过,logback是一个实现了具体日志打印的框架,但是MyBatis上面列出来的分类并没有支持logback,它又为什么能够打印呢?这就是SLF4J的作用了,因为logback也实现了SLF4J提供的接口,所以我们需要将logback和SLF4J结合配置使用才行。而后面的介绍中也可以看到,MyBatis中如果我们不指定日志种类的时候,优先选择的就是SLF4J,这正是因为SLF4J可以和其他许多日志框架一起结合来使用。

那么假如我们指定了日志类型为SLF4J,但是不引入其他任何实现呢?
答案就是MyBatis不会打印任何日志出来,下图就是只配置了SLF4J而没有引入其他任何实现的警告信息:

在这里插入图片描述

可以看到这里提示我们SLF4J没有任何实现,而后面的sql语句和参数这些信息也没有打印出来。

MyBatis日志实现原理

日志的解析

老规矩,我们还是先找到加载mybatis-config配置文件中的解析日志的源码:

在这里插入图片描述

在这里插入图片描述

这里首先会根据我们配置的属性作为别名去TypeAliasRegistry类中查找对应的类,如果不存在这个别名,那就会把我们配置的属性直接通过Class.forName去查找日志类,所以看到这里就明白我们可以自定义日志类,只要实现Log接口就行,然后配置我们自己的类名就行了。虽然别名都存在TypeAliasRegistry类里面,但是我们前面介绍MyBatis配置文件的时候,列出了TypeAliasRegistry类中默认初始化的别名,并没有看到日志相关类的别名,那么日志的别名又是在哪里配置的呢?我们打开Configuration类:

在这里插入图片描述

可以看到Configuration的构造方法里面也初始化了一些别名注册到TypeAliasRegistry类了。
接下来我们看看读取到日志类之后调用了setLogImpl做了什么事情:

在这里插入图片描述

调用了LogFactory类的方法。

LogFactory

LogFactory工厂是负责创建日志对象对应的适配器。
LogFactory的静态代码块内按顺序初始化了所有内置的日志

在这里插入图片描述

再看一下tryImplementation方法,如果logConstructor不为空,说明当前还没有加载到日志适配器,那就继续执行run()方法,也就是继续执行useXXXLogging方法,而所有的useXXXLogging方法都是调用了setImplementation方法。

在这里插入图片描述

下面这里如果加载成功之后就会对logConstructor进行赋值,那么后续的方法就不会再执行run()方法, 而如果抛出异常,因为已经被捕获了,所以就会继续往后执行静态代码块内的方法。

在这里插入图片描述

从上面的LogFactory中我们可以看到,初始化的时候就会默认初始化一个日志适配器,所以如果我们引用了相关日志所需要的类,那么就会按照static代码块内的顺序进行选择一个合适的日志适配器。

继续回到上面的Configuration里面,这里拿到我们配置的日志信息之后,会直接调用useCustomLogging方法,也就是绕过了上面的logConstructor == null这个判断,而直接调用了setImplementation方法,所以假如我们配置了日志信息,那么会覆盖初始化的日志适配器。

PS:假如我们配置了一个不存在的日志类,那么因为它是直接调用setImplementation方法,所以异常就会被抛出来,不抛异常的方法是tryImplementation而不是setImplementation。

jdbc log

MyBatis的日志包下面还有一个包时jdbc,这个我们还没有介绍,那么jdbc包下面的类又有什么用呢?我们先看一下类图关系:

在这里插入图片描述

很明显,MyBatis将日志拆分成了ConnectionLogger,PreparedStatementLogger,ResultSetLogger,StatementLogger四种类型分开处理,它们都继承了BaseJdbcLogger类,而且实现了InvocationHandler接口,也很明显,这里用到了JDK动态代理。

任意点开ConnectionLogger可以发现,它是用来代理Connection对象的:

在这里插入图片描述

其他三个那很明显,分别是用来代理PreparedStatement,ResultSet,Statement这三个对象的。也就是说MyBatis中日志最终的打印是通过JDK动态代理来实现的,而且不同的执行过程分成了四个对象来分别负责对应的日志打印

我们继续看一下ConnectionLogger的invoke方法,可以看到,这里就是打印了一句日志:

在这里插入图片描述

上面日志打印出来的效果就是我们下面红框中的日志:

在这里插入图片描述

等等,差点被忽悠了,这代码里面并没有打印“==>”,打印出来的这个符号又是怎么来的呢?

那就需要进入debug方法里面继续看一看,这个debug方法是在抽象类BaseJdbcLogger里面实现的,所以我们还需要看看BaseJdbcLogger类的debug方法。

在这里插入图片描述

可以看到这里打印的时候拼接了一个前缀:

在这里插入图片描述

PS:queryStack是查询层数,如果没有嵌套查询则queryStack=1

总结

本文主要分析了MyBatis日志的加载原理,并对LogFactory作为适配器对象工厂是如何选择日志适配器对象进行了分析。最后分析了MyBatis是如何通过动态代理将不同日志类型分为不同对象来实现日志打印功能的。

到此这篇关于详解MyBatis日志如何做到兼容所有常用的日志框架的文章就介绍到这了,更多相关MyBatis兼容所有日志框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Java线程池如何统计线程空闲时间

    详解Java线程池如何统计线程空闲时间

    这篇文章主要和大家分享一个面试题:Java线程池是怎么统计线程空闲时间?文中的示例代码讲解详细,对我们掌握Java有一定帮助,需要的可以参考一下
    2022-11-11
  • 一文详解SpringBoot Redis多数据源配置

    一文详解SpringBoot Redis多数据源配置

    Spring Boot默认只允许一种 Redis 连接池配置,且配置受限于 Lettuce 包,不够灵活,所以本文将为大家介绍如何自定义Redis配置方案实现多数据源支持,需要的可以参考下
    2024-11-11
  • Java如何通过属性名获取Object对象属性值

    Java如何通过属性名获取Object对象属性值

    这篇文章主要介绍了Java如何通过属性名获取Object对象属性值问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • 最简单的Spring Cloud教程第一篇:服务的注册与发现(Eureka)

    最简单的Spring Cloud教程第一篇:服务的注册与发现(Eureka)

    这篇文章主要给大家介绍了关于Spring Cloud服务的注册与发现(Eureka)的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用spring cloud具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-08-08
  • springcloud组件技术分享(推荐)

    springcloud组件技术分享(推荐)

    Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。这篇文章主要介绍了springcloud组件技术分享,需要的朋友可以参考下
    2020-10-10
  • Java 线程池详解及实例代码

    Java 线程池详解及实例代码

    这篇文章主要介绍了Java 线程池的相关资料,并符实例代码,帮助大家学习参考,需要的朋友可以参考下
    2016-09-09
  • Java多线程中ThreadLocal解读

    Java多线程中ThreadLocal解读

    这篇文章主要介绍了Java多线程中ThreadLocal解读,  多线程访问同一个共享变量的时候容易出现并发问题,因此为了保证线程安全性,我们都会采用加锁的方式,而ThreadLocal是除加锁方式之外的另一种保证线程安全性的方法,需要的朋友可以参考下
    2023-09-09
  • SpringBoot实现启动项目后立即执行的方法总结

    SpringBoot实现启动项目后立即执行的方法总结

    在项目开发中某些场景必须要用到启动项目后立即执行方式的功能,所以这篇文章就来和大家聊聊实现立即执行的几种方法,希望对大家有所帮助
    2023-05-05
  • Java中的volatile关键字原理深入解析

    Java中的volatile关键字原理深入解析

    这篇文章主要介绍了Java中的volatile关键字原理深入解析,volatile是Java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量,需要的朋友可以参考下
    2023-12-12
  • java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径

    java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径

    这篇文章主要介绍了java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径,需要的朋友可以参考下
    2014-04-04

最新评论