Mybatis 数据访问阶段之源码分析
这是mybatis文章,思路在biding模块分析之后
在了解完成biding模块之后,我们来到了mybatis的核心也就是数据访问的模块
一、Executor 中的模板模式
在了解之前我们先熟悉一下模板模式 :
模板模式:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定实现;类结构如下:
AbstractClass 中模板方法 template()定义了功能实现的多个步骤,抽象父类只会对其中几个通用的步骤有实现,而一些可定制化的步骤延迟到子类 ConcreteClass1、ConcreteClass2中实现,子类只能定制某几个特定步骤的实现,而不能改变算法的结构;
应用场景:遇到由一系列步骤构成的过程需要执行,这个过程从高层次上看是相同的但是有些步骤的实现可能不同,这个时候就需要考虑用模板模式了;
二、关于 Executor 组件
在讲第二阶段的时候,提到 Sqlsession 的功能都是基于 Executor 来实现的,Executor 是MyBaits 核心接口之一,定义了数据库操作最基本的方法,在其内部遵循 JDBC 规范完成对数据库的访问;Executor 类继承机构如下图所示:
每个类对应的内容以及相关数据结构
- Executor: MyBaits 核心接口之一,定义了数据库操作最基本的方法;
- CacheingExecutor:使用装饰器模式,对真正提供数据库查询的 Executor 增强了二级缓存的 能 力 ; 二 级 缓 存 初 始 化 位 置 :
DefaultSqlSessionFactory.openSessionFromDataSource(ExecutorType,TransactionIsolationLevel, boolean); - BaseExecutor:抽象类,实现了 executor 接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为: doUpdate,doQuery 等方法;
- BatchExecutor: 批量执行所有更新语句,基于 jdbc 的 batch 操作实现批处理;
- SimpleExecutor: 默认执行器,每次执行都会创建一个 statement,用完后关闭。;
- ReuseExecutor: 可重用执行器,将 statement 存入 map 中,操作 map 中的 statement 而不会重复创建 statement;
代码如下(示例):
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
//执行增删改的三种类型sql语句
int update(MappedStatement ms, Object parameter) throws SQLException;
//执行select类型的sql语句,返回结果为集合对象
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
}
/**
* @author Clinton Begin
*
* 抽象类,实现了executor接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery等方法;
*
*/
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;//事务对象
protected Executor wrapper;//封装的Executor对象
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//延迟加载的队列
protected PerpetualCache localCache;//一级缓存的实现,PerpetualCache
protected PerpetualCache localOutputParameterCache;//一级缓存用于缓存输出的结果
protected Configuration configuration;//全局唯一configuration对象的引用
protected int queryStack;//用于嵌套查询的的层数
private boolean closed;
......
}
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
.....
}
MyBatis 的 执 行 器 组 件 是 使 用 模 板 模 式 的 典 型 应 用 , 其 中 BaseExecutor 、BaseStatementHandler 是模板模式的最佳实践;BaseExecutor 执行器抽象类,实现了 executor接口的大部分方法,主要提供了缓存管理和事务管理的能力
其他子类需要实现的抽象方法为:doUpdate,doQuery 等方法;在 BaseExecutor 中进行一次数据库查询操作的流程如下:
方法入口:
org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();//获取configuration对象
//创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
Statement stmt = prepareStatement(handler, ms.getStatementLog());
//通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取sql语句信息,包括占位符,参数等信息
BoundSql boundSql = ms.getBoundSql(parameter);
//拼装缓存的key值
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//正真的查询方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
执行过程:
如上图所示,doQuery 方法是查询数据的结果的子步骤,doQuery 方法有 SIMPLE、REUSER、BATCH 三种实现,这三种不同的实现是在子类中定义的;
- SimpleExecutor:默认配置,在 doQuery 方法中使用 PrepareStatement 对象访问数据库,每次访问都要创建新的 PrepareStatement 对象;
- ReuseExecutor:在 doQuery 方法中,使用预编译 PrepareStatement 对象访问数据库,访问时,会重用缓存中的 statement 对象;
- BatchExecutor:在 doQuery 方法中,实现批量执行多条 SQL 语句的能力;
2.接下来我们看真正的doquery的方法以及三个重要的对象
通过对 SimpleExecutor doQuery()方法的解读发现,Executor 是个指挥官,它在调度三个小弟工作,这三个小弟分别为:
- StatementHandler:它的作用是使用数据库的 Statement 或 PrepareStatement 执行操作,启承上启下作用;
- ParameterHandler:对预编译的 SQL 语句进行参数设置,SQL 语句中的的占位符“?”都对应 BoundSql.parameterMappings 集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
- ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;
代码如下:
org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取configuration对象
Configuration configuration = ms.getConfiguration();
//创建StatementHandler对象,
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
//读一行一行 读出来通过反射模块填充代码与属性返回结果
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Executor 三小弟内部运作流程如下图所示:
关于 StatementHandler
statementHandler 完成 Mybatis 最核心的工作,也是 Executor 实现的基础;
功能包括:创建statement 对象,为 sql 语句绑定参数,执行增删改查等 SQL 语句、将结果映射集进行转化;
StatementHandler 的类继承关系如下图所示:
- BaseStatementHandler:所有子类的抽象父类,定义了初始化 statement 的操作顺序,由子类实现具体的实例化不同的 statement(模板模式);
- RoutingStatementHandler:Excutor 组件真正实例化的子类,使用静态代理模式,根据上下文决定创建哪个具体实体类;
(1) RoutingStatementHandler 是在 Configuration 的 newStatementHandler 中创建的
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
(2) RoutingStatementHandler 中使用动态代理的方式进行请求转发,在构造方法中,根据上下文(配置)决定创建具体实现类;
private final StatementHandler delegate;//底层封装的真正的StatementHandler对象
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
switch (ms.getStatementType()) {
/*非预编译的*/
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
/*默认的制定 这个属性可以在xml文件里面配置的 <statementType ="" >*/
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
/*告诉Mybatis这个存储过程*/
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
...
三个实现类各自的区别
- SimpleStatmentHandler :使用 statement 对象访问数据库,无须参数化;
- PreparedStatmentHandler :使用预编译 PrepareStatement 对象访问数据库;
- CallableStatmentHandler :调用存储过程;
关于 ResultHandler
ResultSetHandler 将从数据库查询得到的结果按照映射配置文件的映射规则,映射成相应的结果集对象;
在 ResultSetHandler 内部实际是做三个步骤:
找到映射匹配规则 → 反射实例化目标对象 → 根据规则填充属性值,
代码入口 :org.apache.ibatis.executor.statement.PreparedStatementHandler#query
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
debug的位置 : org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
代码流程:
//org.apache.ibatis.executor.statement.SimpleStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();//获取sql语句
statement.execute(sql);//执行sql语句
return resultSetHandler.<E>handleResultSets(statement);//使用resultSetHandler处理查询结果
}
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//用于保存结果集对象 猪呢比容器
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//statment可能返回多个结果集对象,这里先取出第一个结果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取结果集对应resultMap,本质就是获取字段与java属性的映射规则
/*找到映射规则 */
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
//结果集和resultMap不能为空,为空抛出异常
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
//获取当前结果集对应的resultMap 也就是字段对应的数据
ResultMap resultMap = resultMaps.get(resultSetCount);
//根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);//获取下一个结果集
cleanUpAfterHandlingResultSet();//清空nestedResultObjects对象
resultSetCount++;
....
}
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
/*对处理结果进行缓存, 并把处理结果填充到list*/
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {//处理多结果集的嵌套映射
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {//如果resultHandler为空,实例化一个人默认的resultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//对ResultSet进行映射,映射结果暂存在resultHandler中
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//将暂存在resultHandler中的映射结果,填充到multipleResults
multipleResults.add(defaultResultHandler.getResultList());
...
}
}
重点的方法
与 spring 结合原理
SqlSessionFactoryBean 源码分析
SqlSessionFactoryBean 来充当 SqlSessionFactory ,这里我们要搞清楚的就是为什么SqlSessionFactoryBean 为什么能在 Spring IOC 容器中以 SqlSessionFactory 的类型保存并被获取?先来看看 SqlSessionFactoryBean 的定义是怎样的:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
首先,SqlSessionFactoryBean 实现了 InitializingBean 接口,那么容器在初始化完成SqlSessionFactoryBean 之后必然会调用afterPropertiesSet()方法,
其中调用的buildSqlSessionFactory()方法实际是对 MyBatis 初始化加载配置阶段的封装;
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//封装了mybatis的初始化阶段
this.sqlSessionFactory = buildSqlSessionFactory();
}
其次,SqlSessionFactoryBean 实现了 FactoryBean 接口,当在容器中配置 FactoryBean 的实现类时,并不是将该 FactoryBean 注入到容器,而是调用 FactoryBean 的 getObject 方法产生的实例对象注入容器,
/**
* {@inheritDoc}
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
SqlSessionFactoryBean 就是将 sqlSessionFactory 注入容器,IOC 容器中的其他类型能拿到SqlSession 实例了,就可以进行相关的 SQL 执行任务了;
MapperFactoryBean 源码分析
在之前的所有配置中都没出现过 MapperFactoryBean,而实际上真正帮助 Spring 生成Mapper 接口实现类的恰恰就是它,来看看 MapperFactoryBean 的定义是怎样的:
/**通过在容器中的mapperRegistry 返回当前mapper接口的动态代理
封装了Mybatis的第二阶段,每次注入容器的都是SqlSession实例化的mapper接口的实现类
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
MapperFactoryBean 实现了 FactoryBean 接口,getObject 方法实际是封装了 MyBatis 的第二阶段,注入容器的是 SqlSession 实例化的 Mapper 接口的实现类;
MapperScannerConfigurer 源码分析
虽然 MapperFactoryBean 用于帮助 Spring 生成 Mapper 接口,但我们很少直接配置MapperFactoryBean 而是配置 MapperScannerConfigurer 或者是通过注解的方式来配置的,那么它是怎么实现的呢 ?
原因在于又可能工程中的 mapper 接口数量比较多,为每个 mapper 接口都配置MapperFactoryBean,配置文件会变得非常庞大,所以才会使用 MapperScannerConfigurer 为每个 mapper 接口一对一的生成 MapperFactoryBean,那 MapperScannerConfigurer 是怎么做到的呢?先看看其源码:
/**
BeanDefinitionRegistryPostProcessor spring容器后置处理器的一种实现可以对bean的接口调整后再注入容器
*/
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String basePackage;
private boolean addToConfig = true;
...
}
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor 接口,因此可以对 Bean 的结构调整之后再注入容器。那MapperScannerConfigurer 在扫描完这些mapper 接口之后,主要是将 Mapper 接口一个个的转换成 MapperFactoryBean 之后注入容器
的,具体转换代码见:org.mybatis.spring.mapper.ClassPathMapperScanner.processBeanDefinitions(Set)
//处理扫描得到的BeanDefinitionHolder集合,把集合中的每一个mapper接口装换成MapperFactoryBean后,注册到Spring容器
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//遍历集合
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
//增加一个构造方法,接口类型作为构造函数的入参
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//增加SqlsessionTemplate属性
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
//修改自动注入的方式 bytype
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
插件开发
理解插件插件是用来改变或者扩展 mybatis 的原有的功能,mybaits 的插件就是通过继承Interceptor 拦截器实现的;
注意:在没有完全理解插件之前禁止使用插件对 mybaits 进行扩
展,又可能会导致严重的问题;MyBatis 中能使用插件进行拦截的接口和方法如下:
- Executor(update、query、flushStatment、commit、rollback、getTransaction、close、isClose)
- StatementHandler(prepare、paramterize、batch、update 、query)
- ParameterHandler(getParameterObject 、setParameters)
- ResultSetHandler(handleResultSets、handleCursorResultSets 、handleOutputParameters)
插件开发快速入门
- 实现 Interceptor 接口方法
MyBatis 插件的实现必须实现 Interceptor 接口, 该接口有如下三个方法:
- org.apache.ibatis.plugin.Interceptor.intercept(Invocation):插件对业务进行增强的核心方法
- org.apache.ibatis.plugin.Interceptor.plugin(Object):target 是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象;
- org.apache.ibatis.plugin.Interceptor.setProperties(Properties):读取在 plugin 中设置的参数;
- 确定拦截的签名
前面说到插件能拦截的接口方法,@Intercepts 和@Signature 就是用于标识插件拦截的位置。@Intercepts 其值是一个@Signature 数组。@Intercepts 用于表明当前的对象是一个Interceptor,而 @Signature 则表明要拦截的接口、方法以及对应的参数类型。
Intercepts({
//创建拦截的接口,方法以及对于的参数
@Signature(type= StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
private long threshold;
//插件对业务进行增强的核心方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
Object ret = invocation.proceed();
long end=System.currentTimeMillis();
long runTime = end - begin;
if(runTime>=threshold){
Object[] args = invocation.getArgs();
Statement stat = (Statement) args[0];
MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
Statement statement = statementLogger.getPreparedStatement();
System.out.println("sql语句:“"+statement.toString()+"”执行时间为:"+runTime+"毫秒,已经超过阈值!");
}
return ret;
}
//给被拦截的对象 生成器动态规划代理阶段
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//读取xml中配置的参数
@Override
public void setProperties(Properties properties) {
this.threshold = Long.valueOf(properties.getProperty("threshold"));
}
xml进行配置 :
<plugins>
<plugin interceptor=""></plugin>
</plugins>
责任链的模式
责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作; 和装饰器模式不同,每个节点都知道后继者是谁;适合为完成同一个请求需要多个处理类的 场景;- Handler:定义了一个处理请求的标准接口;
- ConcreteHandler:具体的处理者,处理它负责的部分,根据业务可结束处理流程,也可将请求转发给它的后继者;
- client :发送者,发起请求的客户端;
责任链模式优点:
(1)降低耦合度:它将请求的发送者和接收者解耦;
(2)简化了对象:使得对象不需要知道链的结构;
(3)增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态
地新增或者删除责任;
(4)增加新的请求处理类很方便;
插件模块源码分析
插件模块的源码分析主要搞清楚初始化、插件加载以及插件如何调用三个问题;
- 插件初始化插件的初始化实际是在Mybatis 第一个阶段初始化的过程中加载到Configuration 对象中的,具体代码见:XMLConfigBuilder.pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//遍历所有的插件配置
for (XNode child : parent.getChildren()) {
//获取插件的类名
String interceptor = child.getStringAttribute("interceptor");
//获取插件的配置
Properties properties = child.getChildrenAsProperties();
//实例化插件对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置插件属性
interceptorInstance.setProperties(properties);
//将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
configuration.addInterceptor(interceptorInstance);
}
}
}
在 configuration 对象中,使用 interceptorChain 类属性保存所有的插件,interceptorChain类中有个 List 用于顺序保存所有的插件;
2. 插件的加载
为什么插件可以拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler四个接口指定的方法呢?那是因为通过 configuration 对象创建这四大对象时,通过责任链模式按插件的顺序对四大对象进行了增强;
//org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
/*初始化制定默认的初始化器为Simple*/
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果有<cache>节点,通过装饰器,添加二级缓存的能力
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//org.apache.ibatis.session.Configuration#newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
//org.apache.ibatis.session.Configuration#newParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//org.apache.ibatis.session.Configuration#newResultSetHandler
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
- 插件的调用
插件加载是通过 ExamplePlugin.plugin(Object)来增强的,plugin 方法内部一般使用Plugin.wrap(target, this)来对四大对象进行增强,Plugin.wrap 方法代码
public class Plugin implements InvocationHandler {
//封装的真正提供服务的对象
private final Object target;
//自定义的拦截器
private final Interceptor interceptor;
//解析@Intercepts注解得到的signature信息
private final Map<Class<?>, Set<Method>> signatureMap;
// ...
//静态方法,用于帮助Interceptor生成动态代理
public static Object wrap(Object target, Interceptor interceptor) {
//解析Interceptor上@Intercepts注解得到的signature信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();//获取目标对象的类型
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口(拦截器可以拦截4大对象实现的接口)
if (interfaces.length > 0) {
//使用jdk的方式创建动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//获取当前接口可以被拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
return interceptor.intercept(new Invocation(target, method, args));
}
//如果当前方法不需要被拦截,则调用对象自身的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
....
}
很明显,归根究底 MyBatis 的插件是通过动态代理对原有的对象进行增强的。