【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外掛開發的引言完畢!其實是使用了動態代理和責任鏈結合的方式。