这篇文章将为大家详细讲解有关MyBatis源码分析之日志logging的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
logging 配置加载
我们先从日志的配置加载开始阅读, MyBatis 的各项配置的加载过程都可以从 XMLConfigBuilder 类中找到,我们定位到该类下的日志加载方法 loadCustomLogImpl:
private void loadCustomLogImpl(Properties props) { // 从 MyBatis 的 TypeAliasRegistry 中查找 logImpl 键所对应值的类对象 // 这里 logImpl 对应的 value 值可以从 org.apache.ibatis.session.Configuration 的构造方法中找到 // 注意 Log 类,这是 MyBatis 内部对日志对象的抽象 Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl")); // 将查找到的 Class 对象设置到 Configuration 对象中 configuration.setLogImpl(logImpl); }
很简单的一个方法,每行都有注释,其中 configuration.setLogImpl()
里面调用了 LogFactory.useCustomLogging()
,这出现了新类 LogFactory 类,接下来我们就来聊聊这个类。
LogFactory
useCustomLogging()方法
LogFactory 是框架内部获取 Log 对象的手段,通过它的名字也能看出来。它有如下几类方法:
// 注意这个类型的方法都是同步方法 public synchronized static useXxxLogging(...); public static Log getLog(...); private static tryImplementation(Runnable); private static setImplementation(Class);
刚刚我们看到被调用的方法 useCustomLogging()
方法,是用来设置内部使用的日志框架, MyBatis 自身已经适配了一些常见的日志框架,如 Slf4j 、 Commons Log 、 Log4j 等等。
useCustomLogging()
方法内部调用 setImplementation(Class)
方法,此方法代码如下,功能简单:
private static void setImplementation(Class<? extends Log> implClass) { try { // 获取 Log实现类的构造方法,它只有一个字符串作为参数 Constructor<? extends Log> candidate = implClass.getConstructor(String.class); // 创建一个 Log 对象,打印 debug 日志 Log log = candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) { log.debug("Logging initialized using '" + implClass + "' adapter."); } // ... // 把 candidate 对象设置到 LogFactory 的静态变量 logConstructor,这个静态变量在 getLog() 方法 // 中被用到 logConstructor = candidate; } catch (Throwable t) { throw new LogException("Error setting Log implementation. Cause: " + t, t); } }
Log 接口
刚刚我们接触到了 Log 这个类,它是一个接口,是 MyBatis 内部使用的日志对象的抽象,它是为了兼容市面上各种各样的日志框架,使用了适配器模式,通过 Log 接口来连接 MyBatis 和其他日志框架,通过实现 Log 接口连着 MyBatis 和需要适配的日志框架。
Log 接口代码如下,先试着发现该接口与其他常见的日志接口的区别:
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); }
可有发现?Log 接口缺少了 info 级别的日志输出方法,个人猜测应该是 MyBatis 内部不需要 info 级别的日志输出,毕竟 Log 接口设计之初就是为了内部使用,而框架使用者是不会采用 MyBatis 的日志作为系统的日志。注意一点: 实现了 Log 接口的类必须拥有一个参数只有一个字符串的构造方法 ,MyBatis 就是通过这个构造方法创建日志对象的。
MyBatis 适配了许多常见的日志框架,这里就单单介绍 Log4jImpl 类,它代码非常简单:
public class Log4jImpl implements Log { private static final String FQCN = Log4jImpl.class.getName(); // 这里包装了 Log4j 框架的日志对象,从而实现适配 private final Logger log; public Log4jImpl(String clazz) { log = Logger.getLogger(clazz); } @Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } @Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } @Override public void error(String s, Throwable e) { log.log(FQCN, Level.ERROR, s, e); } @Override public void error(String s) { log.log(FQCN, Level.ERROR, s, null); } @Override public void debug(String s) { log.log(FQCN, Level.DEBUG, s, null); } @Override public void trace(String s) { log.log(FQCN, Level.TRACE, s, null); } @Override public void warn(String s) { log.log(FQCN, Level.WARN, s, null); } }
tryImplementation() 方法
LogFactory 类再介绍一下被静态代码块使用的方法 tryImplementation(Runnable)
。静态代码块代码如下:
static { // 依次执行如下代码,当没有该类会抛 ClassNotFoundException ,然后继续执行 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }
这个方法有点迷惑性,因为它使用 Runnable 接口作为参数,而 useXxxLOgging()
方法又是同步方法,很容易联想到多线程,实际上这里并没有, Runnable 接口不结合 Thread 类使用它就是一个普通的函数接口。除去这些就没什么了,不过是调用了 Runnable 的 run()
方法而已。
getLog() 方法
getLog()
没什么多说的,就是通过反射创建 Log 接口实现类,这里没有使用到缓存,每次调用都是创建一个新的对象。
jdbc 包
这个包与其他包有些不同,它的职能是为各个阶段的流程提供日志打印,该包一共就五个类,它们的关系如下:
除了 BaseJdbcLogger 类其他类都实现了 InvocationHandler 接口,这个接口是 JDK 提供的动态代理接口,所以显而易见可以知道它们就是通过代理在各个阶段打印相应的日志。
以下为 BaseJdbcLogger 类的部分说明:
SET_METHODS:静态字段,记录 PreparedStatement 中 set 开头的的方法名
EXECUTE_METHODS:静态字段,记录 SQL 执行的方法名
columnXxx:实例字段,记录 SQL 参数信息
statementLog:日志对象
queryStack:查询栈数
getParameterValueString():将 SQL 参数转为一个字符串
removeBreakingWhitespace():移除 SQL 中多余的空白字符
prefix():获取前缀 ==>/<==
而其余四个类都是简单的逻辑:判断执行的方法是否为指定方法,然后打印相应的日志。
关于“MyBatis源码分析之日志logging的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。