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
对象typeAliasesPackage
typeAliasesSuperType
typeHandlersPackage
- 数组
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()
创建StatementHandler
java// SimpleExecutor$doQuery() StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
-
在
Configuration$newStatementHandler()
会实例化RoutingStatementHandler
java// 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:
目前还在学习成长中!存在任何问题,欢迎在评论区批评指正。