MyBatis 源码篇-日志模块1
在 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的更多相关文章
- MyBatis 源码篇-日志模块2
上一章的案例,配置日志级别为 debug,执行一个简单的查询操作,会将 JDBC 操作打印出来.本章通过 MyBatis 日志部分源码分析它是如何实现日志打印的. 在 MyBatis 的日志模块中有一 ...
- MyBatis 源码篇-插件模块
本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...
- MyBatis 源码篇-MyBatis-Spring 剖析
本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...
- MyBatis 源码篇-Transaction
本章简单介绍一下 MyBatis 的事务模块,这块内容比较简单,主要为后面介绍 mybatis-spring-1.**.jar(MyBatis 与 Spring 集成)中的事务模块做准备. 类图结构 ...
- MyBatis 源码篇-DataSource
本章介绍 MyBatis 提供的数据源模块,为后面与 Spring 集成做铺垫,从以下三点出发: 描述 MyBatis 数据源模块的类图结构: MyBatis 是如何集成第三方数据源组件的: Pool ...
- MyBatis 源码篇-资源加载
本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...
- MyBatis 源码篇-SQL 执行的流程
本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...
- MyBatis 源码篇-整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...
- myBatis源码解析-日志篇(1)
上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路.在学习源码的路上也掌握了一些设计模式,可所谓一举两得.本次打算写Mybatis的源码解读. 准备工作 1. 下载mybatis源码 下 ...
随机推荐
- PyTricks-How to Sort a Python dict
字典的键值排序 import operator # 1表示按照值排序 xs = {"a": 4, "b": 3, "c": 2, " ...
- zabbix :web 界面显示的监控项值为0或者空
[参考文章]:[错误汇总]zabbix_get 的值一直为 0 1. 问题现象 zabbix 版本:3.4: server 端部署在 192.168.145.134 ,agent 节点部署在 192. ...
- impdp不是内部或外部命令(Linux)
1.在windows环境变量下,配置path系统变量. 右击“我的电脑”—>“高级”—>“环境变量”—>“系统变量”—>path:然后添加";oracle导入导出命令 ...
- 第04组 Beta冲刺(1)
队名:斗地组 组长博客:地址 作业博客:Beta冲刺(1/4) 各组员情况 林涛(组长) 过去两天完成了哪些任务: 1.分配展示任务 2.收集各个组员的进度 3.写博客 展示GitHub当日代码/文档 ...
- linux Host key verification failed.错误
Host key verification failed. 1. ssh-keygen -R 你要访问的IP地址 2. ssh-keygen -R 108.61.163.242
- Heat map 绘图神奇
https://study.163.com/provider/400000000398149/index.htm?share=2&shareId=400000000398149(博主视频教学主 ...
- AlarmManager(闹钟服务)
1.Timer类与AlarmManager类区别: 对Timer就是定时器,一般写定时任务的时候 肯定离不开他,但是在Android里,他却有个短板,不太适合那些需要长时间在后台运行的 定时任务,因为 ...
- wan口的ip是干什么用的
wan口的ip是外网的ip,属于公网的ip.主要用于外网的识别,WAN口主要用于连接外部网络,如ADSL.DDN.以太网等各种接入线路:而LAN口用来连接家庭内部网络,主要与家庭网络中的交换机.集线器 ...
- 001-jdk-数据结构-总览
一.集合类简介 1.1.背景 数组是很常用的一种的数据结构,用它可以满足很多的功能,但是,有时会遇到如下这样的问题: 1.需要该容器的长度是不确定的. 2.需要它能自动排序. 3.需要存储以键值对方式 ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_12-页面静态化-页面静态化流程
需要知道数据结构,然后去做模板标签.首先需要获取页面的数据模型.下面的每一条记录都代表一个页面. 比如这个轮播图.就需要提前给这个轮播图编写一个模板 有很多的页面如果知道每个页面的dataUrl.例如 ...