5.MyBatis外掛的原理?原始碼剖析
技術標籤:MyBatis外掛原理mybatis
Mybatis外掛的載入原理
1.MyBatis支援自定義外掛,目的是能讓開發者結合業務對框架進行增強,自定義的外掛類必須實現org.apache.ibatis.plugin.Interceptor介面。切需要在核心XML配置檔案中通過plugins顯式宣告,如:
2.進入XMLConfigBuilder類的pluginElement()方法,這裡式解析核心配置檔案plugins標籤的地方,根據原始碼得知這裡解析plugin標籤根據反射生成外掛實現類並儲存到configuration的interceptorChain中一個Interceptor的集合。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute ("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration. addInterceptor(interceptorInstance);
}
}
}
總結:根據這裡可以得知如果多個外掛同時增強代理一個Handler那麼在配置檔案plugins標籤中排在最後的外掛攔截器最先執行。
Mybatis外掛依靠動態代理生成代理類原理
用Execute舉例
1.SqlSessionFactory的openSession()方法中openSessionFromDataSource()方法中會建立Execute的實現類並賦值給SqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 在此處實力Execute
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
2.進入configuration.newExecutor(tx, execType);方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object 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);
}
// 判斷是否開啟二級快取,如果開啟二級快取則使用CachingExecutor
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
// 根據XML中配置的攔截器,為現有的Executor 進行動態代理增強
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
3.進入InterceptorChain的pluginAll()方法
interceptorChain儲存了所有的攔截器(interceptors),是mybatis初始化的時候建立的。呼叫攔截器鏈中的攔截器依次的對目標進行攔截或增強。interceptor.plugin(target)中的target就可以理解為原生的Executor 。返回的target是被重重代理後的物件
// 註冊的攔截器外掛集合
private final List<Interceptor> interceptors = new ArrayList();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
4.進入每個外掛攔截器的interceptor.plugin(target);方法
攔截器Interceptor有三個抽象方法需要實現分別是
public interface Interceptor {
//當代理Executor執行被攔截的方法是,對應的攔截器會監聽到並執行intercept方法引數Invocation 包含
//private final Object target; 被代理的Executor或者Executor的上一層代理類
//private final Method method;被代理的Executor所執行的方法
//private final Object[] args;執行方法的引數
//三大物件,一般實現會:{
//sysout("對方法進行了前置增強....");
//invocation.proceed(); 執行原方法
//sysout("對方法進行了後置增強....");
//}
Object intercept(Invocation var1) throws Throwable;
// 主要是為了把這個攔截器生成一個代理放到攔截器鏈中
//引數為Executor物件,返回值為Executor代理物件
//InterceptorChain的pluginAll()方法中呼叫用於生成Executor的重重代理物件
// 一般實現{return Plugin.wrap(target,this);}
Object plugin(Object var1);
//外掛初始化的時候呼叫,也只調用一次,外掛配置的屬性從這裡設定進來即獲取plugin標籤中的property標籤內容
void setProperties(Properties var1);
}
5.進入Plugin.wrap(target,this);方法
public static Object wrap(Object target, Interceptor interceptor) {
// 獲取外掛攔截器攔截的方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 獲取代理前物件的型別
Class<?> type = target.getClass();
// 獲取代理前物件所實現的全部介面
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果所實現的介面大於0則使用JDK動態代理生成代理類,如果小於0則返回原物件。
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
總結:
在建立Executor的實現類的過程中如果有外掛攔截器攔截可Executor中的某個方法並且有再XML中顯式配置則生成的Executor實現類為動態代理物件。
Mybatis代理類執行原理
用Execute中的query方法舉例
1.根據Mybatis外掛依靠動態代理生成代理類原理可以如果Execute為代理類,當query方法執行時,實際上執行的應該時Plugin中的invoke方法,因為Execute為JDK動態代理產生的代理類
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try
{
/**獲取被攔截方法列表,比如: * signatureMap.get(Executor.class), 可能返回 [query, update, commit] */
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){
}
}
總結:外掛的實現原理採用了代理模式(JDK動態代理)