一、配置
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:Executor、ParameterHandler 、ResultSetHandler 、StatementHandler。
这几个方法分别是执行器、参数处理器、返回结果集处理器、Statement处理器。 通常,我们在xml文件中通过plugins属性来定义它们。
<property name="plugins"> <array> <bean class="com.viewscenes.netsupervisor.interceptor.ExecutorIntercepor"></bean> <bean class="com.viewscenes.netsupervisor.interceptor.ResultSetInterceptor"></bean> <bean class="com.viewscenes.netsupervisor.interceptor.PageInterceptor"></bean> </array> </property>
那么,在构建SqlSessionFactory的时候,Mybatis就会检查是否配置了插件。有的话,也比较简单,就是加入到interceptors集合中。
if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); } } public class InterceptorChain { public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } }
然后新建一个类,实现Interceptor接口,同时通过@Intercepts声明接口的名称、方法名称、参数列表即可实现插件。比如下面的例子中,声明了拦截的接口为Executor,方法名为query,参数为args。
@Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})}) public class ExecutorIntercepor implements Interceptor{ public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } public Object plugin(Object target) { if (target instanceof Executor){ return Plugin.wrap(target, this); } return target; } public void setProperties(Properties properties) {} }
二、创建代理
所谓插件,其实就是创建代理的过程。就上面的例子而言,就是创建了Executor接口的代理类,调用程序处理器为Plugin类。在执行Executor.query()方法的时候,实际调用的是Plugin.invoke(Invocation invocation)。 我们说拦截的方法包含以上四种,那就一个一个来看,它们到底是怎么实现拦截的。
1、Executor
SQL的执行过程中,Mybatis会先创建一个sqlSession对象。在创建sqlSession的时候,就会创建一个执行器。Executor.query方法是一开始就调用的方法,此时SQL还都是一个一个的sqlNode节点未解析的状态。
public class Configuration { public Executor newExecutor(Transaction transaction, ExecutorType 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); } //默认为true,把SimpleExecutor包装成CachingExecutor对象 if (cacheEnabled) { executor = new CachingExecutor(executor); } //产生代理的地方,如果配置了插件,最后返回的executor就是个代理对象 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
可以看到,Mybatis会根据类型创建一个执行器。然后调用interceptorChain.pluginAll(executor)来确定是否需要产生代理。
public class InterceptorChain { public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } }
interceptor我们知道,在构建SqlSessionFactory的时候,就把配置的拦截器加入到其中了。那么在这里,它是循环所有自定义的拦截器,调用其plugin方法。这也就解释了我们为什么在plugin方法中要进行类型判断,否则每次返回的对象就是最后一个拦截器的代理对象。
public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; }
如果,类型匹配上的话就调用Plugin的静态方法wrap,实际产生代理。也就是说,@Signature注解上配置的是哪个接口,这里就产生哪个接口的代理。
public class Plugin implements InvocationHandler { public static Object wrap(Object target, Interceptor interceptor) { //获取@Signature注解的接口,方法和参数 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); //获取目标类实现的接口 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { //调用JDK的方法,返回代理对象 //调用程序处理器Plugin就是本类,它已经实现了InvocationHandler接口 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } }
那么,在调用到Executor.query()方法的时候,实际执行的是Plugin类的invoke()。在invoke方法里面就会判断当前调用的方法是否在自定义拦截器注解方法的范围内,然后调用其intercept方法。
public class Plugin implements InvocationHandler { 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)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
2、StatementHandler
在执行到SimpleExecutor.doQuery方法的时候,要创建StatementStatementHandler对象,这里也可以配置拦截器。这时候,SQL语句已经解析完毕,开始要调用StatementHandler.prepare方法进行预编译SQL。思考一下,我们可以拦截它做什么呢? 当然了,它们的创建过程都是一样的,都是调用interceptorChain.pluginAll(executor)。
public class Configuration { 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; } }
3、ResultSetHandler
在上一步实例化PreparedStatementHandler对象的时候,会调用其父类的构造方法,在这里创建了两个对象:ResultSetHandler、ParameterHandler。ResultSetHandler是返回值集合处理类,它的handleResultSets方法返回的就是转换完毕的Java数据集合。 可以思考下,在这里拦截的话,可以干些什么呢?
public abstract class BaseStatementHandler{ protected final ResultSetHandler resultSetHandler; protected final ParameterHandler parameterHandler; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); } }
它创建的是返回值集合处理器是DefaultResultSetHandler,同时一样的也会调用interceptorChain.pluginAll验证是否要产生代理。
public class Configuration { 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; } }
三、总结
本文阐述了三种常用拦截器的配置方式和解析过程,分别是Executor、StatementHandler、ResultSetHandler,它们执行的时机分别如下:
- Executor
生成sqlSession对象之后,开始调用Executor进行实际方法的调用。
- StatementHandler
解析完SQL,创建PreparedStatement对象预编译并设置参数。
- ResultSetHandler
从数据库拿到数据,并转换为Java数据集合之后返回。