Mybatis的日誌分析
在使用Mybatis的時候,可以看到控制檯的日誌輸出。有很多的日誌框架,那麼Mybatis如何與這些日誌框架進行整合呢?優先級又如何確定?日誌信息如何優雅的輸出?
1、統一日誌標準
使用接口進行標準統一。
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);
}
2、採用適配器模式進行適配
每個日誌的實現都提供對應的適配器,以log4j爲例:
將第三方的日誌接口轉換爲Mybatis規定的統一日誌接口
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);
}
}
3、按優先級進行加載,加載到之後,後序的不再加載
按順序加載:
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
工廠類中保存對應的日誌的Construct對象
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、動態代理模式進行優雅輸出
爲了將業務與日誌進行解耦,採取動態代理模式,以ConnectionLogger爲例:
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
// 從Object類中繼承過來的方法不進行增強
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
/**
* PreparedStatment: connection.prepareStatement(sql);
* connection.prepareCall(sql);
*/
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
// 日誌輸出
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
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);
}
}