myBatis源码解析-日志篇(1)
上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路。在学习源码的路上也掌握了一些设计模式,可所谓一举两得。本次打算写Mybatis的源码解读。
准备工作
1. 下载mybatis源码
下载地址:https://github.com/mybatis/mybatis-3
2. 下载mybatis-parent源码
下载地址:https://github.com/mybatis/parent
3. 编译
进入mybatis-paren所在文件夹
mvn clean install
进入mybatis所在文件夹
mvn clean
mvn install -Dmaven.test.skip=true
4. 用IDEA或Eclipse打开mybatis即可
源码分析-日志模块
1. 日志基础包
package org.apache.ibatis.logging; // mybatis自定义接口,提供四种级别 error->debug->trace->warn
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提供了日志的四种级别,error->debug->trace->warn
2. 从源码可以看到,mybatis提供了如jdk14,log4j,log4j2等日志实现,分析常用的如log4j2源码
// log4j的适配器
public class Log4j2Impl implements Log { // 真正提供日志能力的log4j的日志类
private Log log;
// 构造方法,导入真正的实现类
public Log4j2Impl(String clazz) {
Logger logger = LogManager.getLogger(clazz); if (logger instanceof AbstractLogger) {
log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
} else {
log = new Log4j2LoggerImpl(logger);
}
} public boolean isDebugEnabled() {
return log.isDebugEnabled();
} public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
// 调用真正的实现方法
public void error(String s, Throwable e) {
log.error(s, e);
}
// 调用真正的实现方法
public void error(String s) {
log.error(s);
}
// 调用真正的实现方法
public void debug(String s) {
log.debug(s);
}
// 调用真正的实现方法
public void trace(String s) {
log.trace(s);
}
// 调用真正的实现方法
public void warn(String s) {
log.warn(s);
} }
综上,可以看到,mybatis并没有提供真正的日志实现接口,只是定义了一套自己的日志接口,其实现交给真正的具体日志类(如log4j,log4j2)。此处用到了适配器模式。比如log4j2,mybatis提供自定义接口,提供log4j2适配器继承自定义接口,在log4j2适配器里调用log4j2的真实方法来实现自己的接口。
3.各日志默认调用流程
看源码包中有很多日志实现,那具体的默认调用流程是怎样。查看org.apache.ibatis.logging包中的LogFactory类。看名字就知道是用到了工厂模式。
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;
// 自动扫描日志实现,并且第三方日志插件加载优先级如下
// 类加载时会默认实现静态方法,实现顺序为slf4j->commonsLoging->log4j2->log4j->jdklog
static {
// 调用tryImplementation方法
tryImplementation(new Runnable() {
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
public void run() {
useNoLogging();
}
});
}
......
LogFactory有默认的日志加载顺序,写在静态代码块中。默认实现顺序为slf4j->commonsLoging->log4j2->log4j->jdklog。接着分析tryImplementation方法
// 此方法调用的是线程的run方法,仔细看,是直接run,不是start,所以不是多线程
private static void tryImplementation(Runnable runnable) {
// 判断全局的日志构造方法是否为空,若为空,则调用线程的run方法
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// 对没有具体实现的日志类,直接忽略,不抛异常,
// ignore
}
}
}
查看具体的run方法,此处继续分析log4j2的run方法。
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
// 使用到了java的反射机制
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 利用反射获取log4j2的真实构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(new Class[] { String.class });
// 获取log4j2的实例
Log log = candidate.newInstance(new Object[] { LogFactory.class.getName() });
log.debug("Logging initialized using '" + implClass + "' adapter.");
// 赋值给全局变量logConstructor
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
以log4j2为例,分析了具体的日志实现。但我们使用mybatis时通常会在执行sql的时候,对sql进行打印输出。所以接下来查看mybatis如何使用这些日志接口。
4. 分析org.apache.ibatis.logging.jdbc下的类
在源码分析前,回顾下jdbc连接数据库方式。
........
Class.forName("com.mysql.jdbc.Driver");
//2.获得数据库链接
Connection conn=DriverManager.getConnection(URL, USER, PASSWORD);
//3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
Statement st=conn.createStatement();
ResultSet rs=st.executeQuery("select * from user");
.........
加载数据库忽略,此处有Connection,Statement,ResultSet等参数,查看org.apache.ibatis.logging.jdbc下的类,都有对应的log类,先分析BaseJdbcLogger类。
public abstract class BaseJdbcLogger {
// PrepareedStatement下的所有set方法
protected static final Set<String> SET_METHODS = new HashSet<String>();
protected static final Set<String> EXECUTE_METHODS = new HashSet<String>();
// PrepareecdStatement 设置的key value pair
private Map<Object, Object> columnMap = new HashMap<Object, Object>();
// PreparedStatement 设置的column
private List<Object> columnNames = new ArrayList<Object>();
// PrepareedStatement 设置的value
private List<Object> columnValues = new ArrayList<Object>();
protected Log statementLog;
protected int queryStack;
/*
* Default constructor
*/
public BaseJdbcLogger(Log log, int queryStack) {
this.statementLog = log;
if (queryStack == 0) queryStack = 1;
this.queryStack = queryStack;
}
// 初始化默认的一些set方法
static {
SET_METHODS.add("setString");
SET_METHODS.add("setInt");
SET_METHODS.add("setByte");
SET_METHODS.add("setShort");
SET_METHODS.add("setLong");
SET_METHODS.add("setDouble");
SET_METHODS.add("setFloat");
SET_METHODS.add("setTimestamp");
SET_METHODS.add("setDate");
SET_METHODS.add("setTime");
SET_METHODS.add("setArray");
SET_METHODS.add("setBigDecimal");
SET_METHODS.add("setAsciiStream");
SET_METHODS.add("setBinaryStream");
SET_METHODS.add("setBlob");
SET_METHODS.add("setBoolean");
SET_METHODS.add("setBytes");
SET_METHODS.add("setCharacterStream");
SET_METHODS.add("setClob");
SET_METHODS.add("setObject");
SET_METHODS.add("setNull");
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
// setColumn方会记录设置的column和对应的value
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
再来分析ConnectionLogger类
// 实现InvocationHandler就知道是个代理类
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
// 真正的connection
private Connection connection; private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn;
}
// 增强
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
// 若是从Object继承的方法直接忽略
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 若是prepareStatement方法
if ("prepareStatement".equals(method.getName())) {
// 打印sql参数
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 调用connection真实方法
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
// 将生成的PreparedStatement也构建成代理对象
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) { // 若是prepareCall方法
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) { // 若是createStatement方法
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} /*
* Creates a logging version of a connection
*
* @param conn - the original connection
* @return - the connection with logging
*/
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
} /*
* return the wrapped connection
*
* @return the connection
*/
public Connection getConnection() {
return connection;
} }
后续如PreparedStatementLogger,ResultSetLogger等也是一样,都是封装了PreparedStatement或ResultSet,在执行真实语句前后进行日志打印,打印执行的Sql语句,此处用到了代理模式。熟悉AOP的可能对此有了解。
myBatis源码解析-日志篇(1)的更多相关文章
- myBatis源码解析-数据源篇(3)
前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...
- myBatis源码解析-反射篇(4)
前沿 前文分析了mybatis的日志包,缓存包,数据源包.源码实在有点难顶,在分析反射包时,花费了较多时间.废话不多说,开始源码之路. 反射包feflection在mybatis路径如下: 源码解析 ...
- myBatis源码解析-类型转换篇(5)
前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...
- myBatis源码解析-缓存篇(2)
上一章分析了mybatis的源码的日志模块,像我们经常说的mybatis一级缓存,二级缓存,缓存究竟在底层是怎样实现的.此次开始分析缓存模块 1. 源码位置,mybatis源码包位于org.apach ...
- 【学习转载】MyBatis源码解析——日志记录
声明:转载自前辈:开心的鱼a1 一 .概述 MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,但MyBatis统一提供了trace.deb ...
- mybatis源码解析-日志适配器
1.为什么需要使用适配器? 集成第三方日志组件,屏蔽日志组件底层实现,统一提供写日志的接口. 2.什么是适配器模式 定义:将一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法 ...
- Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
随机推荐
- 《UNIX环境高级编程》(APUE) 笔记第三章 - 文件I/O
3 - 文件I/O Github 地址 1. 文件描述符 对于内核而言,所有打开的文件都通过 文件描述符 (file descriptor) 引用.当打开一个现有文件或创建一个新文件时,内核向进程返回 ...
- Python数据结构-树与树的遍历
树:是一种抽象的数据类型 树的作用:用来模拟树状结构性质的数据集合 树的特点: 每个节点有零个或者多个节点 没有父节点的节点,叫做根节点 每一个根节点有且只有一个父节点 除了根节点外,每个节点可以分成 ...
- 别逃避,是时候来给JVM一记重锤了
今天是猿灯塔“365天原创计划”第2天. 今天讲: 为什么写这个主题呢? 之前看到不少同学在讨论, 今天呢火星哥抽出点时间来帮大家整理一下关于JVM的一些知识点 一.JVM是什 ...
- SQL注入原理及代码分析(一)
前言 我们都知道,学安全,懂SQL注入是重中之重,因为即使是现在SQL注入漏洞依然存在,只是相对于之前现在挖SQL注入变的困难了.而且知识点比较多,所以在这里总结一下.通过构造有缺陷的代码,来理解常见 ...
- 聊聊Java
聊聊Java 笔记源于 视频教程Bilibili:狂神说Java 关注公众号:狂神说 能干嘛? 热度 TIOBE 狂神计划 三高:高可用.高性能.高并发 全球几千万的程序员都会Java,真正精通的不到 ...
- 重学 Java 设计模式:实战策略模式「模拟多种营销类型优惠券,折扣金额计算策略场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 文无第一,武无第二 不同方向但同样努力的人,都有自身的价值和亮 ...
- 「疫期集训day4」硝烟
那真是一阵恐怖的炮击(that boomed booms),响亮的炮音(that noise),滚滚的硝烟(that smoke),熊熊的火焰在围绕着我们前进...小心前进(go and be car ...
- Xenon's Attack on the Gangs(树规)
题干 Input Output Example Test 1: Test 2: 3 5 1 2 1 2 2 3 1 3 1 4 3 5 3 10 Tips 译成人话 给n个结点,n-1条无向边.即一棵 ...
- Pop!_OS配置Python环境
Pop!_OS配置Python环境 #0x0 安装vscode #0x1 配置vscode #0x0 安装vscode 见vscode安装 #0x1 配置vscode 安装Python插件 安装pyl ...
- U盘+grub2安装centos8实战
1. U盘准备 这里的U盘也可以换成硬盘 grub2安装一直失败,怀疑U盘坏了,下面命令修复了一下 [root@host2 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE ...