文章出處

Mybatis插件原理:Mybatis采用責任鏈模式,通過動態代理組織多個攔截器(插件),通過這些攔截器可以改變Mybatis的默認行為(諸如SQL重寫之類的),由于插件會深入到Mybatis的核心,因此在編寫自己的插件前最好了解下它的原理,以便寫出安全高效的插件。

代理鏈的生成

Mybatis支持對Executor、StatementHandler、PameterHandler和ResultSetHandler進行攔截,也就是說會對這4種對象進行代理。下面以Executor為例。Mybatis在創建Executor對象時會執行下面一行代碼:

  1. executor=(Executor)interceptorChain.pluginAll(executor);

    InterceptorChain里保存了所有的攔截器,它在mybatis初始化的時候創建。上面這句代碼的含義是調用攔截器鏈里的每個攔截器依次對executor進行plugin(插入?)代碼如下:

/***每一個攔截器對目標類都進行一次代理
*@paramtarget*@return層層代理后的對象
*/publicObjectpluginAll(Objecttarget){
for(Interceptorinterceptor:interceptors){target=interceptor.plugin(target);
}returntarget;
}@Intercepts({@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})})publicclassExamplePluginimplementsInterceptor{
@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{
returninvocation.proceed();}
 @Override
publicObjectplugin(Objecttarget){returnPlugin.wrap(target,this);
}@OverridepublicvoidsetProperties(Propertiesproperties){
}}

每一個攔截器都必須實現上面的三個方法,其中:

1)Object intercept(Invocation invocation)是實現攔截邏輯的地方,內部要通過invocation.proceed()顯式地推進責任鏈前進,也就是調用下一個攔截器攔截目標方法。

2)Object plugin(Object target)就是用當前這個攔截器生成對目標target的代理,實際是通過Plugin.wrap(target,this)來完成的,把目標target和攔截器this傳給了包裝函數。

3)setProperties(Properties properties)用于設置額外的參數,參數配置在攔截器的Properties節點里。

注解里描述的是指定攔截方法的簽名[type,method,args](即對哪種對象的哪種方法進行攔截),它在攔截前用于決斷。

Plugin.wrap方法

從前面可以看出,每個攔截器的plugin方法是通過調用Plugin.wrap方法來實現的。代碼如下:

 

publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){//從攔截器的注解中獲取攔截的類名和方法信息Map,Set>signatureMap=getSignatureMap(interceptor);Classtype=target.getClass();//解析被攔截對象的所有接口(注意是接口)Class[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){//生成代理對象,Plugin對象為該代理對象的InvocationHandler(InvocationHandler屬于java代理的一個重要概念,不熟悉的請參考相關概念)returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}

這個Plugin類有三個屬性:

private Object target;//被代理的目標類

private Interceptor interceptor;//對應的攔截器

private Map, Set> signatureMap;//攔截器攔截的方法緩存<?>

我們再次結合(Executor)interceptorChain.pluginAll(executor)這個語句來看,這個語句內部對

executor執行了多次plugin,第一次plugin后通過Plugin.wrap方法生成了第一個代理類,姑且就叫executorProxy1,這個代理類的target屬性是該executor對象。第二次plugin后通過Plugin.wrap方法生成了第二個代理類,姑且叫executorProxy2,這個代理類的target屬性是executorProxy1...這樣通過每個代理類的target屬性就構成了一個代理鏈(從最后一個executorProxyN往前查找,通過target屬性可以找到最原始的executor類)。

代理鏈上的攔截

代理鏈生成后,對原始目標的方法調用都轉移到代理者的invoke方法上來了。Plugin作為InvocationHandler的實現類,他的invoke方法是怎么樣的呢?

publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{Setmethods=signatureMap.get(method.getDeclaringClass());if(methods!=null&&methods.contains(method)){//調用代理類所屬攔截器的intercept方法,returninterceptor.intercept(newInvocation(target,method,args));}returnmethod.invoke(target,args);}catch(Exceptione){throwExceptionUtil.unwrapThrowable(e);}

在invoke里,如果方法簽名和攔截中的簽名一致,就調用攔截器的攔截方法。我們看到傳遞給攔截器的是一個Invocation對象,這個對象是什么樣子的,他的功能又是什么呢?

publicclassInvocation{privateObjecttarget;privateMethodmethod;privateObject[]args;publicInvocation(Objecttarget,Methodmethod,Object[]args){this.target=target;this.method=method;this.args=args;}...publicObjectproceed()throwsInvocationTargetException,IllegalAccessException{returnmethod.invoke(target,args);}}publicObjectintercept(Invocationinvocation)throwsThrowable{//完成代理類本身的邏輯...//通過invocation.proceed()方法完成調用鏈的推進returninvocation.proceed();}

 


看文倉www.kanwencang.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。
歡迎轉載:http://www.kanwencang.com/bangong/20170111/85021.html

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()