什么是代理模式?
代理模式的定義:代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。
舉個例子來說明:假如說我現在想買一輛二手車,雖然我可以自己去找車源,做質量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什么我還要額外做這么多事呢?於是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負責選擇自己喜歡的車,然后付錢就可以了。用圖表示如下:
為什么要用代理模式?
- 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現相同的接口。
- 開閉原則,增加功能:代理類除了是客戶類和委托類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委托類的功能,這樣做我們只需要修改代理類而不需要再修改委托類,符合代碼設計的開閉原則。代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后對返回結果的處理等。代理類本身並不真正實現服務,而是同過調用委托類的相關方法,來提供特定的服務。真正的業務功能還是由委托類來實現,但是可以在業務功能執行的前后加入一些公共的服務。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委托類。
有哪幾種代理模式?
我們有多種不同的方式來實現代理。如果按照代理創建的時期來進行分類的話, 可以分為兩種:靜態代理、動態代理。靜態代理是由程序員創建或特定工具自動生成源代碼,在對其編譯。在程序員運行之前,代理類.class文件就已經被創建了。動態代理是在程序運行時通過反射機制動態創建的。
1.靜態代理
第一步:創建服務類接口
1 package main.java.proxy; 2 3 /** 4 * @Auther: dan gao 5 * @Description: 6 * @Date: 22:40 2018/1/9 0009 7 */ 8 public interface BuyHouse { 9 void buyHosue(); 10 }
第二步:實現服務接口
1 import main.java.proxy.BuyHouse; 2 3 /** 4 * @Auther: dan gao 5 * @Description: 6 * @Date: 22:42 2018/1/9 0009 7 */ 8 public class BuyHouseImpl implements BuyHouse { 9 10 @Override 11 public void buyHosue() { 12 System.out.println("我要買房"); 13 } 14 }
第三步:創建代理類
1 package main.java.proxy.impl; 2 3 import main.java.proxy.BuyHouse; 4 5 /** 6 * @Auther: dan gao 7 * @Description: 8 * @Date: 22:43 2018/1/9 0009 9 */ 10 public class BuyHouseProxy implements BuyHouse { 11 12 private BuyHouse buyHouse; 13 14 public BuyHouseProxy(final BuyHouse buyHouse) { 15 this.buyHouse = buyHouse; 16 } 17 18 @Override 19 public void buyHosue() { 20 System.out.println("買房前准備"); 21 buyHouse.buyHosue(); 22 System.out.println("買房后裝修"); 23 24 } 25 }
第四步:編寫測試類
import main.java.proxy.impl.BuyHouseImpl; import main.java.proxy.impl.BuyHouseProxy; /** * @Auther: dan gao * @Description: * @Date: 22:43 2018/1/9 0009 */ public class ProxyTest { public static void main(String[] args) { BuyHouse buyHouse = new BuyHouseImpl(); buyHouse.buyHosue(); BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse); buyHouseProxy.buyHosue(); } }
靜態代理總結:
優點:可以做到在符合開閉原則的情況下對目標對象進行功能擴展。
缺點:我們得為每一個服務都得創建代理類,工作量太大,不易管理。同時接口一旦發生改變,代理類也得相應修改。
2.動態代理
在動態代理中我們不再需要再手動的創建代理類,我們只需要編寫一個動態處理器就可以了。真正的代理對象由JDK再運行時為我們動態的來創建。
第一步:編寫動態處理器
1 package main.java.proxy.impl; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 /** 7 * @Auther: dan gao 8 * @Description: 9 * @Date: 20:34 2018/1/12 0012 10 */ 11 public class DynamicProxyHandler implements InvocationHandler { 12 13 private Object object; 14 15 public DynamicProxyHandler(final Object object) { 16 this.object = object; 17 } 18 19 @Override 20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 21 System.out.println("買房前准備"); 22 Object result = method.invoke(object, args); 23 System.out.println("買房后裝修"); 24 return result; 25 } 26 }
第二步:編寫測試類
1 package main.java.proxy.test; 2 3 import main.java.proxy.BuyHouse; 4 import main.java.proxy.impl.BuyHouseImpl; 5 import main.java.proxy.impl.DynamicProxyHandler; 6 7 import java.lang.reflect.Proxy; 8 9 /** 10 * @Auther: dan gao 11 * @Description: 12 * @Date: 20:38 2018/1/12 0012 13 */ 14 public class DynamicProxyTest { 15 public static void main(String[] args) { 16 BuyHouse buyHouse = new BuyHouseImpl(); 17 BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new 18 Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse)); 19 proxyBuyHouse.buyHosue(); 20 } 21 }
注意Proxy.newProxyInstance()方法接受三個參數:
ClassLoader loader
:指定當前目標對象使用的類加載器,獲取加載器的方法是固定的Class<?>[] interfaces
:指定目標對象實現的接口的類型,使用泛型方式確認類型InvocationHandler:
指定
動態處理器,
執行目標對象的方法時,會觸發事件處理器的方法
動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動態生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫Proxy。Java的繼承機制注定了這些動態代理類們無法實現對class的動態代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支持class動態代理會更美好。接口和類的划分,本就不是很明顯,只是到了Java中才變得如此的細化。如果只從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實現對抽象類的動態代理,相信也有其內在的價值。此外,還有一些歷史遺留的類,它們將因為沒有實現任何接口而從此與動態代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美並不等於不偉大,偉大是一種本質,Java動態代理就是佐例。
3.CGLIB代理
JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因為采用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
第一步:創建CGLIB代理類
1 package dan.proxy.impl; 2 3 import net.sf.cglib.proxy.Enhancer; 4 import net.sf.cglib.proxy.MethodInterceptor; 5 import net.sf.cglib.proxy.MethodProxy; 6 7 import java.lang.reflect.Method; 8 9 /** 10 * @Auther: dan gao 11 * @Description: 12 * @Date: 20:38 2018/1/16 0016 13 */ 14 public class CglibProxy implements MethodInterceptor { 15 private Object target; 16 public Object getInstance(final Object target) { 17 this.target = target; 18 Enhancer enhancer = new Enhancer(); 19 enhancer.setSuperclass(this.target.getClass()); 20 enhancer.setCallback(this); 21 return enhancer.create(); 22 } 23 24 public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 25 System.out.println("買房前准備"); 26 Object result = methodProxy.invoke(object, args); 27 System.out.println("買房后裝修"); 28 return result; 29 } 30 }
第二步:創建測試類
1 package dan.proxy.test; 2 3 import dan.proxy.BuyHouse; 4 import dan.proxy.impl.BuyHouseImpl; 5 import dan.proxy.impl.CglibProxy; 6 7 /** 8 * @Auther: dan gao 9 * @Description: 10 * @Date: 20:52 2018/1/16 0016 11 */ 12 public class CglibProxyTest { 13 public static void main(String[] args){ 14 BuyHouse buyHouse = new BuyHouseImpl(); 15 CglibProxy cglibProxy = new CglibProxy(); 16 BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse); 17 buyHouseCglibProxy.buyHosue(); 18 } 19 }
CGLIB代理總結: CGLIB創建的動態代理對象比JDK創建的動態代理對象的性能更高,但是CGLIB創建代理對象時所花費的時間卻比JDK多得多。所以對於單例的對象,因為無需頻繁創建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是采用動態創建子類的方法,對於final修飾的方法無法進行代理。