前言
非常重要的一個設計模式,也很常見,很多框架都有它的影子。定義就不多說了。兩點:
1、為其它對象提供一個代理服務,間接控制對這個對象的訪問,聯想 Spring 事務機制,在合適的方法上加個 transaction 注解,就分分鍾實現了事務。
2、除了1,代理對象還能充當中介的角色。
為什么要有代理模式?
如果希望不給原有對象附加太多的責任(和本對象無關的冗余代碼),但是還想能為其實現新功能,那么代理模式就是做這個的。還是聯系 Spring 事務機制,很好的應用場景。
實際生活里,可以聯系租房,大家租房一般都會找中介,這個中介就是代理對象的角色,它解耦(分離)了房東的一部分責任,因為房東太忙了,或者房東不屑於做這些事情,故交給代理對象去做。
一句話:解耦合,提高擴展性
靜態代理模式
顧名思義,代理類被顯示的指定了,即代理在代碼里被寫死了。實現最簡單,也很少用,但是能幫助快速理解思想
public interface StaticProxy { void dosth(); } //////////////接下來是很熟悉的做法,實現這個借口 public class RealRole implements StaticProxy { @Override public void dosth() { System.out.println("do sth"); } } ///////////然后重要的角色——代理類,聯系 Spring 事務機制 public class ProxyRole implements StaticProxy { private StaticProxy staticProxy; public ProxyRole() { this.staticProxy = new RealRole(); } @Override public void dosth() { // 真正業務邏輯之前的處理,比如加上事務控制 before(); this.staticProxy.dosth(); // 真正的業務邏輯處理,比如數據庫的 crud after(); // 善后處理,比如,事務提交 } private void after() { System.out.println("after dosth"); } private void before() { System.out.println("before dosth"); } } ////////執行 public class ProxyMain { public static void main(String[] args) { StaticProxy staticProxy = new ProxyRole(); staticProxy.dosth(); } }
打印==============
before dosth
do sth
after dosth
如上就是最簡單的靜態代理模式的實現,很直觀,就是使用委托的思想,把責任轉移到被代理的對象上,代理類實現非業務相關的功能
缺陷
靜態代理非常簡單,但是它的缺陷也是顯然的,因為靜態代理的代理關系在 IDE 編譯時就確定了,如果接口改變了,不僅實現類要改變,代理類也要改變,代理類和接口之間的耦合非常嚴重。
動態代理模式
和靜態代理相反,代理類不是寫死的,而是動態的創建。又分為兩種實現方案:
基於 JDK實現
也很簡單,就是利用 Java 的 API 來實現代理類,即我們不用自己寫代理類了,也就是上面例子里的類——ProxyRole。到這里,其實也能猜出來,本質就是利用 Java 的反射機制在程序運行期動態的創建接口的實現類,並生產代理對象而已,如此一來,就能避免實現的接口——StaticProxy 改變了,導致代理類也跟着變的場景發生。下面看實現代碼:
首先寫好需要實現的接口,和具體實現類
public interface DynamicProxy { void dosth(); } //////////// public class DynamicRealRole implements DynamicProxy { @Override public void dosth() { System.out.println("do sth"); } }
然后,要實現 JDK 的一個接口——InvocationHandler,Java 的動態代理機制中,有兩個重要的類,一個是 InvocationHandler 接口,一個是 Proxy 類。
注意,DynamicProxyRole 不是代理類,代理類我們不需要自己寫,它是 JDK 動態生成給我們的(反射機制)
public class DynamicProxyRole implements InvocationHandler { private Object object; // 被代理的對象 public DynamicProxyRole(Object object) { // 構造注入 this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object ret = method.invoke(object, args); after(); return ret; } private void after() { System.out.println("after dosth"); } private void before() { System.out.println("before dosth"); } }
InvocationHandler 接口只有一個方法 —— invoke,參數也很好理解,分別是:
proxy:代理的真實對象,也就是實現類的對象
method:要調用真實對象的某個方法的Method對象,也就是會調用 dosth 的方法的對象
args:調用真實對象某個方法時接受的參數,沒有就是空數組
最后寫運行類
public class DynamicMain { private static DynamicProxy realRole = new DynamicRealRole(); // 被代理的類,也就是實現類 private static DynamicProxyRole dynamicProxyRole = new DynamicProxyRole(realRole); public static void main(String[] args) { // 通過JDK動態代理獲取被代理對象(實現類的對象)的代理對象,該代理類實現了指定的需要去代理的接口,也就是第2個參數 DynamicProxy dynamicProxyObj = (DynamicProxy) Proxy.newProxyInstance( realRole.getClass().getClassLoader(), // 被代理的類的加載器 realRole.getClass().getInterfaces(), // 被代理的類需要實現的接口,可以有多個 dynamicProxyRole // 必須是實現了 InvocationHandler 接口的類,invoke 方法里寫業務邏輯和代理方法 ); dynamicProxyObj.dosth(); } }
Proxy 類的作用是動態創建一個代理對象,也就是代理對象不需要我們自己寫。Proxy 提供了許多的方法,用的最多的是 newProxyInstance,注釋里也寫了:
其中第一個參數是被代理的類的加載器,傳入的目的是告訴 JDK 由哪個類加載器對生成的代理進行加載。其實就是真實的類(實現類)的對象的加載器。
第二個參數是代理類需要實現的接口,可以多個。其實就是接口 DynamicProxy,很好理解,在靜態代理模式中,我們就需要手動實現這個接口,來實現代理類。
第三個參數就是實現了InvocationHandler接口的類即可,原因是此類里有 invoke 方法,而通過 Proxy 的 newProxyInstance 方法生成的代理類去調用接口方法(dosth)時,對方法(dosth)的調用會自動委托給 InvocationHandler 接口的 invoke 方法,這樣也就實現了代理模式。
綜上,代理對象就實現了在程序運行時產生。進一步要知道,所有的 JDK 動態代理都會繼承 java.lang.reflect.Proxy,同時還會實現我們指定的接口(Proxy 的 newProxyInstance 第二個參數里的接口)。
看到這里,也確定,JDK 動態代理核心就是反射思想的應用,沒什么新鮮的東西。
缺陷
JDK 動態代理這種方式只能代理接口,這是其缺陷
基於 CGLib 實現
Java動態代理是基於接口實現的,如果對象沒有實現接口,那么可以用 CGLIB 類庫實現,它的原理是基於繼承實現代理類。代碼也不難
首先,寫一個類,其沒有實現接口,此時前面的 JDK 動態代理就無法使用了。
public class NoInterfaceReal { public void dosth() { System.out.println("do sth"); } }
其次,需要實現CGLIB 類庫提供的接口——MethodInterceptor
在這之前,先下載CGLib 包
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.10</version> </dependency>
然后實現其提供的接口——MethodInterceptor,關鍵方法是 intercept
public class CGLibProxy implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object ret = proxy.invokeSuper(obj, args); after(); return ret; } private void after() { System.out.println("after dosth"); } private void before() { System.out.println("before dosth"); } }
實現了 MethodInterceptor 接口后,后續生成的代理對象對 dosth 方法的調用會被轉發到 intercept 方法,自然也就實現了代理模式。
最后,通過 CGLIB 動態代理生成代理對象,就完成了代理模式,非常簡單
public class CGLibMain { public static void main(String[] args) { CGLibProxy cgLibProxy = new CGLibProxy(); NoInterfaceReal proxy = (NoInterfaceReal) Enhancer.create(NoInterfaceReal.class, cgLibProxy); proxy.dosth(); } }
通過CGLib 的Enhancer類 create 了一個代理對象,參數傳入需要被代理的類(可以不是接口),和實現了 MethodInterceptor 接口的類的對象即可。CGLib 就會為我們自動生成繼承了被代理的類的代理對象,通過代理對象調用 dosth 方法,其調用會被委托給第二個參數里的 intercept 方法。
綜上得知:
1、CGLib 底層是利用 asm 字節碼框架實現的,該框架可以在 Java 程序運行時對字節碼進行修改和動態生成,故它可以代理普通類,具體細節是通過繼承和重寫需要被代理的類(NoInterfaceReal)來實現。
2、CGLib 可以實現對方法的代理,即可以實現攔截(只代理)某個方法。
3、通過CGLib 的 Enhancer 類來create 代理對象。而對這個對象所有非final方法的調用都會委托給 MethodInterceptor 接口的 intercept,我們可以在該方法內部寫攔截代碼,最后在通過調用MethodProxy 對象的 invokeSuper() 方法,把調用轉發給真實對象
缺陷
無法對 final 類、或者 final 方法進行代理
代理模式的性能對比
直接搬運結論:CGLib 底層基於asm 框架實現,比 Java 反射性能好,但是比 JDK 動態代理稍微慢一些
代理模式的缺點
主要是性能問題,什么增加系統復雜度等都不是事兒。同等條件,用代理,肯定比不用代理要慢一些。
代理模式和裝飾器模式對比
實現方式上很像,但是目標不一樣,后者是為了給類(對象)增加新的功能,不改變API,前者除了這些作用,目標主要是為了使用中間人(代理角色)給本類(對象)減少負擔。
代理模式的應用
非常常見了,AOP非常典型,還有各種框架的攔截器機制,數據庫切換等工具。。。
歡迎關注
dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!