Mybatis原始碼分析(六)外掛的建立代理過程

清幽之地發表於2019-03-10

一、配置

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資料集合之後返回。

思考一下,這三種型別攔截器我們可以哪來做什麼呢?下節課,筆者會拿實際案例來展示它們的應用。

相關文章