Mybatis内部模块之---日志模块
1.设计模式
首先日志模块采用的是适配器模式:因为日志厂商,没有专门针对Mybatis的专门日志模块。所以Mybatis要引入外部的日志模块,于是才用了该设计模式
2.日志模块所在位置
org.apache.ibatis.logging
例:
//jdkLog的适配器,实现的是Log接口(Mybatie自己规定的日志需要有的能力) public class Jdk14LoggingImpl implements Log { //真正提供日志能力的jdk的日志类(这就是引入的外部实现类) private final Logger log; public Jdk14LoggingImpl(String clazz) { log = Logger.getLogger(clazz); } @Override public boolean isDebugEnabled() { return log.isLoggable(Level.FINE); } @Override public boolean isTraceEnabled() { return log.isLoggable(Level.FINER); } @Override public void error(String s, Throwable e) { log.log(Level.SEVERE, s, e); } @Override public void error(String s) { log.log(Level.SEVERE, s); } @Override public void debug(String s) { log.log(Level.FINE, s); } @Override public void trace(String s) { log.log(Level.FINER, s); } @Override public void warn(String s) { log.log(Level.WARNING, s); } }
3.Mybatis又是怎样加载到Mybatis框架里面的
下面我们看看 LogFactory 这个类,这里通过静态代码块,实现了日志框架的加载顺序
public final class LogFactory { /** * Marker to be used by logging implementations that support markers */ public static final String MARKER = "MYBATIS"; //被选定的第三方日志组件适配器的构造方法 private static Constructor<? extends Log> logConstructor; //自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog 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<?> aClass) { return getLog(aClass.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 } } } //通过指定的log类来初始化构造方法 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); } } }
4.日志能力又是怎么样集成到Mybatis运行当中的呢
4.1. 采用的是动态代理实现的。
4.2. 在logging中的jdbc里面是Mybatis增强的地方
4.3. 关于类的组成结构,我们来看一下。
BaseJdbcLogger:所有日志增强的抽象基类,所有日志增强的父类
子类:
ConnectionLogger 负责打印连接信息,和sql语句,并创建PreparedStatementLogger
PreparedStatementLogger 负责打印参数信息,并创建ResultSetLogger
ResultSetLogger 负责打印结果信息
StatementLogger
上面的几个类,父类都是 BaseJdbcLogger ,并且都实现了InvocationHandler
我们就看一个简单的例子就行了:
/** * Copyright ${license.git.copyrightYears} the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.logging.jdbc; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; import org.apache.ibatis.logging.Log; import org.apache.ibatis.reflection.ExceptionUtil; /** * Connection proxy to add logging * * @author Clinton Begin * @author Eduardo Macarron * */ public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler { //真正的连接对象 private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog, int queryStack) { super(statementLog, queryStack); this.connection = conn; } @Override //对连接的增强 public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { //如果是从Obeject继承的方法直接忽略 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } //如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句 //并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数 if ("prepareStatement".equals(method.getName())) { if (isDebugEnabled()) { debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句 } 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);//打印sql语句 } PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);//创建代理对象 stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } else if ("createStatement".equals(method.getName())) { 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); } } /* * Creates a logging version of a connection * * @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); } /* * return the wrapped connection * * @return the connection */ public Connection getConnection() { return connection; } }
5.日志集成到流程中
让我们想一下,现在日志的基本知识我们知道了,但是日志又是怎么样集成到流程中呢,让我们看看
类:SimpleExecutor.java private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } 类:BaseExecutor.java protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } } 类:ConnectionLogger.java 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); }