1. 程式人生 > 其它 >5.MyBatis外掛的原理?原始碼剖析

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動態代理)