在 Java 开发中常用的日志框架有 Log4j、Log4j2、Apache Common Log、java.util.logging、slf4j 等,这些日志框架对外提供的接口各不相同。本章详细描述 MyBatis 是如何通过适配器的方式集成和复用这些第三方框架的。

日志适配器

MyBatis 的日志模块位于 org.apache.ibatis.logging 包中,该模块中 Log 接口定义了日志模块的功能,然后分别为不同的日志框架定义不同的日志适配器,这些日志适配器都继承 Log 接口,LogFactory 工厂负责创建对应的日志框架适配器。

下面来看 jdk14 日志适配器模式的类图:

在 LogFactory 类加载时会执行其静态代码快,按照顺序加载并实例化对应日志框架的适配器,然后用 logConstructor 字段记录当前使用的第三方日志框架的适配器。

tryImplementation() 方法使用 try cache 捕获了加载日志框架过程中产生的异常信息,且在 cache 中没做任何操作,所以不会有异常信息抛出,正常执行。如果没有引入任何日志框架,会使用 useJdkLogging ,这是 JDK 自带的日志工具。

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; // 尝试加载每种日志框架,调用顺序是:
// useSlf4jLogging --> useCommonsLogging --> useLog4J2Logging -->
// useLog4JLogging --> useJdkLogging --> useNoLogging
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();
}
});
} 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);
}
} 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);
} // 尝试加载日志框架
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// 加载异常被忽略了
// ignore
}
}
} 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字段
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
} }

引入 SLF4J 日志框架

SLF4J 只是日志框架统一的接口定义(参考:http://www.slf4j.org/manual.html),还需要引入实现,pom.xml 文件增加如下内容:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.28</version>
<scope>test</scope>
</dependency>

在 LogFactory 类加载的时候,其静态代码块会将 logConstructor 初始化为 SLF4J 适配器的构造函数。

通过 simplelogger.properties 属性文件配置日志级别为 debug,属性文件的命名是固定的,参考 org.slf4j.impl.SimpleLoggerConfiguration 类:

执行一个简单的查询操作,会输出如下日志信息:

[main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
[main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
[main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
[main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
[main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
[main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
[main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1426329391.
[main] DEBUG com.yjw.mybatis.dao.StudentMapper.selectByPrimaryKey - ==> Preparing: select id, name, sex, selfcard_no, note from t_student where id = ?
[main] DEBUG com.yjw.mybatis.dao.StudentMapper.selectByPrimaryKey - ==> Parameters: 1(Long)
[main] DEBUG com.yjw.mybatis.dao.StudentMapper.selectByPrimaryKey - <== Total: 1

Slf4jImpl 类实现了 org.apache.ibatis.logging.Log 接口,并封装了 SLF4J 的日志接口,Log 接口的功能全部通过调用 SLF4J 的日志接口实现。

Slf4jImpl 构造函数中实现了 SLF4J 的版本区分,根据不同的版本使用不同的接口实现日志功能,源码如下:

public class Slf4jImpl implements Log {

  private Log log;

  public Slf4jImpl(String clazz) {
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);
} @Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
} @Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
} @Override
public void error(String s, Throwable e) {
log.error(s, e);
} @Override
public void error(String s) {
log.error(s);
} @Override
public void debug(String s) {
log.debug(s);
} @Override
public void trace(String s) {
log.trace(s);
} @Override
public void warn(String s) {
log.warn(s);
} }

MyBatis 源码篇

MyBatis 源码篇-日志模块1的更多相关文章

  1. MyBatis 源码篇-日志模块2

    上一章的案例,配置日志级别为 debug,执行一个简单的查询操作,会将 JDBC 操作打印出来.本章通过 MyBatis 日志部分源码分析它是如何实现日志打印的. 在 MyBatis 的日志模块中有一 ...

  2. MyBatis 源码篇-插件模块

    本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...

  3. MyBatis 源码篇-MyBatis-Spring 剖析

    本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...

  4. MyBatis 源码篇-Transaction

    本章简单介绍一下 MyBatis 的事务模块,这块内容比较简单,主要为后面介绍 mybatis-spring-1.**.jar(MyBatis 与 Spring 集成)中的事务模块做准备. 类图结构 ...

  5. MyBatis 源码篇-DataSource

    本章介绍 MyBatis 提供的数据源模块,为后面与 Spring 集成做铺垫,从以下三点出发: 描述 MyBatis 数据源模块的类图结构: MyBatis 是如何集成第三方数据源组件的: Pool ...

  6. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  7. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  8. MyBatis 源码篇-整体架构

    MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...

  9. myBatis源码解析-日志篇(1)

    上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路.在学习源码的路上也掌握了一些设计模式,可所谓一举两得.本次打算写Mybatis的源码解读. 准备工作 1. 下载mybatis源码 下 ...

随机推荐

  1. 浅谈 es6 箭头函数, reduce函数介绍

    今天来谈一下箭头函数, es6的新特性 首先我们来看下箭头函数长什么样子, let result = (param1, param2) => param1+param2; 上述代码 按照以前书写 ...

  2. 微服务中使用MQ——RabbitMQ

    概念 什么是消息 消息是指在两个独立的系统间传递的数据.这两个系统可以是两台计算机,也可以是两个进程. 消息是平台无关和语言无关的! 什么是队列 队列是一种数据结构,内部是用数组或链表实现的, 队列的 ...

  3. 图的深度优先遍历(DFS)和广度优先遍历(BFS)算法分析

    1. 深度优先遍历 深度优先遍历(Depth First Search)的主要思想是: 1.首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点: 2.当没有未访问过的顶点时,则回 ...

  4. kotlin中访问封闭作用内的变量

    在java中,匿名对象访问封闭作用域内的变量,需要用final 声明变量在java8中,如果只是使用封闭作用域内的变量,该变量并不需要使用final,但是一旦修改值,就需要使用final 来声明变量. ...

  5. 使用ffmpeg裁剪和合并视频

    剪切视频 使用 -ss 和 -t 选项,从第0秒开始,向后截取31秒视频,并保存 ffmpeg -ss :: -i video.mp4 -vcodec copy -acodec copy -t :: ...

  6. 停止monkey的方法

    注意 Monkey启动后会不断地向被测对象发送随机事件流,直到事件执行完毕或者发生异常时才停止.在Monkey运行过程中,即便断开 与PC的连接,Monkey依然可以在手机上继续运行. 停止Monke ...

  7. 请求路径@PathVariable注释中有点.英文句号的问题(忽略英文句号后面的后缀)

    前端页面请求地址 <video id=example-video width=960 height=540 class="video-js vjs-default-skin" ...

  8. ubuntu kylin 18.04 使用 wine 安装 EasyConnect 的windows版本

    首先下载wine: sudo apt-get install wine-stable 然后使用wine安装安装包EXE文件(安装包你自己去下): wine EasyConnectInstaller.e ...

  9. Centos7 系统更改apache默认网站目录(解决You don't have permission to access / on this server问题)

    当我们在Centos7中配置好Apache时,发现apache默认解析目录是在 /var/www/html,也就是说当访问服务器 IP 或者本地 localhost 时, 默认定位到这个目录里的 in ...

  10. cvpr2015papers

    @http://www-cs-faculty.stanford.edu/people/karpathy/cvpr2015papers/ CVPR 2015 papers (in nicer forma ...