咱們平時去餐廳吃飯,不是直接告訴廚師作什麼菜的,而是先告訴服務員點什麼菜,而後由服務員傳到給廚師,至關於服務員是廚師的代理,咱們經過代理讓廚師炒菜,這就是代理模式。代理模式須要三個東西:
真實對象——廚師(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("結帳"); } }
可是靜態代理的缺點也很明顯,多了代理類,工做量變大,開發效率下降。爲了彌補這些缺點,就能夠使用動態代理。動態代理又有幾種實現
基於接口動態代理——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方法裏面第二個參數就是傳入真實對象的接口。