共计 11801 个字符,预计需要花费 30 分钟才能阅读完成。
筹备
在浏览源码前, 须要先 clone 源码 地址:https://github.com/mybatis/my…
Mybatis 框架应用大量常见的设计模式, 学习 Mybatis 源码咱们次要学习以下几点:
- 学习大佬们的编码思维及标准
- 学习一些传承下来的设计模式
- 实际 java 基础理论
带着问题浏览源码
- 问题 1: 源码中用了哪些设计模式?为什么要用这些设计模式?
- 问题 2.Mybatis 关上调试模式之后,能打印 sql 语句等信息,这是怎么实现的?实现过程中应用了什么设计模式?如果有多个日志实现, 加载程序是什么?
带着这些个问题 咱们去源码中找寻答案
首先咱们先来看看源码包功能模块图
Mybatis 框架提供了一个顶层接口 SqlSession
, 外围解决层, 根底撑持层 这里应用了一个常见的设计模式: 外观模式 / 门面模式
明天咱们次要解说 根底撑持层
中日志模块
外部是如何解决的.
源码 -logging
关上源码我的项目, 能够看到有 20 个目录, 这里咱们先不深刻去看, 把眼光聚焦到 logging 目录
关上目录, 咱们能够看到映入眼帘的就是 Log LogException LogFactory (红色局部)和具体的日志实现 (黄色局部),
关上 Log 接口, 咱们能够看到 Log 接口提供了七个接口办法
实现这个接口的实现办法咱们看看都是谁呢, 能够看到就是咱们刚刚黄色框框局部, 那么他是怎么来解决那么多 Log 实现的呢, 别急, 持续往下看
咱们轻易关上一个 Log 的实现类, 可一看到实际上这并不是真正的实现了所须要的 Log 只是在对每个用到的 Log 实现做了参数适配而已
(其余的类也是相似, 这里应用了适配器模式)
那么有那么多日志实现, 他的加载程序是什么呢. 到这里咱们会想到刚刚看到的 LogFactory 类, 大叫都晓得工厂办法就是用来获取实例的既然要获取实例那么他的解决也必定在工厂外部处理完毕了(这里应用了简略工厂模式) 那么咱们关上 LogFactory 类来看看他的加载程序到底是怎么实现的:
import java.lang.reflect.Constructor;
public final class LogFactory {
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor; // 定义 logConstructor 他继承于 Constructor
static { // 加载程序在这里. 初始化时必然依照这个程序执行, 能够发现上面这一排排就是咱们刚刚看到的各个适配器
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {// 在这里私有化结构器 就不能通过 new 来创立实例了}
public static Log getLog(Class<?> clazz) {// 第一步. 如果咱们须要一个 Log 那么咱们就调用这个办法, 传入须要的类即可
return getLog(clazz.getName());
}
public static Log getLog(String logger) {// 第二步. 依据类的名字获取 Log 实现. 如果获取不到会报错, 这个 logConstructor 就是咱们下面的, 他外部在初始化时曾经通过 static 的动态代码块进行了解决, 所以会解决为对应的日志实现,
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
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) {// 将传入的代码块执行, 留神这里是.run 不是.start
if (logConstructor == null) {// 如果还没有实现才执行
try {runnable.run();
} catch (Throwable t) {// 如果加载这个实现呈现了异样, 如:ClassNotFoundException 这个时候间接疏忽, 期待下一个实现调用
// 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);
}
}
}
回顾一下问题: 源码中用了哪些设计模式?
到目前咱们看到 Mybatis 应用了 1. 外观模式 / 门面模式 2. 适配器模式 3 简略工厂模式
到这里咱们曾经拿到了 Log 的实例不论他是通过 log4j 还是 slf4j 的实现, 那他是怎么实现对日志进行加强的呢 怎么打印进去的 SQL 语句和参数以及后果计数的?
如果想要输入这些信息, 那么首先必定要获取到这些信息.
上面咱们就去看这块, 看看他是怎么获取和存贮这些信息的.
public abstract class BaseJdbcLogger {
protected static final Set<String> SET_METHODS;//set 办法容器
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();// 执行办法容器
private final Map<Object, Object> columnMap = new HashMap<>();// 列的键值对
private final List<Object> columnNames = new ArrayList<>();// 所有 key 的列表
private final List<Object> columnValues = new ArrayList<>();// 所有 value 的列表
protected final Log statementLog; // Log 实现
protected final int queryStack;
/*
* Default constructor
*/
public BaseJdbcLogger(Log log, int queryStack) {// 默认的结构
this.statementLog = log;
if (queryStack == 0) {this.queryStack = 1;} else {this.queryStack = queryStack;}
}
static {// 初始化 就加载两个容器 这两个容器用来装所有的预编译 set 参数的办法和所有执行办法
SET_METHODS = Arrays.stream(PreparedStatement.class.getDeclaredMethods())// 这里应用 JAVA8 新个性将 PreparedStatement 类的所有 set 结尾的办法映射成一个 Set
.filter(method -> method.getName().startsWith("set"))
.filter(method -> method.getParameterCount() > 1)
.map(Method::getName)
.collect(Collectors.toSet());
EXECUTE_METHODS.add("execute");// 将 4 个执行办法增加到 EXECUTE_METHODS 这个 Set
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
protected void setColumn(Object key, Object value) {columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
protected Object getColumn(Object key) {return columnMap.get(key);
}
protected String getParameterValueString() {List<Object> typeList = new ArrayList<>(columnValues.size());
for (Object value : columnValues) {if (value == null) {typeList.add("null");
} else {typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
}
}
final String parameters = typeList.toString();
return parameters.substring(1, parameters.length() - 1);
}
protected String objectValueString(Object value) {if (value instanceof Array) {
try {return ArrayUtil.toString(((Array) value).getArray());
} catch (SQLException e) {return value.toString();
}
}
return value.toString();}
protected String getColumnString() {return columnNames.toString();
}
protected void clearColumnInfo() {columnMap.clear();
columnNames.clear();
columnValues.clear();}
protected String removeExtraWhitespace(String original) {return SqlSourceBuilder.removeExtraWhitespaces(original);
}
protected boolean isDebugEnabled() {return statementLog.isDebugEnabled();
}
protected boolean isTraceEnabled() {return statementLog.isTraceEnabled();
}
protected void debug(String text, boolean input) {if (statementLog.isDebugEnabled()) {statementLog.debug(prefix(input) + text);
}
}
protected void trace(String text, boolean input) {if (statementLog.isTraceEnabled()) {statementLog.trace(prefix(input) + text);
}
}
private String prefix(boolean isInput) {char[] buffer = new char[queryStack * 2 + 2];
Arrays.fill(buffer, '=');
buffer[queryStack * 2 + 1] = ' ';
if (isInput) {buffer[queryStack * 2] = '>';
} else {buffer[0] = '<';
}
return new String(buffer);
}
}
到这咱们晓得了他是怎么存贮信息的获取的形式就是在调用时通过存贮的办法和 set 办法列表来查问, 那么他是什么时候被调用呢? 认真看看这个图咱们发现了端倪 在 BaseJdbcLogger 上面还有
- ConnectionLogger
- PreparedStatementLogger
- ResultSetLogger
- StatementLogger
这些不就是咱们很眼生的流程吗 获取数据库连贯 –> 预编译 SQL–> 执行 SQL–> 获取后果
咱们轻易关上一个 看看他们是怎么在本人的一亩三分地干活的
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {// 继承于 BaseJdbcLogger 实现 InvocationHandler 看到 InvocationHandler 就会想到 -> 代理模式
private final Connection 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 {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {// 判断办法名是不是这两中的一个 短语或 有一个就为 true
if (isDebugEnabled()) {// 原来开启 debug 就会打出日志是在这里呀
debug("Preparing:" + removeExtraWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);// 通过 connection 创立了 PreparedStatement
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); // 把 stmt 传过来给 PreparedStatementLogger 的 newInstance 创立了一个 stmt 的代理返回
return stmt;// 留神这里返回的不是实在 stmt 而是代理过后的 stmt
} else if ("createStatement".equals(method.getName())) {// 如果是创立 Statement
Statement stmt = (Statement) method.invoke(connection, params); // 拿到连贯去创立一个 Statement
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);// 把 Statement 传进 StatementLogger 获取 Statement 的代理对象来返回
return stmt;// 留神这里返回的不是实在 stmt 而是代理过后的 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
* @param statementLog
* the statement log
* @param queryStack
* the query stack
* @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;}
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
private final PreparedStatement statement; // 还是一样 这里是被代理的理论对象
private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {super(statementLog, queryStack);
this.statement = stmt;
}
@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())) {// 还记得那几个容器吗 用来存储各种办法的 当初他来了
// 开启 DEBUG 我就把参数打印进去
if (isDebugEnabled()) {debug("Parameters:" + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {ResultSet rs = (ResultSet) method.invoke(statement, params);// 获取到一个 resultset
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);// 把 resultset 也代理了来返回
} else {return method.invoke(statement, params);
}
} else if (SET_METHODS.contains(method.getName())) {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);
}
}
/**
* Creates a logging version of a PreparedStatement.
*
* @param stmt - the statement
* @param statementLog - the statement log
* @param queryStack - the query stack
* @return - the proxy
*/
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
/**
* Return the wrapped prepared statement.
*
* @return the PreparedStatement
*/
public PreparedStatement getPreparedStatement() {return statement;}
}
最初是 ResultSetLogger
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {//ResultSetLogger 的加强办法
try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {if ((Boolean) o) {
rows++;// 只有还有下一条 next, 就记数加 1 rows++;
if (isTraceEnabled()) {ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {debug("Total:" + rows, false);// 开启 debug 状况下 打印出总数计数
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);
}
}
到这里咱们根本曾经摸完了这块逻辑 当初复盘一下问题
- 问题 1. 源码中用了哪些设计模式?为什么要用这些设计模式?
门面 / 外观模式 适配器模式 代理模式 简略工厂模式. 为什么要应用这些模式 太长了我就不写了, 大家自行总结 - 问题 2.Mybatis 关上调试模式之后,能打印 sql 语句等信息,这是怎么实现的?实现过程中应用了什么设计模式?如果有多个日志实现, 加载程序是什么?
在调试模式能打印日志, 是 mybatis 框架在理论执行时对对象进行了代理, 进行了加强. 他应用了代理设计模式. 他实现日志的程序是:
static {tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
加群让咱们一起学习吧
关注公众号:java 宝典