代理模式其實有兩大類:靜態代理與動態代理,之前的例子中,採用實現同一接口的方式來顯式創建的代理類的方式屬於靜態代理模式,這種模式簡單易理解,缺點就是一旦需要進行代理模式設計的類有很多,需要爲每個類創建一個代理類,即爲繁瑣,這樣,動態代理應運而生,使用動態代理,可以爲多個類創建代理。
動態代理有兩種方式,一種是JDK實現的動態代理,一種是CGLIB實現的動態代理,其中JDK動態代理是基於接口的,CGLIB是基於類的,明顯CGLIB要高效一些,因爲它免去了接口的消耗與限制。
1、JDK動態代理
JDK動態代理是基於接口實現的,採用的是反射原理。
要使用JDK動態代理需要實現InvocationHandler接口,InvocationHandler接口是JDK專爲動態代理而設計的接口,其有數個實現類,是JDK中使用動態代理來實現一些其他的功能,我們的實現類似於它們,完全可以參考其代碼。
InvocationHandler接口中只有一個方法 invoke,這個方法用於調用具體的實現來完成功能,調用的過程需要藉助於Java反射原理。
還是之前的實例:
自然人接口:ZiRanRen
1 public interface ZiRanRen {2 void quanli();3 }
馬雲:MaYun
1 public class MaYun implements ZiRanRen { 2 public void eat() { 3 System.out.println("今天吃滿漢全席"); 4 } 5 public void drink() { 6 System.out.println("今天喝大西洋"); 7 } 8 @Override 9 public void quanli() {10 System.out.println("我賦予我的代理律師來行使這些權利,此時代理律師全權代理我處理某些事務");11 }12 }
InvocationHandler實例:MyInvocationHandler
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 4 public class MyInvocationHandler implements InvocationHandler{ 5 private Object target; 6 MyInvocationHandler(){super();} 7 MyInvocationHandler(Object target){ 8 super(); 9 this.target = target;10 }11 @Override12 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {13 if("quanli".equals(method.getName())){14 return method.invoke(target, args);15 }else 16 return method.invoke(target, args);17 }18 }
測試類:Clienter
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Proxy; 3 4 public class Clienter { 5 public static void main(String[] args) { 6 //動態代理實現 7 ZiRanRen ls = new MaYun(); 8 InvocationHandler invocationHandler = new MyInvocationHandler(ls); 9 ZiRanRen Proxyer = (ZiRanRen)Proxy.newProxyInstance(ls.getClass().getClassLoader(), ls.getClass().getInterfaces(), invocationHandler);10 Proxyer.quanli();11 }12 }
執行結果:
我賦予我的代理律師來行使這些權利,此時代理律師全權代理我處理某些事務
代碼解析:
(1)MyInvocationHandler實現類中定義的Object target中的target指的是接口的實現類,即之前所說的委託類,本段代碼中指的就是MaYun類,這個從最後測試類中第7、8行可以看出,我們將MyYun類的實例作爲參數來生成MyInvocationHandler實例。
(2)代碼編寫的階段並未顯式調用MaYun類的quanli方法,但是最後卻是實實在在被調用了,這時怎麼回事呢?
其實這正是動態代理的魅力所在,動態代理的動態二字的含義是不侷限於某一個具體類,當我們實現了一個InvocationHandler接口的實例之後,就可以使用這個實例進行任意類的代理,被代理的類是在運行時纔會確定(這也正是反射的能力之所在),編碼階段根本無從知曉,複用性極強。
(3)InvocationHandler接口的三個參數問題:第一個參數proxy表示的是被代理的實例(此處指MaYun類的實例ls),第二個參數method代表要執行的被代理類中的方法(此處指MaYun類中的quanli方法),第三個參數表示被代理類中方法的參數列表(此處指MaYun類的quanli方法的參數列表)。如此看來,InvocationHandler的三個參數就是精確指定要執行哪個類的哪個方法,參數是***。當然在代碼中還是泛指,這個精確要到程序運行時才能真正體現。
(4)測試類中創建代理類實例的模式與靜態代理的模式一致,表示與被代理類實現同一接口。使用Proxy代理類的靜態方法newProxyInstance方法來完成代理類實例的創建,其有三個參數:第一個是實例類的加載器,第二個是實例類實現的接口,第三個是InvocationHandler接口的實例。
2、CGLIB動態代理
CGLIB動態代理不同於JDK提供的動態代理,它不再基於接口,而是直接基於被代理類來完成代理模式。我們可以將CGLIB看作是一個代理類的生成工具,通過編程的方式來完成一個類的創建及實例的創建,不同於我們以前直接使用Class來創建類的方式,明顯複雜性提升很多,但是CGLIB將這種複雜的類生成功能屏蔽化,我們只需要藉助於它所提供的API就可以完成代理類的生成和實例的創建。
要使用CGLIB,需要嚮導入cglib的jar包,此處我們使用:cglib-nodep-2.2.2.jar。
參見如下示例代碼:
被代理類:MaYun
1 public class MaYun { 2 public void eat() { 3 System.out.println("今天吃滿漢全席"); 4 } 5 public void drink() { 6 System.out.println("今天喝大西洋"); 7 } 8 public void quanli() { 9 System.out.println("我賦予我的代理律師來行使這些權利,此時代理律師全權代理我處理某些事務");10 }11 }
方法回調器:Cgliber
1 import java.lang.reflect.Method; 2 3 import net.sf.cglib.proxy.Enhancer; 4 import net.sf.cglib.proxy.MethodInterceptor; 5 import net.sf.cglib.proxy.MethodProxy; 6 /** 7 * Cglib就猶如一個代碼式的類生成器,一般手動創建類的內容涉及到的東西,都需要通過Cglib提供的API來完成。 8 * 如下代碼中:設置超類和設置回調,對應於正常類編寫時,編寫的繼承類與方法回調 9 * 這裏實現的是MethodInterceptor接口(方法攔截器),這個接口的作用就是實現方法攔截(回調)10 *11 */12 public class Cgliber implements MethodInterceptor{13 private Object target;//這個target指的就是被代理類的實例14 /*15 * 生成代理實例(依據被代理類來生成代理類),Enhancer則是代理類生成工具16 */17 public Object getInstance(Object target){18 this.target = target;19 Enhancer enhancer = new Enhancer();20 enhancer.setSuperclass(this.target.getClass());//設置被繼承類(超類)21 enhancer.setCallback(this);//設置回調22 return enhancer.create();23 }24 /*25 * 回調方法26 * intercept是攔截之意,此處是指使用代理方法proxy來調用原類(obj)中的指定方法(method),帶參數(args),27 */28 @Override29 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {30 proxy.invokeSuper(obj, args);31 return null;32 }33 }
測試類:Clienter
1 public class Clienter { 2 public static void main(String[] args) { 3 /* 4 * Cgliber可以看成是一個帶有類生成能力的方法回調器 5 */ 6 Cgliber cgliber = new Cgliber(); 7 /* 8 * 使用方法回調器的類生成功能生成代理類實例:mayunCgliber 9 * 由於getInstance方法返回值爲Object類型,並不是MaYun類的子類型,所以需要將其強轉爲MaYun類型,實際上mayunCgliber是代理實例10 */11 MaYun mayunCgliber = (MaYun) cgliber.getInstance(new MaYun());12 mayunCgliber.quanli();13 }14 }
執行結果:
我賦予我的代理律師來行使這些權利,此時代理律師全權代理我處理某些事務
使用CGLIB的關鍵就在方法回調器類Cgliber中:
(1)實現MethodInterceptor接口,該接口中只有一個intercept方法,這個方法的作用就是攔截和回調,攔截的意思是我們可以在具體執行方法的前、後加上其他的代碼來擴展功能,回調之意爲在這個方法中調用父類(被代理類)中的指定方法。
(2)爲了生存代理類及其實例,需要在Cgiber中增加創建代理類的代碼,使用CGLIB提供的API來進行類生成,CGLIB使用Enhancer作爲類生成器,使用其下的各個接口就能完成類和實例的創建。
其實在測試類中我們可以發現一點,無論是JDK的動態代理還是CGLIB的動態代理,代理類與被代理類之間是存在耦合的,這與靜態代理一致,這個耦合提現在方法的調用之上。