在 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. csp-s模拟测试112 & csp-s模拟测试113

    考前两天模拟. Day1直接炸飞,T1浪费的时间太长,对拍+调试了一个多小时但复杂度还不能过,最后5分钟想出来了解决方案但是已经打不出来了.T2读入出了事故RE0.T3打了假贪心. Day2心态几乎也 ...

  2. 重读APUE(1)-lseek注意事项

    lseek使用的注意事项: 1. lseek的返回值,成功返回新的文件偏移量,失败返回-1,不能用<0判断:因为文件偏移量可能是正的,也可能是负的,所以不能使用<0判断成功与否:注意:对于 ...

  3. 拆分项目搞成framework 实例

    目前工作中遇到的问题,是讲项目三大模块拆分, 将Discover.Shop和Events的源代码拆分到独立的项目中 拆分是个麻烦事,里面相互依赖很多 ,打包编译也异常复杂,各种报错 编译Event 遇 ...

  4. 第11组 Beta冲刺(2/5)

    第11组 Beta冲刺(2/5)   队名 不知道叫什么团队 组长博客 https://www.cnblogs.com/xxylac/p/11997386.html 作业博客 https://edu. ...

  5. Redis | 一文轻松搞懂redis集群原理及搭建与使用

    转载:https://juejin.im/post/5ad54d76f265da23970759d3 作者:SnailClimb 这里总结一下redis集群的搭建以便日后所需同时也希望能对你有所帮助. ...

  6. 背包DP 方案数

    题目 1 P1832 A+B Problem(再升级) 题面描述 给定一个正整数n,求将其分解成若干个素数之和的方案总数. 题解 我们可以考虑背包DP实现 背包DP方案数板子题 f[ i ] = f[ ...

  7. [go]grpc远程接口调用实现

    // grpc序列化/反序列化成对应语言的对象 // 1.写idl(数据类型+方法) // 2.生成对应语言的序列化/反序列化代码 // 3.方法需要自己实现 // 环境(将gopath/bin加入p ...

  8. C++ STL transform

    #include<iostream>#include<vector>#include <list>#include <algorithm>#includ ...

  9. logback 和 log4j对比,及相关配置

    Logback 一.logback的介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和log ...

  10. 使用IDEA来实现分支代码合并

    使用beyond comapre进行分支代码的合并是常用的方法,同时比较2个分支的代码,选择需要和入的代码后再提交即可. 如果是不能使用beyond comapre的情况下,使用IDEA的分支比较功能 ...