MyBatis是如何实现日志模块的详解

场景复现

你知道MyBatis是怎么实现日志的?额,这个简单,我知道啊!不就是在mybatis-config.xml文件中配置一下吗?

<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <settings>
  	<setting name="logImpl" value="SLF4J"/>
  </settings>
</configuration>

看,就是上面这么配置就行了,这样还可以实现日志切换呢!

看官方文档介绍,有如下日志实现可以切换:

你知道具体的原理吗?MyBatis是怎样实现日志切换的呢?

额,这个,这个...(挠头,源码还没看过呢,😓)

源码撸起来~

对于这个不确定的事,作为程序员的我,怎能轻易说不知道呢?!源码走起来~

找到一个入口

首先,先从GitHub上将MyBatis的源码给clone到本地.老大们撸了那么多行代码,从哪开始下手呢???

大佬们,也是很规范的,写完代码有不少的Junit测试类,从test/java中找到了logging包.那就从这开始吧~

@Test
public void shouldReadLogImplFromSettings() throws Exception {
//try-with-resources语句,目的就是为了读取配置文件IO
//mybatis-config.xml配置文件内容如上,<setting name="logImpl" value="SLF4J"/>
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/logging/mybatis-config.xml")) {
 //解析配置文件,解析配置文件的代码就在这了~
 new SqlSessionFactoryBuilder().build(reader);
}

Log log = LogFactory.getLog(Object.class);
log.debug("Debug message.");
assertEquals(log.getClass().getName(), NoLoggingImpl.class.getName());
}

SqlSessionFactoryBuilder类

//根据配置文件IO构建SqlSessionFactory
public SqlSessionFactory build(Reader reader) {
  return build(reader, null, null);
}

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    //构建XML配置构建器
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    //解析XML配置文件
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      reader.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

XMLConfigBuilder XML配置构建器

构造函数

//reader是XML配置文件IO
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
  this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

//实际调用的XML配置文件构建器构建函数
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  //注意了,这是重点,在这地方调用了父类的构造函数,新建了一个Configuration对象,下面看看Configuration构造函数做了什么
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

parse解析方法

/**
 * 解析 mybatis-config.xml
 * @return
 */
public Configuration parse() {
  // 只能解析一次
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //根据XPATH获取configuration节点
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

parseConfiguration 解析配置方法

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    // 解析 properties 节点
    propertiesElement(root.evalNode("properties"));
    // 解析 settings 节点
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    // 解析 typeAliases 节点
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析 plugins 节点
    pluginElement(root.evalNode("plugins"));
    // 解析 objectFactory 节点
    objectFactoryElement(root.evalNode("objectFactory"));
    // 解析 objectWrapperFactory 节点
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 解析 reflectorFactory 节点
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // 解析 environments 节点, 需要在 objectFactory 和 objectWrapperFactory才能读取
    environmentsElement(root.evalNode("environments"));
    // 解析 databaseIdProvider 节点
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析 typeHandlers 节点
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析 mappers 节点
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

其中Properties settings = settingsAsProperties(root.evalNode("settings"));是解析setttings节点,将settings节点的内容解析到 Properties 对象,其中涉及到很多操作,不在本文分析之列.

settingsElement(settings);将settings中的配置设置到Configuration对象.

settingsElement settings节点的配置

private void settingsElement(Properties props) throws Exception {
  ...
  configuration.setLogPrefix(props.getProperty("logPrefix"));
  @SuppressWarnings("unchecked")
  // 这个不就是从settings中解析日志的实现吗?快看看~
  // resovleClass是父类BaseBuilder中的方法
  Class<? extends Log> logImpl = (Class<? extends Log>) resolveClass(props.getProperty("logImpl"));
  //将日志的实现类设置到configuration配置类中
  configuration.setLogImpl(logImpl);
  configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

Configuration 配置类

public class Configuration {
  ...
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  ...
  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);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
}

从上面的Configuration构造函数中可以看到,类型别名中定义了日志实现的名称与,实现类.class(这些实现类都是MyBatis实现的,稍后再说)

//设置日志实现类
public void setLogImpl(Class<? extends Log> logImpl) {
  if (logImpl != null) {
    this.logImpl = logImpl;
    //调用日志工厂,设置日志实现
    LogFactory.useCustomLogging(this.logImpl);
  }
}

BaseBuilder

构造函数

public BaseBuilder(Configuration configuration) {
  this.configuration = configuration;
  //可以看到这个地方的类型别名是从configuration中获取的
  this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
protected <T> Class<? extends T> resolveClass(String alias) {
  if (alias == null) {
   return null;
  }
  try {
   //解析别名
   return resolveAlias(alias);
  } catch (Exception e) {
   throw new BuilderException("Error resolving class. Cause: " + e, e);
  }
}

//这个不就是从别名中获取对应的实现吗??!
//配置SLF4J,返回Slf4jImpl.class
protected <T> Class<? extends T> resolveAlias(String alias) {
  //这个地方的typeAliasRegistry是从configuration中获取的
  return typeAliasRegistry.resolveAlias(alias);
}

TypeAliasRegistry 类型别名

类型别名注册

//就是将key转换位小写,存入HashMap中,key是别名,value是Class类对象
public void registerAlias(String alias, Class<?> value) {
  if (alias == null) {
    throw new TypeException("The parameter alias cannot be null");
  }
  // issue #748
  String key = alias.toLowerCase(Locale.ENGLISH);
  if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
    throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
  }
  TYPE_ALIASES.put(key, value);
}
//根据对应的别名key,获取实现类class
public <T> Class<T> resolveAlias(String string) {
  try {
    if (string == null) {
      return null;
    }
    // issue #748
    String key = string.toLowerCase(Locale.ENGLISH);
    Class<T> value;
    if (TYPE_ALIASES.containsKey(key)) {
      value = (Class<T>) TYPE_ALIASES.get(key);
    } else {
      value = (Class<T>) Resources.classForName(string);
    }
    return value;
  } catch (ClassNotFoundException e) {
    throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
  }
}

Slf4jImpl Slf4j实现类

public class Slf4jImpl implements Log {

 private Log log;

 public Slf4jImpl(String clazz) {
  //使用Slf4j的API获取日志器
  Logger logger = LoggerFactory.getLogger(clazz);

  if (logger instanceof LocationAwareLogger) {
   try {
    // check for slf4j >= 1.6 method signature
    logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
    log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
    return;
   } catch (SecurityException e) {
    // fail-back to Slf4jLoggerImpl
   } catch (NoSuchMethodException e) {
    // fail-back to Slf4jLoggerImpl
   }
  }

  // Logger is not LocationAwareLogger or slf4j version < 1.6
  log = new Slf4jLoggerImpl(logger);
 }

 ...
}

可见,最终还是调用具体的日志API实现!

LogFactory 日志工厂

package org.apache.ibatis.logging;

import java.lang.reflect.Constructor;

/**
 * 日志工厂
 */
public final class LogFactory {

 /**
  * Marker to be used by logging implementations that support markers
  */
 public static final String MARKER = "MYBATIS";

 // 记录当前使用的第三方日志库组件所对应的适配器的方法
 private static Constructor<? extends Log> logConstructor;

 // tryImplementation 进行尝试加载
 static {
  tryImplementation(LogFactory::useSlf4jLogging);
  tryImplementation(LogFactory::useCommonsLogging);
  tryImplementation(LogFactory::useLog4J2Logging);
  tryImplementation(LogFactory::useLog4JLogging);
  tryImplementation(LogFactory::useJdkLogging);
  tryImplementation(LogFactory::useNoLogging);
 }

 // 私有化
 private LogFactory() {
  // disable construction
 }

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

 /**
  * 以下的 useXXXogging 的方法都是尝试加载日志的实现
  * 最终的实现都是 setImplementation
  */

 public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
  setImplementation(clazz);
 }

 public static synchronized void useSlf4jLogging() {
  setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
 }

 public static synchronized void useCommonsLogging() {
  setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
 }

 public static synchronized void useLog4JLogging() {
  setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
 }

 public static synchronized void useLog4J2Logging() {
  setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
 }

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

 /**
  * 尝试加载
  * @param runnable
  */
 private static void tryImplementation(Runnable runnable) {
  if (logConstructor == null) {
   try {
    // 会调用 useSlf4jLogging 类似的方法
    runnable.run();
   } catch (Throwable t) {
    // ignore
   }
  }
 }

 /**
  * 设计日志的实现类
  * @param implClass Log 的子类
  */
 private static void setImplementation(Class<? extends Log> implClass) {
  try {
   // 通过反射获取构造方法
   Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
   Log log = candidate.newInstance(LogFactory.class.getName());
   if (log.isDebugEnabled()) {
    log.debug("Logging initialized using '" + implClass + "' adapter.");
   }
   //设置日志实现的有参构造函数
   logConstructor = candidate;
  } catch (Throwable t) {
   throw new LogException("Error setting Log implementation. Cause: " + t, t);
  }
 }

}

可以看到其中有静态代码块,随着类的加载,尝试加载日志的实现!

总结

通过以上的一步一步分析,可以看到MyBatis是分别通过对日志实现进行进行包装,最终还是调用具体的日志API实现.

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • MyBatis源码分析之日志logging详解

    前言 本文介绍个人对 logging 包下源码的理解.分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 logging 配置加载 我们先从日志的配置加载开始阅读, MyBatis 的各项配置的加载过程都可以从 XMLConfigBuilder 类中找到,我们定位到该类下的日志加载方法 loadCustomLogImpl: private void loadCustomLogImpl(Properties props) { // 从 MyBatis 的 TypeAliasRegist

  • springboot整合mybatis将sql打印到日志的实例详解

    在前台请求数据的时候,sql语句一直都是打印到控制台的,有一个想法就是想让它打印到日志里,该如何做呢? 见下面的mybatis配置文件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-

  • MyBatis源码分析之日志记录详解

    一 .概述 MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,但MyBatis统一提供了trace.debug.warn.error四个级别: 自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog; 日志的使用要优雅的嵌入到主体功能中: 二.设计模式 将各种日志组件如(slf4J ,commonsLoging ,Log4J2 , Log4J

  • MyBatis启动时控制台无限输出日志的原因及解决办法

    你是否遇到过下面的情况,控制台无限的输出下面的日志: Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. Logging initialized using 'class org.apache.ibatis.lo

  • MyBatis是如何实现日志模块的详解

    场景复现 你知道MyBatis是怎么实现日志的?额,这个简单,我知道啊!不就是在mybatis-config.xml文件中配置一下吗? <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <set

  • python logging日志模块的详解

    python logging日志模块的详解 日志级别 日志一共分成5个等级,从低到高分别是:DEBUG INFO WARNING ERROR CRITICAL. DEBUG:详细的信息,通常只出现在诊断问题上 INFO:确认一切按预期运行 WARNING:一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如.磁盘空间低").这个软件还能按预期工作. ERROR:更严重的问题,软件没能执行一些功能 CRITICAL:一个严重的错误,这表明程序本身可能无法继续运行 这5个等级,也

  • python日志模块loguru详解

    目录 前言 使用步骤 安装库 简单使用方法 配置 异常追溯 总结 前言 在部署一些定时运行或者长期运行的任务时,为了留存一些导致程序出现异常或错误的信息,通常会才用日志的方式来进行记录这些信息.python内置的logging标准库博主是没用过,今天给大家介绍loguru,loguru 库的使用可以说是十分简单,希望通过本文大家再也不用通过print来排查代码了. 使用步骤 安装库 pip install loguru 简单使用方法 from loguru import logger logge

  • Django logging日志模块实例详解(日志记录模板配置)

    目录 一.Django日志 二.Logger 记录器 Django 内置记录器 三.Handler 处理程序 Logging 自身携带Handler 四.Filter过滤器 五.Formatters格式化器 六:Django 集成日志logginger 模块 总结 一.Django日志 Django使用python内建的logging模块打印日志,Python的logging配置由四个部分组成: 1>.记录器(Logger) 2>.处理程序(Handler) 3>.过滤器(Filter)

  • python3中超级好用的日志模块-loguru模块使用详解

    目录 一. 使用logging模块时 二. loguru模块的基础使用 三. logurr详细使用 3.1 add 方法的定义 3.2 基本参数 3.3 删除 sink 3.4 rotation 配置 3.5 retention 配置 3.6 compression 配置 3.7 字符串格式化 3.8 Traceback 记录 一. 使用logging模块时 用python写代码时,logging模块最基本的几行配置,如下: import logging logging.basicConfig(

  • Mybatis 创建方法、全局配置教程详解

    总体介绍:MyBatis实际上是Ibatis3.0版本以后的持久化层框架[也就是和数据库打交道的框架]! 和数据库打交道的技术有: 原生的JDBC技术--->Spring的JdbcTemplate技术 这些工具都是提供简单的SQL语句的执行,但是和我们这里学的MyBatis框架还有些不同, 框架是一整套的东西,例如事务控制,查询缓存,字段映射等等. 我们用原生JDBC操作数据库的时候都会经过: 编写sql---->预编译---->设置参数----->执行sql------->

  • IDEA的Mybatis Log Plugin插件配置和使用详解

    在使用Mybatis开发项目时,由于避免出现SQL注入,大部分情况下都是使用#{}占位符的方式传参. 所以日志打印SQL时,打印的也是占位符,如: 如果SQL比较复杂,参数又很多的话,要通过日志拼凑真正可执行的SQL还是件比较头痛的事情. 好在IDEA有款很不错的插件(Mybatis Log Plugin)可以解决上述问题. 插件安装 像其它插件一样,可选择在线安装和离线安装. 在线安装:搜索Mybatis Log Plugin,直接install即可.离线安装:可从:http://plugin

  • MyBatis注解方式之@Update/@Delete使用详解

    @Update 1. RoleMapper接口增加接口方法 /** * * * @Title: updateSysRoleById * * @Description: updateSysRoleById * * @param sysRole * @return * * @return: int */ @Update({ "update sys_role set role_name = #{roleName},enabled = #{enabled},create_by = #{createBy}

  • MyBatis框架零基础快速入门案例详解

    目录 一.创建数据库和表 二.创建maven工程 三.代码编写 1.编写Student实体类 2.编写DAO接口StudentDao 3.编写DAO接口Mapper映射文件StudentDao.xml. 4.创建MyBatis主配置文件 四.创建测试类进行测试 1.创建测试类MyBatisTest 2.配置日志功能 五.增删改操作 insert操作 MyBatis下载地址:https://github.com/mybatis/mybatis-3/releases 一.创建数据库和表 数据库名ss

随机推荐