java框架學習日誌-7(靜態代理和JDK代理)

靜態代理

咱們平時去餐廳吃飯,不是直接告訴廚師作什麼菜的,而是先告訴服務員點什麼菜,而後由服務員傳到給廚師,至關於服務員是廚師的代理,咱們經過代理讓廚師炒菜,這就是代理模式。代理模式須要三個東西:
真實對象——廚師(chef),用戶真正須要去用到的對象。
調用者——客人(Client),須要用到真實對象的對象。
代理對象——服務員(waiter),代理真實對象與調用者溝通的對象。
廚師須要有個炒菜的方法,服務員負責代理廚師,因此也須要有一個炒菜方法,爲了保證兩個對象的炒菜方法一致,須要一個接口。java

public interface Cook {
    public void cook(String name);//傳入菜品名字
}

而後chef實現這個接口spring

public class Chef implements Cook{
    @Override
    public void cook(String name) {
        System.out.println("正在炒"+name);
    }
}

waiter也須要實現這個接口ide

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //經過構造函數與獲取chef的實例對象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //本身不實現作菜,而是調用chef的方法
        chef.cook("魚香肉絲");
    }
}

設置好後,客人就來點餐了函數

public class Client {

    public static void main(String[] args) {

        Chef chef=new Chef();
        Waiter waiter=new Waiter(chef);
        waiter.cook("魚香肉絲");
    }
}


這裏client沒有直接調用chef的cook方法,而是經過waiter調用的cook方法。這樣waiter就起到了代理的做用。代理的優勢是可讓真實對象處理的業務更加純粹,再也不去關注一些公共的事情,公共的業務由代理來完成。
好比這個點餐的例子,可讓廚師專心作飯,不用去管點餐的事情,若是還要增長收錢功能,就可讓服務員去完成。測試

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //經過構造函數與獲取chef的實例對象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //本身不實現作菜,而是調用chef的方法
        chef.cook("魚香肉絲");
    }
    public void pay(){
        System.out.println("結帳");
    }
}

動態代理

** 動態代理是spring的難點和重點。務必好好掌握。**

可是靜態代理的缺點也很明顯,多了代理類,工做量變大,開發效率下降。爲了彌補這些缺點,就能夠使用動態代理。動態代理又有幾種實現
基於接口動態代理——jdk動態代理
基於類動態代理——cglib動態代理
還有用javasist來生成動態代理this

先說jdk動態代理,動態代理只須要一個代理類就能夠代理全部真實對象。靜態代理是這麼怎麼作的呢?上面例子中,靜態代理類(Waiter)代理了真實對象(Chef)。爲了保證真實對象的方法(cook)與代理類的方法(cook)一致,因此須要都實現同一個接口(Cook),那若是還須要代理其餘類呢?好比代理老闆收錢,又須要實現pay接口,代理會計算帳,又須要實現reckon接口。就會很麻煩。代理

動態代理只須要代理類實現一個InvocationHandler接口。就能夠在代理類裏面動態設置代理對象。API裏InvocationHandLer是這樣描述的code



InvocationHandler是由代理實例的調用處理程序實現的接口 。 在這個例子中,代理實例就是指服務員(waiter),調用處理程序指調用廚師(chef)的作菜(cook),就是chef.cook("魚香肉絲")。這裏還提到了invoke方法對象



處理代理實例上的方法調用並返回結果。 當在與之關聯的代理實例上調用方法時,將在調用處理程序中調用此方法。結合例子,當你想點餐(處理代理實例上的方法)時,就能夠調用這個方法。而後結果點餐結果也會告訴你(並返回結果),由於這個方法能夠代理不少真實對象,因此返回的Object。
還有一個須要瞭解的Proxy類,API中的描述blog



注意這裏斷句,Proxy提供了建立動態代理類和實例,的靜態方法。這裏的靜態方法就是



第一個參數——類加載器,java去執行某一個類的時候,須要將這個.clss文件加載到java虛擬機裏面去,這個加載的過程就須要類加載器去加載。 第二個參數——要實現接口的列表,接口也有class。因此是class<?>[]。
第三個參數——這個InvocationHandler就和以前的聯繫上了。
大概瞭解後能夠配合代碼理解了。咱們在上面點餐的例子上更改。動態代理指動態生成的代理類,所以接口(Cook)依然存在,真實對象(Chef)也存在。而後是代理類(waiter),代理類須要去實現InvocationHandler接口,由於能夠實現動態代理了,稍微改個名字Waiter2。

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

public class Waiter2 implements InvocationHandler{
    private Cook cook;//真實對象,由於chef實現了Cook,因此建立cook就能夠了

    public void setCook(Cook cook) {
        this.cook = cook;
    }


    /**
     *代理方法邏輯
     * @param proxy 代理對象(chef)
     * @param method 當前調度方法,代理對象的調用處理程序方法的對象(cook())
     * @param args 當前方法的參數 (菜品名)
     * @return 返回代理結果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一個參數是真實對象
        return obj;
    }

    /**
     * 生成代理類
     * @return 返回代理類
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }
}

詳細解釋

private Cook cook;//真實對象,由於chef實現了Cook,因此建立cook就能夠了

    public void setCook(Cook cook) {
        this.cook = cook;
    }

代理對象依然由於須要代理真實對象,因此仍是須要先建立真實對象,真實對象廚師Chef繼承了接口Cook的烹飪方法cook,因此建立接口Cook就能夠了。而後經過set方法來設置真實對象。

/**
     *代理方法邏輯
     * @param proxy 代理對象(chef)
     * @param method 當前調度方法,代理對象的調用處理程序方法的對象(cook())
     * @param args 當前方法的參數 (菜品名)
     * @return 返回代理結果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一個參數是真實對象
        return obj;
    }

以前提到過,invoke是處理代理實例上的方法調用並返回結果,能夠理解成讓代理對象與真實對象創建邏輯關係。
這裏的method.invoke()是經過反射去調用真實對象,第一個參數cook就是真實對象,第二個參數就是傳的參數值。 而後返回代理結果就能夠了。
以前還提到過Proxy有一個靜態方法能夠生成代理類。

/**
     * 生成代理類
     * @return 返回代理類
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }

newProxyInstance的第一個參數是類加載器,直接傳入當前類的類加載器。
第二參數是接口,這裏傳入cook的接口。 第三個參數是InvocationHandler,自己這個類就實現了InvocationHandler接口,因此傳入當前類。
而後是客戶端

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //獲取真實對象的實例
        Waiter2 waiter2=new Waiter2();//獲取代理的實例
        waiter2.setCook(chef); //設置代理去代理哪一個真實實例
        Cook proxy=(Cook) waiter2.getProxy();//建立代理,getProxy返回的是object,因此轉換一下。
        proxy.cook("宮保雞丁");//經過代理去控制真實對象。
    }
}

Cook proxy=(Cook) waiter2.getProxy();這一行是用來建立代理的,proxy就至關於以前的服務員。建立爲Cook類型是爲了調用cook方法。以前的靜態代理中,用的是Waiter waiter去建立代理,由於Waiter也實現了Cook接口。因此能夠用Waiter類型。
運行結果

若是要添加公共方法到代理對象中

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("來了,老弟");
        Object obj=method.invoke(cook,args);//第一個參數是真實對象
        return obj;
    }


以前說靜態代理有個壞處,有不少類,動態代理能夠解決這個問題。如今好像還看不出來。咱們稍微改一下,讓動態代理類能夠代理全部類。
這個例子中代理的是廚師,也能夠加入收錢功能,或者打掃衛生功能。真實對象怎麼變,咱們的目的不變,代理真實對象,那麼就能夠把Waiter中的真實對象Cook,換成Object。Waiter2代碼以下

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

public class Waiter2 implements InvocationHandler{
    private Object target;//真實對象

    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     *代理方法邏輯
     * @param proxy 代理對象(chef)
     * @param method 當前調度方法,代理對象的調用處理程序方法的對象(cook())
     * @param args 當前方法的參數 (菜品名)
     * @return 返回代理結果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("服務員,麻煩你");//公共方法
        Object obj=method.invoke(target,args);//第一個參數是真實對象
        return obj;
    }

    /**
     * 生成代理類
     * @return 返回代理類
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

新增兩個接口,打掃和付錢,和它們的接口實現類

public interface Clean {
    public  void clean();
}

public class CleanImpl implements Clean{
    public void clean(){
        System.out.println("打掃");
    }
}

public interface Pay {
    public void paymoney(int i);
    public void free();
}

public class PayImpl implements Pay{
    public void paymoney(int i){
        System.out.println("付錢誒!一共"+i);
    }
    public void free(){
        System.out.println("能不能免單");
    }
}

而後再客戶端測試一下

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //獲取真實對象的實例
        Pay pay=new PayImpl();
        Clean clean=new CleanImpl();
        Waiter2 waiter2=new Waiter2();//獲取代理的實例

        waiter2.setTarget(chef); //設置代理去代理哪一個真實實例
        Cook cookproxy=(Cook) waiter2.getProxy();//建立代理,getProxy返回的是object,因此轉換一下。
        cookproxy.cook("宮保雞丁");//經過代理去控制真實對象。

        waiter2.setTarget(pay);
        Pay payproxy=(Pay) waiter2.getProxy();
        payproxy.paymoney(50);
        payproxy.free();

        waiter2.setTarget(clean);
        Clean cleanproxy=(Clean) waiter2.getProxy();
        cleanproxy.clean();
    }
}

這樣這個代理類就能夠代理各類各樣的類。注意被代理的類必須實現接口,由於在newProxyInstance方法裏面第二個參數就是傳入真實對象的接口。

相關文章
相關標籤/搜索