【Mybtais】Mybatis 插件 Plugin開發(一)動態代理步步解析
需求:
對原有系統中的方法進行‘攔截’,在方法執行的前後添加新的處理邏輯。
分析:
不是辦法的辦法就是,對原有的每個方法進行修改,添加上新的邏輯;如果需要攔截的方法比較少,選擇此方法到是會節省成本。但是面對成百上千的方法怎麽辦?此時需要用到動態代理來實現。
場景:
例如:對原有的系統添加日誌記錄、添加性能分析等等。。。
舉例:
如下,需要對Sleep對象的sleep方法進行“攔截”,並在此方法的執行前後添加新的邏輯。想知道‘睡覺前幹了什麽?睡覺後幹了什麽?’
interface Sleep { public void sleep(); }
public classSleepImpl implements Sleep{ public void sleep() { System.out.println("我於"+new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date())+"開始睡覺"); } }
創建動態代理類,實現InvocationHandler接口即可。下面的wrap方法:傳入要被代理的對象target。返回包裝後的代理對象。$Proxy 打斷點會看到這樣的對象。針對下面的sleepProxy對象,sleepProxy.sleep()調用需要攔截的方法。實際上調用的是Plugin中的invoke方法。invoke方法中的method.invoke(target,args)是真是的調用被代理對象的sleep方法。所以直接在此語句的前後添加相應的邏輯即可滿足需要。
public class Plugin implements InvocationHandler { private Object target; Plugin(Object target){ this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //睡覺前做的事 Object result = method.invoke(target, args);//睡覺後做的事 return result; } public static Object wrap(Object target){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target)); } }
public class Main { public static void main(String[] args) { //要被代理的對象 Sleep sleep = new SleepImpl(); //代理對象 Sleep sleepProxy = (Sleep)Plugin.wrap(sleep); sleepProxy.sleep(); } }
到此,你以為就結束了?不 ,這個僅僅是 說了在睡覺 前後做了什麽事,加入還想知道,你在睡覺前後吃了什麽東西?當然睡覺後吃東西有點說不通。但 意會就可以了。還有其他巴拉巴拉的需求。你該怎麽做?是不是要把所有的 新的邏輯都方法 Plugin中invoke方法中去?這樣不合適吧!亂 亂 亂 這樣。那咱們能不能抽象出來一個攔截接口,接口中有攔截後要做什麽的方法。各種需求只需要實現這個攔截接口即可!
interface Interceptor { public void interceptBefore()throws Exception; public void interceptAfter()throws Exception; }
public class SleepBeforeAndAfter implements Interceptor { public void interceptBefore() throws Exception { System.out.println("之前。。。"); } public void interceptAfter() throws Exception { System.out.println("之後。。。"); } }
然後動態代理類Plugin需要修改
/** * 動態代理 * * @author 魏正迪 * 2018年10月13日 */ public class Plugin implements InvocationHandler { private Object target; private List<Interceptor> iList = new ArrayList<Interceptor>(); Plugin(Object target , List<Interceptor> iList){ this.target = target; this.iList = iList; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for(Interceptor i :iList){ i.interceptBefore(); } Object result = method.invoke(target, args); for(Interceptor i :iList){ i.interceptAfter(); } return result; } public static Object wrap(Object target,List<Interceptor> iList){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target,iList) ); } }
public class Main { public static void main(String[] args) { Sleep sleep = new SleepImpl(); List<Interceptor> iList = new ArrayList<Interceptor>(); iList.add(new SleepBeforeAndAfter()); Sleep sleepProxy = (Sleep)Plugin.wrap(sleep,iList); sleepProxy.sleep(); } }
現在想對每個對象的方法進行攔截,直接實現Interceptor接口即可!實現其中的兩個方法。此時我們新加的邏輯和原有的邏輯並沒有什麽交集。假如我們想在interceptor中的兩個方法中使用被代理對象的各種屬性,此時該怎麽做?首先想到是將interceptor接口的兩個方法添加參數。
public class SleepBeforeAndAfter implements Interceptor { public void interceptBefore(Object target, Method method, Object[] args) throws Exception { System.out.println("之前。。。interceptBefore(Object target, Method method, Object[] args)"); } public void interceptAfter(Object target, Method method, Object[] args) throws Exception { System.out.println("之後。。。interceptAfter(Object target, Method method, Object[] args)"); } }
到此,個人感覺沒啥問題了【大牛如發現明顯不符的請指出】。但但但但是我們奔著簡單明了、面向對象的思想(其實就是mybatis源碼插件設計)。我們做出進一步的精簡。於是Invocation對象產生了。看到Method對象傳進來了。我們是不是可以想到,我們不再 在Plugin中的invoke方法中調用method.invoke(target,args);了,而是在Intercetpor中處理完前後邏輯後進行調用。這樣分工明確了。
/** * 攔截對象的包裝 * @author 魏正迪 * 2018年10月13日 */ public class Invocation { private Object target; private Object []args; private Method method; Invocation(Object target,Method method,Object[] args){ this.target = target; this.args = args; this.method = method; } /** * 執行攔截對象的對應的方法 * @return * @throws Exception */ public Object process() throws Exception{ return method.invoke(target, args); } }
此時攔截器Interceptor應該是這樣的
interface Interceptor { public Object intercept(Invocation invocation)throws Exception; }
public class SleepBeforeAndAfter implements Interceptor { public Object intercept(Invocation invocation) throws Exception{ System.out.println("攔截sleep方法要執行的方法之前"); Object result = invocation.process(); System.out.println("攔截sleep方法要執行的方法之後"); return result; } }
此時Plugin應該是這樣的
public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; Plugin(Object target,Interceptor interceptor){ this.target = target; this.interceptor = interceptor; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target,method,args); return interceptor.intercept(invocation); } public static Object wrap(Object target,Interceptor interceptor){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target,interceptor) ); } }
public class Main { public static void main(String[] args) { Sleep sleep = new SleepImpl(); SleepBeforeAndAfter s = new SleepBeforeAndAfter(); Sleep sleepProxy1 = (Sleep)Plugin.wrap(sleep,s); sleepProxy1.sleep(); Sleep sleepProxy2 = (Sleep)Plugin.wrap(sleepProxy1, s); sleepProxy2.sleep(); } }
到此,mybatis插件開發的引言完畢!其實是使用了動態代理和責任鏈結合的方式。
【Mybtais】Mybatis 插件 Plugin開發(一)動態代理步步解析