警告
本文最后更新于 2020-12-20,文中内容可能已过时。
mybatis源码系列文章,示例中带有中文注释的源码 copy 自 https://gitee.com/wlizhi/mybatis-3
链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。
1 适配器模式
适配器模式(Adapter Patter)是作为两个不兼容接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不
兼容而不能一起工作的那些类可以一起工作。
适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组件的时候经常被使用到。
注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对其进行重构。
2 代理模式
定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。
目的:
- 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性。
- 通过代理对象对原有的业务增强。
3 Mybatis Log 模块核心类结构图
4 Mybatis Log 模块的核心类
- LogFactory log对象工厂
- Log mybatis 中自定义的一个 log 接口。使用了适配器模式,所有的第三方日志组件引入后,通过适配类,最终都会被转换为 Log 类型,mybatis 源码中的日志,统一使用 Log 接口。
- BaseJdbcLogger jdbc包中所有日志增强的抽象基类,使用动态代理对目标进行日志增强。
- ConnectionLogger 对 Connection 的日志增强类。
- PreparedStatementLogger 对 PreparedStatement 的日志增强类。
- ResultSetLogger 对 ResultSet 的日志增强类。
- StatementLogger 对 Statement 的日志增强类。(一般使用 PreparedStatementLogger,且与前者原理相同,不列举。)
5 Log工厂类 LogFactory
在类加载器加载 LogFactory 时,通过静态代码块,按照一定顺序,加载第三方日志组件,只要有一个加载成功,下面的就不再加载。
第三方日志组件加载优先级:slf4j -> commonsLogging -> log4J2 -> log4j -> JdkLog
加载后将对应的日志组件的构造函数缓存到静态成员中。最终通过 LogFactory.getLog(Class<?> clazz)
进行日志对象的获取。
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
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;
static {
// 注意这里实在静态代码块中执行,在类加载器,第一次加载 LogFactory 时,就会执行这块代码,进行日志具体实现的初始化。
// 按照优先级尝试加载日志实现类。第三方日志插件加载优先级: slf4j -> commonsLogging -> log4J2 -> log4j -> JdkLog
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
/** 构造函数私有,不允许外界创建该类实例 */
private LogFactory() { }
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {// 获取log对象,通过静态块中加载log实现类时,缓存的log实现构造函数,以反射的方式创建log对象。
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
// 省略部分方法...
private static void tryImplementation(Runnable runnable) {
// 这个方法是加载log的方法。当构造方法为空,才执行方法。如果加载不到,则会忽略异常。
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// 注意这里忽略了异常,即当加载一个log实现,加载不到时,就会尝试加载另一种实现。
// 而加载不到的实现会抛出 java.lang.NoClassDefFoundError 到这里,最终会忽略掉异常。
// ignore
}
}
}
// 通过指定的 log 类来初始化构造方法
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 反射创建 log 实例。
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 = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
|
tryImplementation() 中调用了具体加载日志组件的方法,如果项目中未配置对象的组件,则会在 run() 中抛出异常
java.lang.NoClassDefFoundError
,并在此方法中捕获,这里会忽略掉加载的异常。
注意判断条件 logConstructor == null
也就是说,只有加载失败时,才会向下执行继续加载下一个组件,如果加载成功了,则 logConstructor
是有值的。
setImplementation() 是具体加载日志组件的方法,如果加载成功,会对 logConstructor 进行赋值,并将具体使用的日志实现类进行日志打印。
6 Mybatis Log 规范 Log
mybatis将不同组件的日志,统一封装成了四种日志级别。分别是 trace、debug、warn、error。
这四种日志级别,在 Log 接口中进行了定义。
源码如下:
1
2
3
4
5
6
7
8
9
10
|
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);
}
|
7 日志增强基 BaseJdbcLogger
BaseJdbcLogger 是所有 jdbc 相关日志增强的基类,在这里定义了一些变量,用于缓存与数据库交互中传递的一些参数信息。
BaseJdbcLogger 数据结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public abstract class BaseJdbcLogger {
/** 保存preparestatment中常用的set方法(占位符赋值) */
protected static final Set<String> SET_METHODS;
/** 保存preparestatment中常用的执行sql语句的方法 */
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
/** 保存preparestatment中set方法的键值对 */
private final Map<Object, Object> columnMap = new HashMap<>();
/** 保存preparestatment中set方法的key值 */
private final List<Object> columnNames = new ArrayList<>();
/** 保存preparestatment中set方法的value值 */
private final List<Object> columnValues = new ArrayList<>();
/** 日志对象 */
protected final Log statementLog;
protected final int queryStack;
}
|
在静态块中,对 SET_METHODS 以及 EXECUTE_METHODS 进行了初始化。
初始化源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public abstract class BaseJdbcLogger {
static {
// 反射获取 PreparedStatement 中所有 set 开头且有参数的方法名称
SET_METHODS = Arrays.stream(PreparedStatement.class.getDeclaredMethods())
.filter(method -> method.getName().startsWith("set"))
.filter(method -> method.getParameterCount() > 1)
.map(Method::getName)
.collect(Collectors.toSet());
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
}
|
并提供了一些设置参数以及获取参数值的字符串表示的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public abstract class BaseJdbcLogger {
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
protected String getParameterValueString() {
// 将参数的值、参数类型,封装成string返回。
List<Object> typeList = new ArrayList<>(columnValues.size());
for (Object value : columnValues) {
if (value == null) {
typeList.add("null");
} else {
typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
}
}
final String parameters = typeList.toString();
return parameters.substring(1, parameters.length() - 1);
}
}
|
还有一些其他方法,不再列举。
8 连接日志增强 ConnectionLogger
ConnectionLogger 实现了 InvocationHandler,且内部持有了 Connection 实例。它是 Connection 的日志增强类,从这个实现接口可以看出,使用的是 jdk 动态代理。
它的核心在 invoke 方法,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
/** 使用代理模式对连接进行日志增强。这里封装了连接对象 */
private final Connection connection;
@Override
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 或者 prepareCall 方法,则参数列表中,第一个参数一定是 sql 语句,这里打印 sql 语句。
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
// 在打印sql语句的时候,将sql中的多余空白去除,替换为单个空格。
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
// 调用业务方法
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
// 创建 PreparedStatement 的代理对象,即对其进行日志增强,与此类相似。使用 PreparedStatementLogger 作为增强类。
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
// 如果是 createStatement 方法,执行业务方法
Statement stmt = (Statement) method.invoke(connection, params);
// 对 Statement 创建代理,使用 StatementLogger 作为增强类。
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else { // 如果以上方法都不是,则直接执行业务方法。即只对上面两个 if 分支中判断的方法进行日志增强。
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
|
在 invoke() 中,对 Connection 进行了日志增强,针对 prepareStatement()、prepareCall()、createStatement() 进行日志打印,并对其返回值生成代理。
还有一个生成 Connection 代理的方法:
1
2
3
4
5
6
7
8
|
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
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);
}
}
|
9 PreparedStatementLogger
与 ConnectionLogger 类似,它也是一个 InvocationHandler 实现类。内部持有了 PreparedStatement 对象。对一些特定方法添加了日志增强,
并对方法的返回值 ResultSet 生成代理对象。
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
private final PreparedStatement statement;
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
// 这个方法的增强内容,都是打印 debug 日志,在打印之前,判断 isDebugEnabled(),为了提高性能,
// 因为打印的内容可能存在字符串拼装,相对判断一个布尔值,相对要消耗性能许多,而 debug 一般在生产环境并不会激活。
// 如果是 Object 中声明的方法,则直接跳过,执行业务方法。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// BaseJdbcLogger 中静态块中声明的方法集合。如果当前方法是集合中的方法,则打印日志。
if (EXECUTE_METHODS.contains(method.getName())) {
// 如果 debug 级别是开启的,则打印日志。打印内容是参数,比如一个 insert 语句,则这里会打印 mapper 方法中传递的参数及类型。
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
// 清除字段信息
clearColumnInfo();
// 如果是 executeQuery 方法,则直接调用方法。并对 ResultSet 生成代理对象。
if ("executeQuery".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
// 如果是 setXXX 方法,且有方法参数,则将这些参数封装起来。 封装后执行具体的方法。
} else if (SET_METHODS.contains(method.getName())) {
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
// 如果是 getResultSet 方法,则执行方法后,将 ResultSet 生成代理,进行日志增强。
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
// 如果是 getUpdateCount 方法,则在执行方法后,打印 debug 日志。
} else if ("getUpdateCount".equals(method.getName())) {
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
|
创建 PreparedStatement 代理的方法:
1
2
3
4
5
6
7
8
9
|
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
// 使用当前类作为增强类,创建 PreparedStatement 的代理对象。
// 符合单一职责原则,创建目标代理对象的工作应该交给与之相关的类来做,而本类就是目标类的增强。
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
}
|
10 结果集日志增强 ResultSetLogger
ResultSetLogger 实现了 InvocationHandler,是对 ResultSet 的日志增强。
invoke() 源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
// 如果是 Object 中声明的方法,则直接跳过,执行业务方法。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
// 执行 next 方法,判断是否还有数据。
if ("next".equals(method.getName())) {
// 如果当前方法是 next,返回值为 true,对变量自增,如果启用了 trace(比debug更低的级别) 级别,则打印 ResultSet 中的值。
if ((Boolean) o) {
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {
debug(" Total: " + rows, false);
}
}
// 清楚字段信息。
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
|
创建 ResultSet 代理对象的方法源码:
1
2
3
4
5
6
7
8
9
10
|
public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
// 使用当前类作为增强类,创建 ResultSet 的代理对象。
// 符合单一职责原则,创建目标代理对象的工作应该交给与之相关的类来做,而本类就是目标类的增强。
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
ClassLoader cl = ResultSet.class.getClassLoader();
return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler);
}
}
|
11 小结
对第三方日志组件的支出,通过适配器模式,对不同的日志组件,提供不同的适配器类,进行转换支持。
对不同节点的日志的打印支持,通过动态代理的方式,进行日志信息的增强。这样的好处是对原有业务方法是无侵入的,与业务功能解耦。
相较于 spring 中 aop 的实现原理是不同的,spring 中如果定义了许多增强方法,会将这些增强统一封装成一个 Advisor 列表。执行的时候,
会生成 MethodInterceptor 执行链。而代理对象,最终只生成一个,只是在代理对象的增强方法中,封装了这个执行链。
而 mybatis 中相对简单暴力一些,只是生成代理,就像使用 ConnectionLogger 增强创建 Connection 代理对象时,其内部封装的其实也是一个代理对象,
并非是原始链接。也就是说,在 mybatis 中对一个实例的多次增强,最终每一个增强都会创建代理对象。即对代理对象进行代理。