日志模块儿源码

日志模块儿是动态代理功能. 用代理模式给Connection进行了包装,添加了打印日志的能力


ü 日志组件是在哪里触发的?


org.apache.ibatis.executor.SimpleExecutor#prepareStatement 在这里触发的

//创建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}


getConnection方法

protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}


ConnectionLogger.newInstance 方法 ,在这个方法生成Collection代理对象.

/
创建连接的日志记录版本

@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);
}




1.日志模块儿

MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,而MyBatis统一提供了trace、debug、warn、error四个级别;

自动扫描日志实现,并且第三方日志插件加载优先级如下:
slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

如果有slf4j的话就优先加载slf4j的,没有就加载commonsLoging ,如果commonsLoging 也没有就加载Log4J2 的,如果Log4J2 没有就加载Log4J ,如果Log4J 也没有就加载JdkLog(JDK自带的log)

日志的使用要优雅的嵌入到主体功能中;

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;

日志模块儿源码 - 图1
Target:目标角色,期待得到的接口.
Adaptee:适配者角色,被适配的接口.
Adapter:适配器角色,将源接口转换成目标接口.

适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组件的时候经常被使用到;

比如有很多支付系统需要对接,比如支付宝,网银的等等,我希望提供一个接口,对我内部提供统一的接口来进行支付的调用.这个场景就非常适合使用适配器模式.
注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构;


MyBatis 日志模块是怎么使用适配器模式?实现如下:

Target:目标角色,期待得到的接口。org.apache.ibatis.logging.Log 接口,对内提供了统一的日志接口;
Adaptee:适配者角色,被适配的接口。其他日志组件组件如 slf4J 、commonsLoging 、Log4J2 等被包含在适配器中。
Adapter:适配器角色,将源接口转换成目标接口。针对每个日志组件都提供了适配器,每 个 适 配 器 都 对 特 定 的 日 志 组 件 进 行 封 装 和 转 换 ; 如 Slf4jLoggerImpl 、JakartaCommonsLoggingImpl 等;

日志模块适配器结构类图:

日志模块儿源码 - 图2
总结:日志模块实现采用适配器模式,日志组件(Target)、适配器以及统一接口(Log 接口)定义清晰明确符合单一职责原则;同时,客户端在使用日志时,面向 Log 接口编程,不需要关心底层日志模块的实现,符合依赖倒转原则;最为重要的是,如果需要加入其他第三方日志框架,只需要扩展新的模块满足新需求,而不需要修改原有代码,这又符合了开闭原则;