十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
今天就跟大家聊聊有关MyBatis中有哪些日志模块,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
从策划到设计制作,每一步都追求做到细腻,制作可持续发展的企业网站。为客户提供成都网站建设、做网站、网站策划、网页设计、申请域名、虚拟主机、网络营销、VI设计、 网站改版、漏洞修补等服务。为客户提供更好的一站式互联网解决方案,以客户的口碑塑造优易品牌,携手广大客户,共同发展进步。
org.apache.ibatis.logging
mybatis 没有自己的日志模块 他是使用的第三方日志(也有jdk自带日志) 日志的加载顺序 slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
在静态代码块儿中声明加载顺序
public final class LogFactory { //被选定的第三方日志组件适配器的构造方法 private static Constructor logConstructor; /** * Marker to be used by logging implementations that support markers */ public static final String MARKER = "MYBATIS"; //被选定的第三方日志组件适配器的构造方法 private static Constructor 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); }
这里用到了java8的语法糖 tryImplementation方法需要一个runnable类型的参数,runnable被打上了@FunctionalInterface注解,所以可以使用lambda语法简写 logConstructor 保存着日志对象的构造方法,之后通过反射创建对象,只有logConstructor == null的时候采取调用running的run方法,那么run的内容是什么呢?
tryImplementation(LogFactory::useSlf4jLogging); private static void tryImplementation(Runnable runnable) { if (logConstructor == null) {//当构造方法不为空才执行方法 try { runnable.run(); } catch (Throwable t) { // ignore } } }
LogFactory::useSlf4jLogging setImplementation方法尝试通过传递过来的Class创建类(通过反射拿到构造方法),如果成功,讲构造方法保存到logConstructor中备用
public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } //通过指定的log类来初始化构造方法 private static void setImplementation(Class implClass) { try { Constructor 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); } }
这用到的设计模式 适配器模式 因为不同的日志组件对日志的info error等定义不同,mybatis把他们统一成如下级别
package org.apache.ibatis.logging; 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);
我们看优先级最高的日志slf4J的实现类
package org.apache.ibatis.logging.log4j; import org.apache.ibatis.logging.Log; import org.apache.log4j.Level; import org.apache.log4j.Logger; /** * @author Eduardo Macarron */ public class Log4jImpl implements Log { private static final String FQCN = Log4jImpl.class.getName(); 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); } }
MyBatis通过动态代理的方式增强了Connection,PreparedStatement,ResultSet.其对应的实现是xxxLogger类
先看ConnectionLogger类部分代码,特别注意27行,同样通过动态代理拿到了具有打印功能的PreparedStatement--PreparedStatementLogger
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; ...... // 创建这个ConnectionLogger的代码 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); }
BaseJdbcLogger 是Connection,PreparedStatement,ResultSet 的基类,他是个抽象类 成员变量展示
//所有日志增强的抽象基类 public abstract class BaseJdbcLogger { //保存preparestatment中常用的set方法(占位符赋值) protected static final SetSET_METHODS = new HashSet<>(); //保存preparestatment中常用的执行sql语句的方法 protected static final Set EXECUTE_METHODS = new HashSet<>(); //保存preparestatment中set方法的键值对 private final Map