MyBatis源码分析之日志logging详解

前言

本文介绍个人对 logging 包下源码的理解。分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

logging 配置加载

我们先从日志的配置加载开始阅读, MyBatis 的各项配置的加载过程都可以从 XMLConfigBuilder 类中找到,我们定位到该类下的日志加载方法 loadCustomLogImpl:

private void loadCustomLogImpl(Properties props) {
 // 从 MyBatis 的 TypeAliasRegistry 中查找 logImpl 键所对应值的类对象
 // 这里 logImpl 对应的 value 值可以从 org.apache.ibatis.session.Configuration 的构造方法中找到
 // 注意 Log 类,这是 MyBatis 内部对日志对象的抽象
 Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
 // 将查找到的 Class 对象设置到 Configuration 对象中
 configuration.setLogImpl(logImpl);
}

很简单的一个方法,每行都有注释,其中 configuration.setLogImpl() 里面调用了 LogFactory.useCustomLogging() ,这出现了新类 LogFactory 类,接下来我们就来聊聊这个类。

LogFactory

useCustomLogging()方法

LogFactory 是框架内部获取 Log 对象的手段,通过它的名字也能看出来。它有如下几类方法:

// 注意这个类型的方法都是同步方法
public synchronized static useXxxLogging(...);

public static Log getLog(...);

private static tryImplementation(Runnable);

private static setImplementation(Class);

刚刚我们看到被调用的方法 useCustomLogging() 方法,是用来设置内部使用的日志框架, MyBatis 自身已经适配了一些常见的日志框架,如 Slf4j 、 Commons Log 、 Log4j 等等。

useCustomLogging() 方法内部调用 setImplementation(Class) 方法,此方法代码如下,功能简单:

private static void setImplementation(Class<? extends Log> implClass) {
 try {
  // 获取 Log实现类的构造方法,它只有一个字符串作为参数
  Constructor<? extends Log> candidate = implClass.getConstructor(String.class);

  // 创建一个 Log 对象,打印 debug 日志
  Log log = candidate.newInstance(LogFactory.class.getName());
  if (log.isDebugEnabled()) {
   log.debug("Logging initialized using '" + implClass + "' adapter.");
  }

  // ...
  // 把 candidate 对象设置到 LogFactory 的静态变量 logConstructor,这个静态变量在 getLog() 方法
  // 中被用到
  logConstructor = candidate;
 } catch (Throwable t) {
  throw new LogException("Error setting Log implementation. Cause: " + t, t);
 }
}

Log 接口

刚刚我们接触到了 Log 这个类,它是一个接口,是 MyBatis 内部使用的日志对象的抽象,它是为了兼容市面上各种各样的日志框架,使用了适配器模式,通过 Log 接口来连接 MyBatis 和其他日志框架,通过实现 Log 接口连着 MyBatis 和需要适配的日志框架。

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

}

可有发现?Log 接口缺少了 info 级别的日志输出方法,个人猜测应该是 MyBatis 内部不需要 info 级别的日志输出,毕竟 Log 接口设计之初就是为了内部使用,而框架使用者是不会采用 MyBatis 的日志作为系统的日志。注意一点: 实现了 Log 接口的类必须拥有一个参数只有一个字符串的构造方法 ,MyBatis 就是通过这个构造方法创建日志对象的。

MyBatis 适配了许多常见的日志框架,这里就单单介绍 Log4jImpl 类,它代码非常简单:

public class Log4jImpl implements Log {

 private static final String FQCN = Log4jImpl.class.getName();

 // 这里包装了 Log4j 框架的日志对象,从而实现适配
 private final Logger log;

 public Log4jImpl(String clazz) {
 log = Logger.getLogger(clazz);
 }

 @Override
 public boolean isDebugEnabled() {
 return log.isDebugEnabled();
 }

 @Override
 public boolean isTraceEnabled() {
 return log.isTraceEnabled();
 }

 @Override
 public void error(String s, Throwable e) {
 log.log(FQCN, Level.ERROR, s, e);
 }

 @Override
 public void error(String s) {
 log.log(FQCN, Level.ERROR, s, null);
 }

 @Override
 public void debug(String s) {
 log.log(FQCN, Level.DEBUG, s, null);
 }

 @Override
 public void trace(String s) {
 log.log(FQCN, Level.TRACE, s, null);
 }

 @Override
 public void warn(String s) {
 log.log(FQCN, Level.WARN, s, null);
 }

}

tryImplementation() 方法

LogFactory 类再介绍一下被静态代码块使用的方法 tryImplementation(Runnable) 。静态代码块代码如下:

static {
 // 依次执行如下代码,当没有该类会抛 ClassNotFoundException ,然后继续执行
 tryImplementation(LogFactory::useSlf4jLogging);
 tryImplementation(LogFactory::useCommonsLogging);
 tryImplementation(LogFactory::useLog4J2Logging);
 tryImplementation(LogFactory::useLog4JLogging);
 tryImplementation(LogFactory::useJdkLogging);
 tryImplementation(LogFactory::useNoLogging);
}

这个方法有点迷惑性,因为它使用 Runnable 接口作为参数,而 useXxxLOgging() 方法又是同步方法,很容易联想到多线程,实际上这里并没有, Runnable 接口不结合 Thread 类使用它就是一个普通的函数接口。除去这些就没什么了,不过是调用了 Runnable 的 run() 方法而已。

getLog() 方法

getLog() 没什么多说的,就是通过反射创建 Log 接口实现类,这里没有使用到缓存,每次调用都是创建一个新的对象。

jdbc 包

这个包与其他包有些不同,它的职能是为各个阶段的流程提供日志打印,该包一共就五个类,它们的关系如下:

除了 BaseJdbcLogger 类其他类都实现了 InvocationHandler 接口,这个接口是 JDK 提供的动态代理接口,所以显而易见可以知道它们就是通过代理在各个阶段打印相应的日志。

以下为 BaseJdbcLogger 类的部分说明:

  • SET_METHODS:静态字段,记录 PreparedStatement 中 set 开头的的方法名
  • EXECUTE_METHODS:静态字段,记录 SQL 执行的方法名
  • columnXxx:实例字段,记录 SQL 参数信息
  • statementLog:日志对象
  • queryStack:查询栈数
  • getParameterValueString():将 SQL 参数转为一个字符串
  • removeBreakingWhitespace():移除 SQL 中多余的空白字符
  • prefix():获取前缀 ==>/<==

而其余四个类都是简单的逻辑:判断执行的方法是否为指定方法,然后打印相应的日志。

总结

以上便是个人研究 logging 包的内容,本包使用了以下设计模式(包含不限于):

  • 适配器模式
  • 代理模式

其中适配器模式应该是一个比较不错的示例,可做参考。

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

(0)

相关推荐

  • 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

  • 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源码分析之日志logging详解

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

  • java 源码分析Arrays.asList方法详解

    最近,抽空把java Arrays 工具类的asList 方法做了源码分析,在网上整理了相关资料,记录下来,希望也能帮助读者! Arrays工具类提供了一个方法asList, 使用该方法可以将一个变长参数或者数组转换成List . 其源代码如下: /** * Returns a fixed-size list backed by the specified array. (Changes to * the returned list "write through" to the arr

  • Vue.js源码分析之自定义指令详解

    前言 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染.插入到父节点.更新.解绑时可以执行一些特定的操作(钩子函数() 自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指

  • Vue源码分析之虚拟DOM详解

    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的.例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量.简单来说,可以把Virtual DOM 理解为一个简单的JS对象,并且最少包含标签名( tag).属性(attrs)和子元素对象( children)三个属性. ----- 元素节点: 元素节点更贴近于我们

  • jQuery源码分析之sizzle选择器详解

    前言 Sizzle 原本是 jQuery 中用来当作 DOM 选择器的,后来被 John Resig 单独分离出去,成为一个单独的项目,可以直接导入到项目中使用. 点击这里下:jquery/sizzle. 本来我们使用 jQuery 当作选择器,选定一些 #id 或 .class,使用 document.getElementById 或 document.getElemensByClassName 就可以很快锁定 DOM 所在的位置,然后返回给 jQuery 当作对象.但有时候会碰到一些比较复杂

  • Mybatis源码分析之存储过程调用和运行流程

    这一篇我们学习一下Mybatis调用存储过程的使用和运行流程.首先我们先创建一个简单的存储过程 DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGIN SELECT COUNT(*) FROM users WHERE users.age=age INTO user_count; END $ 这个存储过程的含义其实比较简单的,就是输入age,然后执行select count(

  • MyBatis 源码分析 之SqlSession接口和Executor类

    mybatis框架在操作数据的时候,离不开SqlSession接口实例类的作用.可以说SqlSession接口实例是开发过程中打交道最多的一个类.即是DefaultSqlSession类.如果笔者记得没有错的话,早期是没有什么getMapper方法的.增删改查各志有对应的方法进行操作.虽然现在改进了很多,但是也保留了很多.我们依旧可以看到类似于selectList这样子的方法.源码的例子里面就可以找到.如下 SqlSession session = sqlMapper.openSession(T

  • Mybatis源码分析之插件模块

    Mybatis插件模块 插件这个东西一般用的比较少,就算用的多的插件也算是PageHelper分页插件: PageHelper官网:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md 官网上这个也有谈到Mybatis的插件流程分析. 使用示例 插件类 记录SQL执行的时间, 1.在JDK8之前必须实现Interceptor接口中的三个方法,在JDK8之后只需要实现intercept方法即可: 2.加上

  • 源码分析SpringMvc日志打印被忽略输出问题

    目录 1.写在前面 2.问题引出 3.截取源码分析 4.截取问题处理 1.写在前面 在java的开发过程中,涉及到java web的开发,基本上都是走spring这一套了. 我们之前一般来说,都会说mvc:Model(模型业务).View(视图界面).Controller(控制器).这个学习java开发的,应该都懂吧,这里就不多说了. 这里,我们先着重解析下Controller: Controller控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何

随机推荐