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

news2024/12/26 23:33:31

文章目录

  • 前言
  • 第七章 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解析过程;
  • #{}和${}的区别。

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

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

相关文章

VSCode创建用户代码片段-案例demo

示例 - 在线生成代码片段 Vue3代码片段 {"vue3": {scope": "javascript,typescript,html,vue","prefix": "vue3","body": ["<template>","$1","</template>",""…

基于 SemiDrive E3640 Gateway SSDK3.0 Sent 测试

一、 前言 SENT 全称&#xff1a;Single Edge Nibble Transmission&#xff0c;中文名称为&#xff1a;单边半字传输协议&#xff0c;是 SAE 推出的一种点对点的、单向传输的方案&#xff0c;被用于车载传感器和电子控制单元&#xff08;ECU&#xff09;之间的数据传输。SENT(S…

SSC9211_USB-CAM解决方案

一、方案描述 SSC9211是一种用于USB-CAM应用程序跟场景的高度集成的SOC产品。平台本身基于ARM层-A7双核&#xff0c;内置16位&#xff0c;64M的DDR2&#xff0c;集成了图像传感器接口、高级ISP、高性能JPEG编码器和其他丰富的外设接口。支持单&#xff0c;双 MIPI sensor方案&…

阻塞赋值与非阻塞赋值

1.原理 一个寄存器可以实现延迟一拍的效果。 可以看出输出out和中间寄存器in_reg确实 和输入in相差一拍&#xff0c;也就是一个时钟周期。 而out和in_reg没有延迟一拍是因为使用的是阻塞赋值。右边发生变化&#xff0c;左边立刻变化。 使用非阻塞赋值。 可以看到中间变量In_r…

安卓使用MQTT实现阿里云物联网云台订阅和发布主题(3)

一、订阅主题代码讲解 private final String mqtt_sub_topic "/sys/k0wih08FdYq/LHAPP/thing/service/property/set";//订阅话题//mqtt客户端订阅主题//QoS0时&#xff0c;报文最多发送一次&#xff0c;有可能丢失//QoS1时&#xff0c;报文至少发送一次&#xff0c…

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

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

【理解机器学习算法】之Clustering算法(Agglomerative Clustering)

聚合聚类(Agglomerative Clustering)是一种层次聚类算法&#xff0c;通过逐步合并或“聚集”它们来构建嵌套聚类。这种方法采用自底向上的方式构建聚类层次&#xff1a;它从将每个数据点作为单个聚类开始&#xff0c;然后迭代合并最接近的聚类对&#xff0c;直到所有数据点合并…

ARMday6作业

1&#xff0c;串口字符串收发现象实现图 2.串口控制灯亮灭 main.c #include "uart4.h"//封装延时函数 void delay(int ms) {int i,j;for(i0;i<ms;i){for(j0;j<2000;j){}} }int strcmp(char *a1,char *a2) {int i0;while(a1[i]a2[i]){if(a1[i]\0){break;} i;}…

微软发布首款AI PC ,产业链有望迎来新一轮量价齐升

3月21日晚&#xff0c;微软举办主题为“办公新时代”的线上新品发布会&#xff0c;发布Surface Pro 10和Surface Laptop 6&#xff0c;新品将搭载基于英特尔酷睿Ultra或高通骁龙X Elite的处理器&#xff0c;配备新一代NPU&#xff0c;以增强AI性能。 这两款AI PC将支持“AI Exp…

爱普生EPSON全新传感技术方案亮相高交会,创造新时代“精智生活”

2023年中国国际高新技术成果交易会在深圳福田会展中心盛大举行&#xff0c;是目前中国规模最大、最具影响力的科技类展会之一。爱普生作为始终坚持“科技本地化”战略的技术创新前沿企业参与此次展会&#xff0c;为中国用户带来爱普生电子元器件三款创新技术与四大成熟传感器解…

人大金仓与中科红旗达成战略合作

近日&#xff0c;人大金仓与中科红旗&#xff08;北京&#xff09;信息科技有限公司签订战略合作协议。双方将发挥各自优势&#xff0c;充分整合资源&#xff0c;在党政、金融、运营商、能源、交通等领域深化合作&#xff0c;实现协同发展。 中科红旗&#xff08;北京&#xff…

Linux 在线yum安装: PostgreSQL 15.6数据库

Linux 在线yum安装&#xff1a; PostgreSQL 15.6数据库 1、PostgreSQL数据库简介2、在线安装PostgreSQL15.63、配置 PostgreSQL的环境变量4、使用默认用户登录PostgreSQL5、配置 PostgreSQL 允许远程登录6、修改 PostgreSQL 默认端口7、创建数据库和表、远程用户zyl8、pgAdmin远…

Day69:WEB攻防-Java安全JWT攻防Swagger自动化算法签名密匙Druid泄漏

目录 Java安全-Druid监控-未授权访问&信息泄漏 黑盒发现 白盒发现 攻击点 Java安全-Swagger接口-导入&联动批量测试 黑盒发现 白盒发现 自动化发包测试 自动化漏洞测试 Java安全-JWT令牌-空算法&未签名&密匙提取 识别 JWT 方式一&#xff1a;人工识…

#Linux(环境变量)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;查看环境变量 &#xff08;2&#xff09;修改环境变量 第一种方法&#xff1a;直接使用命令设置&#xff08;立即生效&#xff0c;只会作用…

基于MATLAB的灰色神经网络预测订单需求

%% 清空环境变量 clc clear %% 36个样本&#xff0c;每个样本是6个月的销售量 load data%% 数据累加作为网络输入 [n,m]size(X); for i1:ny(i,1)sum(X(1:i,1));y(i,2)sum(X(1:i,2));y(i,3)sum(X(1:i,3));y(i,4)sum(X(1:i,4));y(i,5)sum(X(1:i,5));y(i,6)sum(X(1:i,6)); end%% …

儿童饰品上亚马逊需什么认证

注意了&#xff01;近期&#xff0c;亚马逊在抽查儿童首饰&#xff0c;被抽查到没有相关认证的产品将面临产品被下架等处罚&#xff01; 违反政策 如果您未在适用的截止日期之前提供所需信息&#xff0c;亚马逊可能会&#xff1a; 移除相关商品信息 暂停您添加新商品和/或商…

打造稳定高效的会员系统:技术架构解析与优化策略

随着互联网时代的发展和用户需求的变化&#xff0c;会员系统成为了各行各业企业实现用户粘性和增长的重要手段。一个稳定高效的会员系统架构能够帮助企业更好地管理会员数据、提供个性化服务和增加用户价值。本文将深入探讨会员系统的技术架构&#xff0c;分析其重要性和挑战&a…

(一)基于IDEA的JAVA基础1

Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论&#xff0…

【效率提升】IDEA中书签功能的妙用

这里写目录标题 1.概述2.书签功能介绍2.1.书签创建和删除2.2.书签列表2.3.自定义书签名2.4.带标签的书签创建2.5.标签快速跳转&#xff08;1到9&#xff09;2.6.其他标签快捷键2.7.其他补充 3.写到最后 1.概述 在多年的代码开发工作中&#xff0c;有一些问题困扰我很长的时间&…

【使用redisson完成延迟队列的功能】使用redisson配合线程池完成异步执行功能,延迟队列和不需要延迟的队列

1. 使用redisson完成延迟队列的功能 引入依赖 spring-boot-starter-actuator是Spring Boot提供的一个用于监控和管理应用程序的模块 用于查看应用程序的健康状况、审计信息、指标和其他有用的信息。这些端点可以帮助你监控应用程序的运行状态、性能指标和健康状况。 已经有了…