日志模块的需求
- mybatis没有自己提供日志的实现类,需要接入第三方的日志框架。但第三方的日志组件的日志级别等都不一致。所以mybatis统一往外提供了
trace debug warn error四个级别 - 自动扫描日志实现 mybatis需要实现,并且第三方日志的加载有固定级别 : slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;
- 日志功能需要优雅的嵌入到主体功能中
包结构

整体的代码量其实很少,主要是设计思想的学习吧
针对需求一,本身无实现,各日志框架不统一,使用适配器模式
适配器模式:
作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。
角色: Targe 希望被转成的目标对象 Log
Adaptee 谁将会被转换成target Slfj 、log4j等日志框架的logger
Adapter 适配者 log4j包、slf4j包下面的Slf4jLoggerImpl 适配对象
针对需求二,多个第三方日志框架,mybatis如何实现加载,加载哪一个?
public final class LogFactory {
/**
* 部分框架有MARKER
*/
public static final String MARKER = "MYBATIS";
/**
* 最终获取到的日志框架的构造
*/
private static Constructor<? extends Log> logConstructor;
static {
// 这里其实就是核心,按照顺序来执行
// 尝试获取logConstructor,以slf4j为例,跟进去
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();
}
});
}
// 如果构造方法为空,执行run方法,注意这里只是run,没有开线程
// 猜测了一下这么做的原因,可能仅为了面向接口,实现隔离吧
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore 因为存在找不到框架的问题,所以找不到直接忽略掉异常
// 会担心都找不到呢? 其实上面静态方法最后两个,一个是jdk,一个是noLog,肯定可以找到的
}
}
}
useSlf4jLogging 具体做了什么
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
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 = candidate; // 获取到了构造方法
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
// 报错抛异常,其实这个异常上面也只是吃掉了,没有处理
}
}
构造方法创建对象
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);
}
}
针对需求三,mybatis如何优雅的将日志功能嵌入到代码中
思考:哪里需要日志? sql需要打印出来 参数需要打印出来 结果和条数需要打印出来
再思考:一般我们希望优雅的加入功能,怎么添加? 代理模式来增强
先介绍下代理模式:
抽象对象: 声明了真实对象和代理对象的接口
真实对象: 真正处理业务的对象
代理对象: 内部包括真实对象,对被代理对象进行增强,相当于访问者和真实对象的中介,经纪人
静态代理和动态代理(jdk cglib)
静态: 实现相同的接口,内部维护真实对象的引用,编译期class就存在
优点:可以在不修改代码的前提下,进行增强
缺点: 冗余,可能会有很多这样的class,并且得实现所有的方法
当修改接口时,真实对象和代理对象都得修改
动态: 在运行期间动态生成class文件。
主要涉及 Proxy InvocationHandler
具体不细说,后续写一篇动态代理源码的分析。
刚刚我们也分析一般哪里需要日志,就去对应的模块找 参数在statment 结果再resultSet sql在哪,不太清楚,就按照sql执行开头寻找 Executor

doQuery: 构建sql语句 stmt = prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连接,获取连接有ConnectionLogger,跟进
Connection connection = getConnection(statementLog);
//调用StatementHandler/prepareStatement的prepare()
stmt = handler.prepare(connection);
//调用StatementHandler.parameterize
handler.parameterize(stmt);
return stmt;
}
// 拿到db的conn后,利用ConnectionLogger生成代理对象
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
//如果需要打印Connection的日志,返回一个ConnectionLogger(代理模式)
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
// 构建statement ,
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//实例化Statement,内部不跟,会根据不同的实现,创建statement和prepareStatement
// connection.prepareStatement or connection.createStatement
statement = instantiateStatement(connection);
//设置超时
setStatementTimeout(statement);
//设置读取条数
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
// 到这里我们可以看出来,还是调用conn里面,直接看ConnectionLogger代理类的invoke即可
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 预处理
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 获取PreparedStatement,回去实例后,继续包装成 PreparedStatementLogger
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
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())) {
// 获取Statement ,回去实例后,继续包装成 StatementLogger
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);
}
}
// 返回statement以后,query
ps.execute();
String[] resultSets = mappedStatement.getResulSets();
// stament的getResultSet,继续去PrePareStatementLogger内部看下
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
// 执行的时候,先打印参数日志
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {
// 返回resultSet 的时候,包装成ResultSetLogger
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
} 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);
} else if ("getResultSet".equals(method.getName())) {
// 返回时包装ResultSetLogger
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} 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);
}
}
// 后续处理result的时候,调用rsw.getResultSet().next(),继续去ResultSetLogger看下
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {
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);
}
}
下面是整体的一个日志模块的一个架构图吧,可以帮助理解:

本文深入探讨MyBatis日志模块的设计与实现,包括日志级别的统一、第三方日志框架的适配策略及优雅的日志功能嵌入方式。
518

被折叠的 条评论
为什么被折叠?



