1.介绍
MyBatis 会话的运行需要 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这四大对象的配合。MyBatis 插件相当于拦截器,可以编写针对以上四种对象的插件,在对象调度的时候,插入一些我们自己的代码。
ParameterHandler:对 SQL 参数进行处理ResultSetHandler:对结果对象进行处理StatementHandler:对 SQL 语句进行处理Executor:执行器
看到这里,是不是觉得有 AOP 那味了。
实际上,插件就是基于面向切面编程的思想,通过动态代理来实现。
- 为需要拦截的接口生成代理对象以实现接口方法拦截功能
- 每当执行这四大对象的方法时,先判断执行的方法是否需要代理的方法;如果是,则执行插件类的增强方法,进行方法的拦截处理
2.源码解析
1.SpringBoot 通过自动配置,加载 MybatisAutoConfiguration,在该类实例化时,会将 Interceptor 注入到属性 interceptors。

2.在 MybatisAutoConfiguration 中通过 @Bean 定义了 SqlSessionFactory;调用该方法实例化 SqlSessionFactory
-
实例化
SqlSessionFactoryBean,并将各类配置信息注入该对象configLocation:定义了配置文件的位置Configuration对象:就像是 MyBatis 的总管,MyBatis 的所有配置信息都存放在这里,此外,它还提供了设置这些配置信息的方法configurationProperties- 数组
interceptors:插件Interceptor databaseIdProvider对象typeAliasesPackagetypeAliasesSuperTypetypeHandlersPackage- 数组
typeHandlers:类型处理器集合 mapperLocations:**Mapper.xml映射文件位置
-
调用
getObject()方法进行实例化-
在该方法中,判断
sqlSessionFactory是否为空,是则调用afterPropertiesSet()方法 -
调用
afterPropertiesSet()方法时,会调用buildSqlSessionFactory()方法- 在
buildSqlSessionFactory()方法中,进行一系列实例化过程 - 重要的是在这里将
Interceptor插件注入Configuration配置类中 - 同时设置环境信息
Environment;对**Mapper.xml文件进行解析
- 在
-
返回
SqlSessionFactory对象
-
2.通过 SqlSessionFactory 创建 SqlSession
java
// 创建 SqlSession;核心代码:DefaultSqlSessionFactory$openSessionFromDataSource
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
java
// 创建 Executor;核心代码:Configuration$newExecutor
// 这个是实现针对 Executor 的插件的核心代码,稍后详解
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
3.通过执行器 Executor 执行相应的方法(查询调用 query(),增删改调用 update())
-
通过
Configuration$newStatementHandler()创建StatementHandlerjava// SimpleExecutor$doQuery() StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); -
在
Configuration$newStatementHandler()会实例化RoutingStatementHandlerjava// SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 都继承了 BaseStatementHandler;调用 BaseStatementHandler 的构造函数实例化 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch(ms.getStatementType()) { case STATEMENT: this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } // 在 BaseStatementHandler 中实例化了 ParameterHandler 和 ResultSetHandler this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
- 通过
ParameterHandler对参数进行处理 - 通过
StatementHandler执行 SQL 语句 - 通过
ResultSetHandlder对执行结果进行处理
执行流程:
DefaultSqlSession$selectOne() ---> DefaultSqlSession$selectList() ---> BaseExecutor$query() ---> BaseExecutor$queryFromDatabase() ---> SimpleExecutor$doQuery()
实现插件重点源码:
以上的四大对象都是通过 Configuration 对象的四个方法来创建的,源码如下所示:

从源码中可以观察到,每个创建的对象都需要被 this.interceptorChain.pluginAll() 进行处理。
interceptorChain 实际上是 InterceptorChain 对象,在该对象中有一个 List 集合,里面保存了定义的插件(Interceptor)。pluginAll() 方法中,会遍历每一个插件,并调用其 plugin(target) 方法。

Interceptor 实际上调用了 Plugin 的 wrap() 方法。

在 Plugin.wrap() 方法判断是否需要生成代理。
java
public static Object wrap(Object target, Interceptor interceptor) {
// 解析在 Interceptor 上修饰的 @Intercepts 的注解信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获取目标对象的 Class
Class<?> type = target.getClass();
// 匹配;目标对象所实现的接口中在 signatureMap 中的将其返回
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 判断;如果数组中存在元素,则说明存在接口被 Interceptor 拦截,然后通过 JDK 动态代理实现代理类
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
当这个代理类执行方法时,会被 Plugin(实际是 InvocationHandler) 中的 invoke() 方法拦截。当执行的方法是需要被拦截的方法时,执行对应的 Interceptor 的拦截方法。
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 根据目标对象的 Class,获取需要被拦截的方法
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
// 当需要被拦截的方法不为空,并且目前调用的方法是需要被拦截的方法时,执行 Interceptor 的 intercept() 方法;在这里就会执行我们自定义的插件的拦截方法
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
参考资料
- 面试官:讲一下Mybatis在SpringBoot中是如何被加载执行的?
- Spring Boot : Mybatis 执行原理分析-CSDN博客
- SqlSessionFactoryBean详解-CSDN博客
- MyBatis - Configuration_mybatisconfiguration-CSDN博客
- Mybatis 之 插件原理 - Vermeer - 博客园 (cnblogs.com)
Tips:
目前还在学习成长中!存在任何问题,欢迎在评论区批评指正。