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.Log

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);
}

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

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

7.2.2 LogFactory工厂

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

源码2org.apache.ibatis.logging.LogFactory

public 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框架输出日志
    @Deprecated
    public 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.LogFactory

private 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.Slf4jImpl

public 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.Slf4jLoggerImpl

class 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.LogFactory

public 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.XMLConfigBuilder

private void loadCustomLogImpl(Properties props) {
    // 读取配置文件中的logImpl参数
    // 并将其转换为对应的Class对象
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    // 注册到Configuration对象中
    configuration.setLogImpl(logImpl);
}
源码8org.apache.ibatis.builder.BaseBuilder

protected <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.Configuration

public 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解析过程;
  • #{}和${}的区别。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信驰达车规蓝牙模块RF-BM-2642QB1I赋能汽车T-Box

近年来&#xff0c;随着人们对数据传输需求的增长&#xff0c;传统网络布线的通讯方式逐渐显现出满足不了的局限性&#xff0c;与此同时&#xff0c;各种无线传输技术迅速发展。汽车工业同样需要无线通讯技术&#xff0c;但红外技术、802.11、HomeRF等技术在汽车工业中存在一定…

axure和蓝湖上查看页面的说明和上传文件

蓝湖上传文件 入口 可添加链接和文件 文件可添加 PDF&#xff0c;word&#xff0c;Excel等&#xff0c;不能添加压缩包&#xff0c;可在线预览文件内容 axure元件说明 在原型上添加说明 axure发布页 axure预览页或发布到axure的服务器上&#xff0c;查看页面说明的方法 点…

python中如何解析Html

在最近需要的需求中&#xff0c;需要 python 获取网页内容&#xff0c;并从html中获取到想要的内容。这里记录一下两个比较常用的python库对html的解析。 1. BeautifulSoup 它是一个非常流行的python脚本库&#xff0c;用于解析HTML和XML文档。如果你对 java 很熟悉&#xff…

如何在Linux系统部署Dupal CMS结合内网穿透实现无公网IP访问web界面

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&a…

美摄科技剪同款SDK解决方案全面升级

视频内容已成为企业宣传、品牌塑造和市场营销的重要载体。然而&#xff0c;如何快速、高效地制作出高质量的视频内容&#xff0c;成为摆在众多企业面前的一大难题。针对这一挑战&#xff0c;美摄科技凭借深厚的技术积累和创新能力&#xff0c;推出了全新的剪同款SDK解决方案&am…

python 爬取杭州小区挂牌均价

下载chrome驱动 通过chrome浏览器的 设置-帮助-关于Google Chrome 查看你所使用的Chrome版本 驱动可以从这两个地方找: 【推荐】https://storage.googleapis.com/chrome-for-testing-publichttp://npm.taobao.org/mirrors/chromedriver import zipfile import os import r…

五、初识Django

初识Django 1.安装django2.创建项目2.1第一种方式&#xff1a;在终端2.2第二种方式&#xff1a;Pycharm 3.创建app4.快速上手4.1再写一个页面4.2templates模板4.3静态文件4.3.1static目录4.3.2引用静态文件 5.模板语法案例&#xff1a;伪联通新闻中心6.请求和相应案例&#xff…

23 OpenCV 直方图比较

文章目录 直方图比较的目的相关性计算 (CV_COMP_CORREL)卡方计算 (CV_COMP_CHISQR)十字计算(CV_COMP_INTERSECT)巴氏距离计算 (CV_COMP_BHATTACHARYYA )compareHist 直方图比较算子示例 直方图比较的目的 直方图比较的目的是衡量两幅图像之间的相似度或差异度。通过计算图像的颜…

数据可视化实战(三)

图书销量情况对比 import pandas as pd import matplotlib.pyplot as plt # 读取Excel数据 dfpd.read_excel(mrbook.xlsx) df序号书号序号.1月份销量rate0B189.787569e1211月15060.31B199.787569e1222月1200-0.32B259.787569e1233月33050.63B219.787569e1244月66100.54NaNNaN5…

redis从入门到实战

Redis基础 简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、基于内存的数据存储和缓存系统。它是一个高性能的键值存储数据库&#xff0c;&#xff0c;以其快速的读写能力、丰富的数据结构和多种应用场景而受到广泛关注。默认的database有16个&…

使用Docker搭建YesPlayMusic网易云音乐播放器并发布至公网访问

目录 ⛳️推荐 1. 安装Docker 2. 本地安装部署YesPlayMusic 3. 部署公有云YesPlayMusic播放器 3.1 安装cpolar内网穿透 3.2 固定YesPlayMusic公网地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一…

Docker网桥、DockerFile自定义镜像、DockerCompose工具(二)

这里写目录标题 1、网桥1.1、网络相关操作1.1.1、创建网络1.1.2、列出网络1.1.3、加入网络1.1.4、查看网络详情1.1.5、删除网络 2、Dockerfile自定义镜像2.1、镜像结构2.2、Dockerfile语法2.2.1、概述2.2.2、快速入门2.2.1、FROM2.2.2、CMD2.2.3、ENV2.2.4、WORKDIR2.2.5、run2…

银行OA系统|基于SpringBoot架构+ Mysql+Java+ B/S结构的银行OA系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

基于FPGA的FFT图像滤波设计

1.FFT滤波算法介绍 FFT滤波就是通过傅里叶运算将图像转换到频域空间&#xff0c;然后在频域中对图像进行处理&#xff0c;最后将处理后的图像通过傅里叶逆运算将图像转会到时域空间。 在频域空间中&#xff0c;我们能够更好的对图像的噪声进行分析&#xff0c;然后找出相关规律…

MAC废纸篓删掉还能复原吗 MAC废纸篓倾倒掉的文件怎么恢复 删除的东西在哪里可以找回来 怎么找回已删除的文件

MAC系统中的废纸篓&#xff08;Trash&#xff09;通常指用来临时存放用户即将丢弃的文件的地方。MAC系统的废纸篓功能相当于Windows系统的垃圾回收站&#xff0c;通过废纸篓删除的文件&#xff0c;一般是无法从系统中操作还原。那么&#xff0c;MAC废纸篓删掉还能复原吗&#x…

5-隐藏层:神经网络为什么working

声明 本文章基于哔哩哔哩付费课程《小白也能听懂的人工智能原理》。仅供学习记录、分享&#xff0c;严禁他用&#xff01;&#xff01;如有侵权&#xff0c;请联系删除 目录 一、知识引入 &#xff08;一&#xff09;隐藏层 &#xff08;二&#xff09;泛化 &#xff08;三…

UE4 Json事件设置Asset值(Asset如果都在同一目录下)

通过Json事件来设置&#xff0c;比如骨骼网格体&#xff08;换皮&#xff09;等等

202112青少年软件编程(Scratch图形化)等级考试试卷(三级)

第1题:【 单选题】 下列程序哪个可以实现: 按下空格键, 播放完音乐后说“你好! ” 2 秒? ( ) A: B: C: D: 【正确答案】: C 【试题解析】 : 第2题:【 单选题】 执行下列程序, 变量 N 的值不可能是? ( ) A:1 B:4 C:5 D:6 【正确答案】: D 【试题解析】…

Git进阶用法:Git分支轻松使用,配有图文

一、文章内容 git和分支相关的概念.git和分支有关的命令.git项目实战环节. 二、相关概念 分支&#xff1a;分支的概念好比树干的分支&#xff0c;每一跟分支都是从主干分出来的&#xff0c;营养是主干给的&#xff0c;所以在git里主干和分支也是如此&#xff0c;在git里主分…

day02_mysql-DDLDMLDQL_课后练习 - 参考答案

文章目录 day02_mysql_课后练习第1题第2题 day02_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库test02_library 2、创建表格books 字段名字段说明数据类型b_id书编号int(11)b_name书名varchar&#xff08;50&#xff09;authors作者varchar(100)price价格floatpub…