当前位置:首页 > 技术分析 > 正文内容

MyBatis日志实现二-MyBatis日志实现

本节我们来了解一下MyBatis的日志实现。MyBatis通过Log接口定义日志操作规范,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接口对应的实现,Log接口的实现类如图所示。从实现类可以看出,MyBatis支持7种不同的日志实现,具体如下。Apache Commons Logging:使用JCL输出日志。

Log4j 2:使用Log4j 2框架输入日志。

Java Util Logging:使用JDK内置的日志模块输出日志。

Log4j:使用Log4j框架输出日志。

No Logging:不输出任何日志。

SLF4J:使用SLF4J日志门面输出日志。

Stdout:将日志输出到标准输出设备(例如控制台)。

Log实现类的逻辑比较简单,只是调用对应日志框架相关的API打印日志。以Log4jImpl实现类为例,代码如下:


package org.apache.ibatis.logging.log4j;

import org.apache.ibatis.logging.Log;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author Eduardo Macarron
 */
public class Log4jImpl implements Log {
  
  private static final String FQCN = Log4jImpl.class.getName();

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

}

如上面的代码所示,在Log4jImpl构造方法中,获取Log4j框架中的Logger对象,然后将日志输出操作委托给Logger对象来完成。其他日志实现类逻辑与之类似,具体可参考MyBatis相关源码。

MyBatis支持7种不同的日志输出策略,在实际使用MyBatis框架时,具体使用哪种方式输出日志呢?

接下来我们就来揭开这个谜团。MyBatis的Log实例采用工厂模式创建。下面是使用LogFactory获取Log实例的案例,代码如下:

public void testLog() {
        // 指定使用Log4j 框架输出日志
        LogFactory.useLog4JLogging();
        // 获取Log实例
        Log log = LogFactory.getLog(LogExample.class);
        // 输出日志
        log.error("LogExample.testLog function.");
    }

在上面的代码中,首先调用LogFactory类的useLog4JLogging()方法显式地指定使用Log4j框架对应的Log实现类打印日志。除了useLog4JLogging()方法外,LogFactory中还提供了一系列类似的方法,用于指定具体使用哪种日志实现类输出日志,这些方法如下:

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

这些方法中,都会调用setImplementation()方法指定日志实现类。我们可以了解一下LogFactory类setImplementation()方法的实现,内容如下:

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

如上面的代码所示,在setImplementation()方法中,首先获取MyBatis日志实现类对应的Constructor对象,然后通过LogFactory类的logConstructor属性记录当前日志实现类的Constructor对象。所以当我们调用LogFactory类的useLog4JLogging()方法时,就确定了使用
org.apache.ibatis.logging.log4j.Log4jImpl实现类输出日志,而Log4jImpl实现类又将日志输出操作委托给Log4j框架,这样就确定了使用Log4j框架输出日志。

MyBatis日志模块设计得比较巧妙的一点是当我们未指定使用哪种日志实现时,MyBatis能够按照顺序查找Classpath下的日志框架相关JAR包。如果Classpath下有对应的日志包,则使用该日志框架打印日志。

接下来我们了解一下MyBatis动态查找日志框架的实现细节。在LogFactory类中有一个初始化代码块,内容如下:

  static {
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useSlf4jLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useCommonsLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useLog4J2Logging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useLog4JLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useJdkLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useNoLogging();
      }
    });
  }

如上面的代码所示,在初始化代码块中,调用LogFactory类的tryImplementation()方法确定日志实现类,tryImplementation()方法的参数是一个Runnable的匿名对象,在run()方法中调用useSLF4JLogging()等方法指定日志实现类。提到Runnable接口,我们容易联想到Java的多线程,但是这里和线程没有任何关系。我们可以关注一下tryImplementation()方法的实现,代码如下:

  private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

如上面的代码所示,在tryImplementation()方法中,只是将Runnable匿名对象的run()方法作为普通方法调用,所以这里不涉及任何Java多线程相关的内容。

回归正题,在LogFactory初始化代码块中,首先调用tryImplementation()尝试调用useSLF4JLogging()方法使用SLF4J日志框架,如果Classpath中不存在SLF4J日志框架相关的JAR包,则useSLF4JLogging()方法会抛出ClassNotFoundException异常或NoClassDefFoundError错误。ClassNotFoundException和NoClassDefFoundError都实现了Throwable接口,tryImplementation()方法中对ClassNotFoundException和NoClassDefFoundError都进行捕获,不做任何处理,然后查找下一个日志框架JAR包是否存在,直至找到Classpath中存在的日志框架。

若Classpath中存在SLF4J框架相关JAR包,则使用SLF4JImpl日志实现类输出日志,并将LogFacotry的logConstructor属性指定为SLF4JImpl类对应的Constructor对象,由于tryImplementation()方法中会判断logConstructor是否为空,因此后续设置日志实现类的逻辑都不会执行。MyBatis查找日志框架的顺序为SLF4J→JCL→Log4j 2→Log4j→JUL→No Logging。如果Classpath下不存在任何日志框架,则使用NoLoggingImpl日志实现类,即不输出任何日志。

在使用MyBatis时,我们还可以通过logImpl参数指定使用哪种框架输出日志,例如:

<settings>
		<setting name="useGeneratedKeys" value="true"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="logImpl" value="LOG4J"/>
		<setting name="cacheEnabled" value="true"/>
	</settings>

logImpl属性可能的属性值有SLF4J、COMMONS_LOGGING、LOG4J、LOG4J2、JDK_LOGGING、STDOUT_LOGGING、NO_LOGGING或日志实现类的完全限定名,原因是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);

另外,Configuration类中维护了一个logImpl属性,当MyBatis框架启动时,解析主配置文件中的logImpl参数,然后调用Configuration类的setLogImpl()方法设置日志实现类。setLogImpl()方法内容如下:

  public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
      this.logImpl = logImpl;
      // 调用LogFactory类的useCustomLogging()方法指定日志实现类
      LogFactory.useCustomLogging(this.logImpl);
    }
  }

如上面的代码所示,在Configuration类的setLogImpl()方法中,调用LogFactory类的useCustomLogging()方法指定日志实现类。而MyBatis中所有的Log实例都是由LogFactory创建的,这样就保证了整个系统输出日志使用同一种框架。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4634.html

分享给朋友:

“MyBatis日志实现二-MyBatis日志实现” 的相关文章

java调用API操作GitLab

最近需要在一个WEB项目中集成GitLab,用到了GitLab的API操作,在网上找了很久都是说直接调用GitLab的Http接口,而且API官方只有javadoc没有其它说明文档,特别记录下,以备查询。这里采用Token的认证方式,因此需要先登陆GitLab新建一个Token,创建方式如下:创建完...

HTML5+眼球追踪?黑科技颠覆传统手机体验

今天,iH5工具推出一个新的神秘功能——眼动追踪,可以通过摄像头捕捉观众眼球活动!为了给大家具体演示该功能的使用,我做了一个案例,供大家参考。实际效果如下:案例比较简单,就是通过眼动功能获取视觉焦点位置,剔除用户看中的牌。现在,舞台的属性中多了一个“启用眼动”的选项,另外,还多了一个“启用摄像头”的...

双子座应用程序推出模型切换器以在Android上访问2.0

#头条精品计划# 快速导读谷歌推出了Gemini 2.0 Flash实验版,现已在其安卓应用中可用,之前仅在gemini.google.com网站上提供。新版本的15.50包含模型切换器,用户可以在设置中选择不同模型,包括1.5 Pro、1.5 Flash和2.0 Flash实验版。谷歌提醒,2.0...

22《Vue 入门教程》VueRouter 路由嵌套

1. 前言本小节我们介绍如何嵌套使用 VueRouter。嵌套路由在日常的开发中非常常见,如何定义和使用嵌套路由是本节的重点。同学们在学完本节课程之后需要自己多尝试配置路由。2. 配置嵌套路由实际项目中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层...

关于Vue页面跳转传参,参数不同, 但页面只获取参数一次的问题

#头条创作挑战赛#1.问题描述问题描述: element 展示表格(页面A),点击表格的每一行的查看详情按钮,可以携带此行的信息参数跳转到另一个页面(页面B),但是从A页面到B页面,只有第一次跳转的时候B页面可以获取到A页面的参数,返回再次A->B ,B页面无法获取到参数。2.解决办法:方法一...

一套代码,多端运行——使用Vue3开发兼容多平台的小程序

介绍Vue3发布已经有一段时间了,从目前来看,其生态还算可以,也已经有了各种组件库给予了支持,但是不管是Vue3还是Vue2都无法直接用来开发小程序,因此国内一些技术团队针对Vue开发了一些多端兼容运行的开发框架,今天来体验一下使用Taro来体验一下使用Vue3开发多平台运行的小程序,以便于兼容各大...