MyBatis 提供了插件的机制,让开发人员可以对 SQL 执行的几个关键过程进行自定义的特殊处理,实现原理依然是 JDK 动态代理。
还是以一个简单的例子开始(MyBatis 3.4.0):
/**
*
* @author xi
* @date 2018/10/01 14:12
*/
public class Demo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
System.out.println(role);
}
}
插件的代码先放上,具体作用后面用到再回头看:
/**
* @author xi
* @date 2019/9/27 21:11
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MyPlugin implements Interceptor {
private Properties props;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before...");
Object result = invocation.proceed();
System.out.println("after...");
return result;
}
@Override
public Object plugin(Object target) {
System.out.println("create proxy");
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
System.out.println(properties.get("dbType"));
props = properties;
}
}
插件的配置:
<plugins>
<plugin interceptor="com.learn.plugin.MyPlugin">
<property name="dbType" value="mysql"/>
</plugin>
</plugins>
1. 插件解析
从 Demo 中的org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
方法,一步步跟进到org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
方法:
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
很明显能看到 plugins 配置的解析,看一下解析方法org.apache.ibatis.builder.xml.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.addInterceptor(interceptorInstance);
}
}
}
很明显是通过反射的方式创建插件的实例对象,然后调用Configuration
对象的org.apache.ibatis.session.Configuration#addInterceptor
方法:
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
InterceptorChain
对象实际上里面就是个ArrayList
对象,到这插件就初始化完毕了。
2. 插件工作流程
插件主要对 4 类关键对象起作用:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
这几类对象大致功能从名称就能看出来,主要说下Executor
。SqlSession
对象是通过Executor
对象来对数据库进行操作的。参考《深入理解mybatis原理》 MyBatis的架构设计以及实例分析 - 我的程序人生(亦山札记) - CSDN博客 中 MyBatis 层次结构图:
2.1 Executor
本次分析一个简单的 SQL 执行过程,找到插件是如何起作用的。回到 Demo 代码中获取SqlSession
对象的这行代码:SqlSession sqlSession = sqlSessionFactory.openSession();
,找到org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
方法。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);// 创建 executor
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在这个方法中创建了Executor
对象,至于这个方法中做的其他的事情不是本次关注的重点,不必过分关注。进入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;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);// 通过拦截器生成代理对象
return executor;
}
先创建了特定类型的Executor
对象,暂时以SimpleExecutor
为主。这里需要注意的是在Executor
对象返回之前,调用了拦截器链的org.apache.ibatis.plugin.InterceptorChain#pluginAll
方法,是时候放出它的代码:
/**
* @author Clinton Begin
*/
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);// 返回结果作为下一次调用的参数
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
这个方法很简单,就是遍历拦截器链。需要注意的是每次拦截器处理后返回的对象(代理对象),都会交给下一个拦截器处理,所以这是一个层层嵌套的代理对象,有好几个中间商赚差价。org.apache.ibatis.plugin.Interceptor#plugin
是一个接口方法,放出拦截器接口的代码:
/**
* @author Clinton Begin
*/
public interface Interceptor {
//
Object intercept(Invocation invocation) throws Throwable;
// 创建 target 对象的代理
Object plugin(Object target);
// 解析配置文件时,用来设置属性
void setProperties(Properties properties);
}
前面给出的自定义的插件实现的就是这个接口,接口中各个方法的具体作用见注释,先继续往下走。
在MyPlugin
的plugin(Object target)
方法主要关注:Plugin.wrap(target, this);
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);// 解析 MyPlugin 类上的注解
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {// 实现了接口则创建代理对象
return Proxy.newProxyInstance(// 创建代理对象
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;// 没有实现接口则直接返回 target
}
注释中已经说明了该方法的作用,这里主要关注一下Plugin
类。它实现了InvocationHandler
接口,所以需要看一下它的org.apache.ibatis.plugin.Plugin#invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {// signatureMap 是前面 wrap 时解析出来的
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}// 调用拦截器(开发者自己实现的插件)
return method.invoke(target, args);// 如果没有拦截该方法,则直接调用
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
具体操作就是 JDK 动态代理那一套。这里有个Invocation
对象,其实就是封装了一层对原始对象的方法调用。回头看自定以的插件的intercept
方法:
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before...");
Object result = invocation.proceed();// 调用原始对象的方法
System.out.println("after...");
return result;
}
插件在原始方法调用前后,做一些特殊的处理。注意Invocation
对象里面保存了三个成员变量,分别是:原始对象、被拦截的方法、被拦截方法的参数。Invocation
类对外提供了获取和修改这 3 个成员变量的方法。
至此,一个完整的插件生效的流程就结束了。插件对其他几个对象的处理也是类似的流程。具体流程就不一一分析了,主要找到生成拦截器链的地方。
2.2 StatementHandler、ParameterHandler、ResultHandler
拦截器链都是在创建指定对象之后,就对原始对象创建代理对象,这些方法都在Configuration
类里面。
先看StatementHandler
对象的创建方法:org.apache.ibatis.session.Configuration#newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
很明显可以看到拦截器链的创建,具体内容和前面一样。在 IDEA 中可以用Command + B
或者Ctrl + B
查看这个创建方法被使用的地方,如下图:
可以看到都是在Executor
对象中调用的,这里以SimpleExecutor
中的org.apache.ibatis.executor.SimpleExecutor#doQuery
为例子:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
创建StatementHandler
对象就是在Executor
准备执行 SQL 的时候。
回头我们再看看StatementHandler
的创建,找到RoutingStatementHandler
的构建方法:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
从名称可以知道是个路由,根据类型创建指定的StatementHandler
对象。3 个子类的构造方法都会调用父类的构造方法:org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
这个方法的前面几行暂时不看,只用看最后两行。回到Configuration
类里面看看两个创建对应 Handler 的方法:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
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;
}
跟前面创建拦截器链的代码都类似,就不一一细说。
最后
附上拦截器相关的类图: 右键查看高清大图