詳解動態代理及其實現原理

1.什麼是代理。比如(工廠,商店,客戶),工廠是委託類,商店是代理類,工廠委託商店做代理來買產品,可以這樣通俗理解。
2.代理的好處:(1.可以隱藏委託類的實現;2.可以實現客戶與委託類間的解耦,在不修改委託類代碼的情況下能夠做一些額外的處理,比如買菸的時候限制年齡…可以在代理類中進行對應的操作。)
3.再講動態代理之前,先明白靜態代理是怎麼實現的,若代理類在程序運行前就已經存在,那麼這種代理方式被成爲靜態代理。
看代碼:
公共的接口Sell:

package proxy;
/**
 * @author yhl
 * 解釋爲什麼要都實現統一接口:通常情況下,靜態代理中的代理類和委託類會實現同一接口或是派生自相同的父類,不然之間沒有一點關係,更談不上代理什麼了)
 */
public interface Sell {
    void sell();//買東西
}

工廠類:

package proxy;

public class Factory implements Sell{

    @Override
    public void sell() {
        System.out.println("Factory sell...");      
    }

}

Store類:

package proxy;

public class Store implements Sell {
    //靜態代理可以通過聚合來實現,讓代理類持有一個委託類的引用即可。
    private Factory factory = null;

    public Store(Factory fc) {

        this.factory = fc;
    }

    @Override
    public void sell() {
        System.out.println("before"); 
            factory.sell();
        System.out.println("end"); 

    }
    //重載一個方法
    public void sell(String param) {
        if(choose(param)){
            System.out.println("before"); 
                factory.sell();
            System.out.println("end"); 
        }else{
            System.out.println("不符合要求");
        }
    }
    /*
     * 根據需求添加一個過濾功能---賣煙給學生
     * 通過靜態代理,我們無需修改Factory類的代碼就可以實現,只需在Store類中的sell方法中添加一個判斷即可如下所示:
     */
    public boolean choose(String param) {
        boolean flag = false;
        if (!param.equals("學生")) {
            flag = true;
        }
        return flag;
    }

}

測試類:

package proxy;

public class Client {

    @Test
    public void 靜態代理測試() {
        Store st = new Store(new Factory());
        st.sell();
        st.sell("學生2");
    }

}

代碼看完之後:
總結靜態類:照應第二個優點,可以實現客戶與委託類間的解耦,在不修改委託類代碼的情況下能夠做一些額外的處理。靜態代理的侷限在於運行前必須編寫好代理類。
動態代理
假設需求:在執行委託類中的方法之前輸出“before”,在執行完畢後輸出“after”。我們還是以上面例子中的Factory類作爲委託類,Store類作爲代理類來進行介紹,假如使用靜態代理來實現這一需求,請返回看上邊的store類代碼,假如說委託對象裏邊有很多方法,那麼這樣的需求添加操作就顯得不合適,造成代碼的臃腫,顯然不符合現在的代碼編寫規範。由此就引出動態代理。
基本概念:在程序運行時,根據委託類及其實現的接口,動態的創建一個代理類,當調用代理類實現的抽象方法時,就發起對委託類同樣方法的調用(可以講解完代碼回頭再看一遍)。
涉及的技術點:1.要提供一個實現了InvocationHandler接口的實現類,並重寫其invoke()方法.2.通過Proxy獲取動態代理實例,查看jdk-api。
先看一個完整的動態代理例子,看到的朋友可以直接拷貝在本地運行:

package proxy.dynmic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//接口
interface Human{
    void info();
    void fly();
}

//委託類
class SuperMan implements Human{
    public void info(){
        System.out.println("我是超人"); 
    }
    public void fly(){
        System.out.println("i can fly !!!");
    }
}

class HumanUtil{
    public void method1(){
        System.out.println("執行method1");
    }
    public void method2(){
        System.out.println("執行method2");
    }
}

//動態的創建一個代理類對象
class MyProxy{
    public static Object getProInstance(Object obj){
        MyInvocationHandler handle = new MyInvocationHandler();
        handle.setObject(obj);//將被代理對象傳進去進行實例化(委託類)
        //創建一個obj對象對應的代理類
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handle);
    }
}

/**
 * handle
 * 處理器-中介類,實現了InvocationHandler這個接口的中介類用做“調用處理器”,”攔截“對代理類方法的調用。當我們調用代理類對象的方法時,這個“調用”會轉送到invoke方法中,
 * 代理類對象作爲proxy參數傳入,參數method標識了我們具體調用的是代理類的哪個方法,args爲這個方法的參數,這樣一來,我們對代理類中的所有方法的調用都會變爲對invoke的調用
 */
class MyInvocationHandler implements InvocationHandler{

    Object obj = null;//被代理對象的聲明(委託類)

    //實例化對象
    public void setObject(Object o){
        this.obj=o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //proxy參數傳遞的即是代理類的實例
        System.out.println("name:"+proxy.getClass().getName());
        //HumanUtil hu= new HumanUtil();//想要在執行1和2之間動態的執行插入的方法
        //hu.method1();
        Object returnval = method.invoke(obj, args);
        //hu.method2();
        return returnval;
    }

}

public class TestAop {
    public static void main(String[] args) {
        SuperMan sm = new SuperMan();
        Object obj  = MyProxy.getProInstance(sm);//代理類對象,同樣的代理類對象也是實現SuperMan接口的
        Human hm = (Human) obj;
        hm.info();//通過代理類的對象調用重寫的抽象方法,實際上會轉到invoke方法調用
    }
}

接下來,開始挨着解決疑點:
類 Proxy,提供用於創建動態代理類和實例的靜態方法,它還是由這些方法創建的所有動態代理類的超類。我們調用Proxy類的newProxyInstance方法來獲取一個代理類實例。這個代理類實現了我們指定的接口並且會把方法調用分發到指定的調用處理器。

//動態的創建一個代理類對象
class MyProxy{
    public static Object getProInstance(Object obj){
        MyInvocationHandler handle = new MyInvocationHandler();
        handle.setObject(obj);//將被代理對象傳進去進行實例化(委託類)
        //創建一個obj對象對應的代理類
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handle);
    }
}

方法的三個參數含義分別如下:
loader:定義了代理類的ClassLoder;
interfaces:代理類實現的接口列表
h:調用處理器,也就是我們上面定義的實現了InvocationHandler接口的類實例。
對應的代碼就是我們MyProxy類中的Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handle);
handle就是調用處理器,指的是MyInvocationHandler這個類。
類InvocationHandler是代理實例的—調用處理程序(handler)—實現的接口。
每個代理實例都具有一個關聯的調用處理程序。對代理實例調用方法時,將對方法調用進行編碼並將其指派到它的調用處理程序的 invoke 方法。
更加通俗的解釋:在使用動態代理時,我們需要定義一個位於代理類與委託類之間的中介類,這個中介類被要求實現InvocationHandler接口,中介類其實就是調用處理程序。接下來我們來看看MyInvocationHandler這個中介類的類是怎麼實現的:

/**
 * handle
 * 處理器-中介類,實現了InvocationHandler這個接口的中介類用做“調用處理器”,”攔截“對代理類方法的調用。當我們調用代理類對象的方法時,這個“調用”會轉送到invoke方法中,
 * 代理類對象作爲proxy參數傳入,參數method標識了我們具體調用的是代理類的哪個方法,args爲這個方法的參數,這樣一來,我們對代理類中的所有方法的調用都會變爲對invoke的調用
 */
class MyInvocationHandler implements InvocationHandler{

    Object obj = null;//被代理對象的聲明(委託類)

    //實例化對象
    public void setObject(Object o){
        this.obj=o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //proxy參數傳遞的即是代理類的實例
        System.out.println("name:"+proxy.getClass().getName());
        //HumanUtil hu= new HumanUtil();//想要在執行代理方法之前之後動態的插入1和2方法
        //hu.method1();
        Object returnval = method.invoke(obj, args);
        //hu.method2();
        return returnval;
    }

}

最後再解釋一下:
從以上代碼中我們可以看到,中介類持有一個委託類對象引用,在invoke方法中調用了委託類對象的相應方法,看到這裏是不是覺得似曾相識?通過聚合方式持有委託類對象引用,把外部對invoke的調用最終都轉爲對委託類對象的調用。這不就是我們上面介紹的靜態代理的一種實現方式嗎?實際上,中介類與委託類構成了靜態代理關係,在這個關係中,中介類是代理類,委託類就是委託類; 代理類與中介類也構成一個靜態代理關係,在這個關係中,中介類是委託類,代理類是代理類。也就是說,動態代理關係由兩組靜態代理關係組成,這就是動態代理的原理
至此,代理的模式已經給大家講解完了,但是在此提出一個疑問,爲什麼在測試類中執行hm.info()時,實際上會轉到invoke方法調用。當你執行完測試代碼後會看到:
這裏寫圖片描述
如圖片所示,代理類的父類是Proxy,實現了Human接口,但是具體的代理類是怎麼生成的,從Client中的代碼看,可以從newProxyInstance這個方法作爲突破口,我們先來看一下Proxy類中newProxyInstance方法的源代碼:

public static Object newProxyInstance(ClassLoader loader,  
        Class<?>[] interfaces,  
        InvocationHandler h)  
throws IllegalArgumentException  
{  
    if (h == null) {  
        throw new NullPointerException();  
    }  

    /* 
     * Look up or generate the designated proxy class. 
     */  
     //這裏是生成class的地方,獲得與指定類裝載器和一組接口相關的代理類類型對象 
    Class cl = getProxyClass(loader, interfaces);  

    /* 
     * Invoke its constructor with the designated invocation handler. 
     */  
    try {  
           /* 
            * Proxy源碼開始有這樣的定義: 
            * private final static Class[] constructorParams = { InvocationHandler.class }; 
            * cons即是形參爲InvocationHandler類型的構造方法 
           */  
            // 調用代理對象的構造方法(也就是$Proxy0(InvocationHandler h))
        Constructor cons = cl.getConstructor(constructorParams);  
        // 生成代理類的實例並把MyInvocationHandler的實例傳給它的構造方法
        return (Object) cons.newInstance(new Object[] { h });  
    } catch (NoSuchMethodException e) {  
        throw new InternalError(e.toString());  
    } catch (IllegalAccessException e) {  
        throw new InternalError(e.toString());  
    } catch (InstantiationException e) {  
        throw new InternalError(e.toString());  
    } catch (InvocationTargetException e) {  
        throw new InternalError(e.toString());  
    }  
}  

看代碼,newProxyInstance方法做了以下幾件事. (1)根據參數loader和interfaces調用方法 getProxyClass(loader, interfaces)創建代理類Proxy0,Proxy0類實現了interfaces的接口,並繼承了Proxy類.
(2)實例化Proxy0並在構造方法中把委託類(handler–MyInvocationHandler)傳過去,接着$Proxy0調用父類Proxy的構造器,爲h賦值,如下:
Proxy0構造器:

 public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }

父類Proxy構造器:

class Proxy{  
    InvocationHandler h=null;  
    protected Proxy(InvocationHandler h) {  
        this.h = h;  
    }  
    ...  
} 

看下生成的動態代理類(類Proxy的getProxyClass方法調用ProxyGenerator的 generateProxyClass方法產生ProxySubject.class的二進制數據):

public final class $Proxy0 extends Proxy implements Human{  
    private static Method m1;  
    private static Method m0;  
    private static Method m3;  
    private static Method m2;  

    static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  

            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  

            //反射原理
            m3 = Class.forName("***.SuperMan").getMethod("info",  
                    new Class[0]);  

            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  

        } catch (NoSuchMethodException nosuchmethodexception) {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        } catch (ClassNotFoundException classnotfoundexception) {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    } //static  

    public $Proxy0(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }  

    @Override  
    public final boolean equals(Object obj) {  
        try {  
            return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  

    @Override  
    public final int hashCode() {  
        try {  
            return ((Integer) super.h.invoke(this, m0, null)).intValue();  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  /** 
     * 這個方法是關鍵部分 
     */ 
    public final void info() {  
        try {  
        // 實際上就是調用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二個問題就解決了
            super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  

    @Override  
    public final String toString() {  
        try {  
            return (String) super.h.invoke(this, m2, null);  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
}  

總結
一個典型的動態代理創建對象過程可分爲以下四個步驟:
1、通過實現InvocationHandler接口創建自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2、通過爲Proxy類指定ClassLoader對象和一組interface創建動態代理類
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3、通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通過構造函數創建代理類實例,此時需將調用處理器對象作爲參數被傳入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
爲了簡化對象創建過程,Proxy類中的newInstance方法封裝了2~4,只需兩步即可完成代理對象的創建。
5、重點解決疑惑:
生成的$Proxy0繼承Proxy類實現Human接口,實現的Human的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))
最後
寫到這裏,基本上關於動態代理的相關問題就基本解釋完成了,這篇文檔的編寫,也參考了不少相關文檔,自己也看了一些視頻介紹,自己會在下邊列出參考文獻,如果有讀到這篇文章的朋友們,要是還有疑問的話,可以留言一起探討,共同進步!!!如果想要再深一層次的去理解,就要熟讀源碼了,這個隨着以後能力的提升再次拜讀。。。相關切面Aop我在這裏就不再多說了,理解起來也比較簡單,可以再參考一些其他的,就到這吧。
參考文獻:
http://blog.csdn.net/zcc_0015/article/details/22695647
http://rejoy.iteye.com/blog/1627405?page=2
https://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章