日志模块的第一个需求是一个典型的使用适配器模式的场景, 适配器模式 (Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;类图如下:
适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式,在系统中接入第三方组件的时候经常被使用到
注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构
在 org.apache.ibatis.logging.LogFactory 中,可以看到是使用静态代码块来实现优先级加载,贴出源码中一部分重要的代码:
public final class LogFactory {
/**
* 被选定的第三方日志组件适配器的构造方法
*/
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 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);
}
}
}
复制代码
代理模式定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
这种代理方式需要代理对象和目标对象实现一样的接口
优点:可以在不修改目标对象的前提下扩展目标对象的功能
缺点:冗余,由于代理对象要实现与目标对象一致的接口,会产生过多的代理;不易维护,一旦接口增加方法,目标对象与代理对象都要进行修改
动态代理利用了 JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能
动态代理又被称为 JDK 代理或接口代理。 静态代理与动态代理的区别 主要在:
注意:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理
JDK 中生成代理对象主要涉及两个类
因此在日志模块包 org.apache.ibatis.logging.jdbc 中有 BaseJdbcLogger、ConnectionLogger、PreparedStatementLogger ResultSetLogge 通过动态代理负责在不同的位置打印日志;几个相关类的类图如下:
public abstract class BaseJdbcLogger {
/**
* 保存preparestatment中常用的set方法(占位符赋值)
*/
protected static final Set<String> SET_METHODS;
/**
* 保存preparestatment中常用的执行sql语句的方法
*/
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
/**
* 保存preparestatment中set方法的键值对
*/
private final Map<Object, Object> columnMap = new HashMap<>();
/**
* 保存preparestatment中set方法的key值
*/
private final List<Object> columnNames = new ArrayList<>();
/**
* 保存preparestatment中set方法的value值
*/
private final List<Object> columnValues = new ArrayList<>();
static {
// 利用反射得到set开头并且参数个数大于1的方法,转换成Set集合
SET_METHODS = Arrays.stream(PreparedStatement.class.getDeclaredMethods())
.filter(method -> method.getName().startsWith("set"))
.filter(method -> method.getParameterCount() > 1)
.map(Method::getName)
.collect(Collectors.toSet());
// 执行sql语句的方法
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
}
复制代码
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
/**
* 真正的连接对象
*/
private final Connection connection;
/**
* 对连接的增强
*/
@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()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
// 打印日志
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 增强的PreparedStatement
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);
}
}
}
复制代码
贴出一部分代码如下
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
private final PreparedStatement statement;
/**
* 1,增强PreparedStatement的setxxx方法将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
* 2. 增强PreparedStatement的execute相关方法,当方法执行时,通过动态代理打印参数,返回动态代理能力的resultSet
* 3. 如果是查询,增强PreparedStatement的getResultSet方法,返回动态代理能力的resultSet
* 4. 如果是更新,直接打印影响的行数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
} else if (SET_METHODS.contains(method.getName())) {
// 将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else if ("getUpdateCount".equals(method.getName())) {
// 如果是更新,直接打印影响的行数
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
复制代码
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 执行result.next方法,判断是否还有数据
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {
if ((Boolean) o) {
// 如果还有数据,计数器rows加一
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {
// 如果没有数据了,打印rows,打印查询出来的数据条数
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
复制代码
既然在 Mybatis 中 Executor 才是访问数据库的组件,日志功能是在 Executor 中被嵌入的,具体代码在 org.apache.ibatis.executor.SimpleExecutor.prepareStatement(StatementHandler, Log) 方法中
// 创建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
// 通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
复制代码