看源码需要先下载源码,可以去Mybatis的github上的仓库进行下载,Mybatis
这次就先整理一下日志这一块的源码分析,这块相对来说比较简单而且这个模块是Mybatis的基础模块。
之前的文章有谈到过Java的日志实现,大家也可以参考一下:日志实现以及使用
我这里看的是目前最新的版本:3.5.7版本。
设计模式
我们先来谈谈这个模块用到的设计模式。
在市面上有第三方日志实现,但是Mybatis总不可能将每个第三方日志组件实现都做一遍单独的接入,所以日志用到的模式叫适配器模式。
适配器模式(Adapter Pattern)
是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
借用一下网上的UML:
Target:目标角色,即期待得到的接口。
Adaptee:适配者角色,被适配的接口(第三方日志接口)。
Adapter:适配器角色,将被适配接口与目标接口进行桥接。
适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式;在系统中接入第三方组 件的时候经常被使用到;
注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构;
代理模式(Proxy Pattern)
一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
在Mybatis中日志模块用到的代理模式使用将查询的参数,结果以及SQL语句进行打印。
源码解析
在源码中org.apache.ibatis.logging包下
Mybatis并没有实现自己的日志接口,但是MyBatis统一提供了trace、debug、warn、error四个级别;只是定义了一个接口Log,一个适配日志工厂LogFactory;
在LogFactory的静态代码块中,调用的方法顺序为:slf4j -> commons-logging -> log4j2 -> log4j -> JDKLogging -> NoLogging。
所以在没有指定Mybatis的日志类型的时候,会去按照这个顺序自动查找对应的第三方日志组件的实现。
package org.apache.ibatis.logging;/*** @author Clinton Begin*/
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);}
package org.apache.ibatis.logging;import java.lang.reflect.Constructor;public final class LogFactory {public static final String MARKER = "MYBATIS";private static Constructor<? extends Log> logConstructor;static {tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}private LogFactory() {// disable construction}public static Log getLog(Class<?> clazz) {return getLog(clazz.getName());}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);}}public static synchronized void useCustomLogging(Class<? extends Log> clazz) {setImplementation(clazz);}public static synchronized void useSlf4jLogging() {setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);}public static synchronized void useCommonsLogging() {setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);}public static synchronized void useLog4JLogging() {setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);}public static synchronized void useLog4J2Logging() {setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);}public static synchronized void useJdkLogging() {setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);}public static synchronized void useStdOutLogging() {setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);}public static synchronized void useNoLogging() {setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);}private static void tryImplementation(Runnable runnable) {if (logConstructor == null) {try {runnable.run();} catch (Throwable t) {// ignore}}}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);}}}
我们就拿一个示例讲解:Log4j
我们找到Log4j的适配器调用的类,如下
其他类型的日志组件适配都是以这种形式进行编码的。
第三方的日志组件已经适配好,接下来就是将查询过程的各项参数结果集打印。
都知道所有的ORM框架底层都是采用JDBC做查询的,Mybatis也不例外。
这里就必须用到了代理模式,Mybatis的代理模式是通过JDK的动态代理进行实现的之前讲AOP时讲过这个;AOP代理及实现
在Mybatis中查询SQL主要是由Exceutor去查询的,默认使用SimpleExecutor,
我们进去这个getConnection方法
这个newInstance方法里面是通过JDK动态代理进行创建的
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);}
所以在拿到Connection链接之后,需创建一个Statement对象,所以在创建的时候,就会进入到invoke方法
这样依次进行代理,在使用ResultSet的时候也会进入到对应的代理对象当中
这样日志打印的过程就是这样的,
具体的日志类型加载后面会讲,日志模块分析就差不多这样讲完了。