1. 程式人生 > >【Mybtais】Mybatis 外掛 Plugin開發(一)動態代理步步解析

【Mybtais】Mybatis 外掛 Plugin開發(一)動態代理步步解析

需求:

  對原有系統中的方法進行‘攔截’,在方法執行的前後新增新的處理邏輯。

分析:

  不是辦法的辦法就是,對原有的每個方法進行修改,新增上新的邏輯;如果需要攔截的方法比較少,選擇此方法到是會節省成本。但是面對成百上千的方法怎麼辦?此時需要用到動態代理來實現。

場景:

  例如:對原有的系統新增日誌記錄、新增效能分析等等。。。

舉例:

  如下,需要對Sleep物件的sleep方法進行“攔截”,並在此方法的執行前後新增新的邏輯。想知道‘睡覺前幹了什麼?睡覺後幹了什麼?’

interface Sleep {
    public void sleep();
}
public class
SleepImpl 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外掛開發的引言完畢!其實是使用了動態代理和責任鏈結合的方式。