MyBatis 容許你在已映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:Executor、ParameterHandler 、ResultSetHandler 、StatementHandler
。sql
這幾個方法咱們在開篇第一章節就已經介紹了,分別是執行器、參數處理器、返回結果集處理器、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集合中。bash
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。app
@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)
。 咱們說攔截的方法包含以上四種,那就一個一個來看,它們究竟是怎麼實現攔截的。ui
在上一章節分析SQL的執行過程的時候咱們看到,Mybatis會先建立一個sqlSession對象。在建立sqlSession的時候,就會建立一個執行器。Executor.query方法是一開始就調用的方法,此時SQL還都是一個一個的sqlNode節點未解析的狀態。this
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)
來肯定是否須要產生代理。spa
public class InterceptorChain {
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
複製代碼
interceptor咱們知道,在構建SqlSessionFactory的時候,就把配置的攔截器加入到其中了。那麼在這裏,它是循環全部自定義的攔截器,調用其plugin方法。這也就解釋了咱們爲何在plugin方法中要進行類型判斷,不然每次返回的對象就是最後一個攔截器的代理對象。.net
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);
}
}
}
複製代碼
在執行到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;
}
}
複製代碼
在上一步實例化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,它們執行的時機分別以下:
生成sqlSession對象以後,開始調用Executor進行實際方法的調用。
解析完SQL,建立PreparedStatement對象預編譯並設置參數。
從數據庫拿到數據,並轉換爲Java數據集合以後返回。
思考一下,這三種類型攔截器咱們能夠哪來作什麼呢?下節課,筆者會拿實際案例來展現它們的應用。