MyBatis 日志模块源码分析

本贴最后更新于 2307 天前,其中的信息可能已经水流花落

相关设计模式

适配器模式

@startuml namespace mybatis { interface Log class LogFactory LogFactory .down.> Log Jdk14LoggingImpl .up.|> Log Slf4jImpl .up.|> Log StdOutImpl .up.|> Log NoLoggingImpl .up.|> Log JakartaCommonsLoggingImpl .up.|> Log Log4jImpl .up.|> Log Log4j2Impl .up.|> Log } namespace slf4j_api { interface Logger } namespace log4j_api_2 { interface Logger } namespace java.lang { class System } namespace java.util.logging { class Logger } namespace jcl_over_slf4j { interface Log } namespace log4j { interface Logger } mybatis.Slf4jImpl o-down- slf4j_api.Logger mybatis.StdOutImpl o-down- java.lang.System mybatis.Jdk14LoggingImpl o-down- java.util.logging.Logger mybatis.JakartaCommonsLoggingImpl o-down- jcl_over_slf4j.Log mybatis.Log4jImpl o-down- log4j.Logger mybatis.Log4j2Impl o-down- log4j_api_2.Logger @enduml

目标接口为 Log 接口,需要适配的类为实际提供日志支持的类,比如 log4j_api_2 .Logger,
适配器实现了 Log 接口,并由 mybatis.Log4j2Impl 组成,两者之间是聚合关系。

门面模式

@startuml namespace slf4j_api { interface Logger interface LocationAwareLogger LocationAwareLogger -up-|> Logger } namespace log4j_slf4j_impl { class Log4jLogger } log4j_slf4j_impl.Log4jLogger .up.|> slf4j_api.LocationAwareLogger namespace Client { } Client -down-> slf4j_api.Logger @enduml

slf4j_api.Logger 充当门面,Client 调用 slf4j_api.Logger 获得服务。
具体的服务支持由 og4j_slf4j_impl.Log4jLogger 或jul_over_slf4j.Log4jLogger等 等提供支持

代理模式

@startuml interface Connection ConnectionImpl .up.|> Connection note left of ConnectionImpl:不需要创建此类 ConnectionProxy .up.|> Connection ConnectionProxy o-up- "1" ConnectionImpl @enduml

ConnectionProxy 作为 Connection 接口实现类的代理类。

日志选择

org.apache.ibatis.logging.LogFactory 静态初始化块中

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 static void tryImplementation(Runnable runnable) { if (logConstructor == null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }

  一共调用了 6 次 tryImplementation,每次调用传入不同的门面,按顺序找到第 1 个实现了门面逻辑的子系统,写入 logConstructor,不再往下找。

  这里虽然创建了 Runnable,但并没有启动线程,因为 tryImplementation 调用的是 run 而不是 start

以 log4j2 为例

只贴出流程中需要的代码,其他的用 ... 表示省略

public Slf4jImpl(String clazz) { Logger logger = LoggerFactory.getLogger(clazz); ... } public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); ... }

iLoggerFactory 打上条件断点,条件是 name.contains("LogFactory")

在 expression 中输出 class org.apache.logging.slf4j.Log4jLoggerFactory,可看到
name 为 org.apache.logging.slf4j.Log4jLoggerFactory,在 log4j-slf4j-impl 中。

选择了 slf4j 门面,log4j-slf4j-impl 为子系统

日志打印逻辑

@startuml abstract class BaseJdbcLogger interface InvocationHandler StatementLogger -up-|> BaseJdbcLogger ConnectionLogger -up-|> BaseJdbcLogger ResultSetLogger -up-|> BaseJdbcLogger PreparedStatementLogger -up-|> BaseJdbcLogger StatementLogger .down.|> InvocationHandler ConnectionLogger .down.|> InvocationHandler ResultSetLogger .down.|> InvocationHandler PreparedStatementLogger .down.|> InvocationHandler @enduml

在代理类的 invoke 方法中打印 log

打印完整日志

import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.type.TypeHandlerRegistry; import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.regex.Matcher; import org.springframework.stereotype.Component; @Component @Log4j2 @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MyBatisLogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { try { MappedStatement mappedStatement = (MappedStatement) invocation .getArgs()[0]; Object parameter = null; if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } String sqlId = mappedStatement.getId(); BoundSql boundSql = mappedStatement .getBoundSql(parameter); Configuration configuration = mappedStatement.getConfiguration(); String sql = getSql(configuration, boundSql, sqlId); log.info(sql); } catch (Exception e) { log.error(e.getMessage(), e); } return invocation.proceed(); } private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) { String sql = showSql(configuration, boundSql); return sqlId.concat(":").concat(sql); } /** * 如果参数是String,则添加单引号  * 如果是日期,则转换为时间格式器并加单引号  * 对参数是null和不是null的情况作了处理  */ private static String getParameterValue(Object obj) { String value = null; if (obj instanceof String) { value = "'" + obj.toString() + "'"; } else if (obj instanceof Date) { DateFormat formatter = DateFormat .getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA); value = "'" + formatter.format(new Date()) + "'"; } else { if (obj != null) { value = obj.toString(); } else { value = ""; } } return value; } // 进行?的替换 private static String showSql(Configuration configuration, BoundSql boundSql) { Object parameterObject = boundSql.getParameterObject(); List parameterMappings = boundSql .getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\\s]+", " "); if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration .getTypeHandlerRegistry(); if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject))); } else { MetaObject metaObject = configuration.newMetaObject( parameterObject); for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); if (metaObject.hasGetter(propertyName)) { Object obj = metaObject.getValue(propertyName); sql = sql .replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj))); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); sql = sql .replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj))); } else { sql = sql.replaceFirst("\\?", "missing"); } } } } return sql; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }

关于拦截器、JDK 动态代理、CGLIB,有时间再开一篇。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 165 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    173 引用 • 414 回帖 • 367 关注
1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...