今天主要是在看mybatis的主流程原始碼,其中比較感興趣的是mybatis的plugin功能,這裡主要記錄下mybatis-plugin的外掛功能原理。
plugin集合列表:在構建SqlSessionFactory
時,通過解析設定或者plugin-bean的注入,會將所有的mybatis-plugin都收集到Configuration
物件的interceptorChain
屬性中。InterceptorChain類定義如下:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
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);
}
}
plugin作用物件:Executor
,ParameterHandler
,ResultSetHandler
,StatementHandler
,這4個物件在mybatis執行sql的過程中
有不同的作用。
Executor:sql執行的具體操作物件。
ParameterHandler:sql執行前的引數處理物件。
ResultSetHandler:sql執行後的結果集處理物件。
StatementHandler:具體送到資料庫執行的sql操作物件。
plugin作用原理:類似AOP,使用JDK動態代理,只不過mybatis的增強物件不是所有物件,而是上面陳列的4個物件而已。
在4個物件建立時,都會對各個物件進行判斷,是否需要進行外掛化。比如下面的外掛:
@Intercepts({@Signature( type= Executor.class, method = "query", args ={
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})})
public class ExamplePlugin implements Interceptor {
// 分頁 讀寫分離 Select 增刪改
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("代理");
Object[] args = invocation.getArgs();
MappedStatement ms= (MappedStatement) args[0];
// 執行下一個攔截器、直到盡頭
return invocation.proceed();
}
}
該外掛將會在Executor
該物件建立時,使用該外掛進行增強。在新開一個sqlSession時,將會建立Executor物件。跟蹤到具體方法:
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 {
//簡單的sql執行器物件
executor = new SimpleExecutor(this, transaction);
}
//判斷mybatis的全域性組態檔是否開啟快取
if (cacheEnabled) {
//把當前的簡單的執行器包裝成一個CachingExecutor
executor = new CachingExecutor(executor);
}
/**
* TODO:呼叫所有的攔截器物件plugin方法
* 外掛: 責任鏈+ 裝飾器模式(動態代理)
*/
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
我們找到interceptorChain.pluginAll
方法:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
發現會通過已載入的所有plugin列表中,逐個遍歷去篩選出符合Executor
型別的外掛,再通過具體外掛的interceptor.plugin
方法去建立
Executor的代理物件。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
再看到具體的Plugin.wrap(target, this)
方法:
public static Object wrap(Object target, Interceptor interceptor) {
// 獲得interceptor設定的@Signature的type
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 當前代理型別
Class<?> type = target.getClass();
// 根據當前代理型別 和 @signature指定的type進行配對, 配對成功則可以代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
這裡我們就很清楚了,通過@Signature
註解上的type、method、args屬性去匹配,如果找到符合的,就會為物件建立代理物件,並返回代理物件。
責任鏈設計模式:因為一個增強物件可能會有多個plugin的增強邏輯,所以在執行的時候使用的是責任鏈設計模式。
因為Plugin.wrap()
方法新建的代理物件中使用的InvocationHandler物件是Plugin本身,所以在執行方法的時候首先要呼叫它的invoke
方法,
@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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
當我們執行Executor的query方法時,符合if (methods != null && methods.contains(method)) {
條件,這時就會去執行具體外掛的增強方法,interceptor.intercept
,
然後再通過傳遞new Invocation(target, method, args)
物件,在外掛執行完之後,再呼叫invocation.proceed()
去執行下一個外掛邏輯。
如下是對Executor的query方法新增了2個外掛的場景:
總結:如果我們的業務需要我們去編寫sql外掛,那我們就需要來研究下Executor
,ParameterHandler
,ResultSetHandler
,StatementHandler
這4個物件的具體跟sql相關的方法,
然後再進行修改,就可以直接起到aop的作用。