Java的三種代理模式簡述
一、代理模式是什麼
代理模式是一種設計模式,簡單說即是在不改變源碼的情況下,實現對目標對象的功能擴展。
比如有個歌手對象叫Singer,這個對象有一個唱歌方法叫sing()。
1 public class Singer{ 2 public void sing(){ 3 System.out.println("唱一首歌"); 4 } 5 }
假如你希望,通過你的某種方式生產出來的歌手對象,在唱歌前後還要想觀眾問好和答謝,也即對目標對象
Singer的sing方法進行功能擴展
。
1 public void sing(){ 2 System.out.println("向觀眾問好"); 3 System.out.println("唱一首歌"); 4 System.out.println("謝謝大傢"); 5 }
但是往往你又不能直接對源代碼進行修改
,可能是你希望原來的對象還保持原來的樣子,又或許你提供的隻是一個可插拔的插件,甚至你有可能都不知道你要對哪個目標對象進行擴展。這時就需要用到java的代理模式瞭。網上好多用生活中的經理人的例子來解釋“代理”,看似通俗易懂,但我覺得不適合程序員去理解。程序員應該從代碼的本質入手。
二、Java的三種代理模式
想要實現以上的需求有三種方式,這一部分我們隻看三種模式的代碼怎麼寫,先不涉及實現原理的部分。
1.靜態代理
1 public interface ISinger { 2 void sing(); 3 } 4 5 /** 6 * 目標對象實現瞭某一接口 7 */ 8 public class Singer implements ISinger{ 9 public void sing(){ 10 System.out.println("唱一首歌"); 11 } 12 } 13 14 /** 15 * 代理對象和目標對象實現相同的接口 16 */ 17 public class SingerProxy implements ISinger{ 18 // 接收目標對象,以便調用sing方法 19 private ISinger target; 20 public UserDaoProxy(ISinger target){ 21 this.target=target; 22 } 23 // 對目標對象的sing方法進行功能擴展 24 public void sing() { 25 System.out.println("向觀眾問好"); 26 target.sing(); 27 System.out.println("謝謝大傢"); 28 } 29 }
測試
1 /** 2 * 測試類 3 */ 4 public class Test { 5 public static void main(String[] args) { 6 //目標對象 7 ISinger target = new Singer(); 8 //代理對象 9 ISinger proxy = new SingerProxy(target); 10 //執行的是代理的方法 11 proxy.sing(); 12 } 13 }
總結:其實這裡做的事情無非就是,創建一個代理類SingerProxy,繼承瞭ISinger接口並實現瞭其中的方法。隻不過這種實現特意包含瞭目標對象的方法,正是這種特征使得看起來像是“擴展”瞭目標對象的方法。假使代理對象中隻是簡單地對sing方法做瞭另一種實現而沒有包含目標對象的方法,也就不能算作代理模式瞭。所以這裡的包含是關鍵。
缺點:這種實現方式很直觀也很簡單,但其缺點是代理對象必須提前寫出,如果接口層發生瞭變化,代理對象的代碼也要進行維護。如果能在運行時動態地寫出代理對象,不但減少瞭一大批代理類的代碼,也少瞭不斷維護的煩惱,不過運行時的效率必定受到影響。這種方式就是接下來的動態代理。
2.動態代理(也叫JDK代理)
跟靜態代理的前提一樣,依然是對Singer對象進行擴展
1 public interface ISinger { 2 void sing(); 3 } 4 5 /** 6 * 目標對象實現瞭某一接口 7 */ 8 public class Singer implements ISinger{ 9 public void sing(){ 10 System.out.println("唱一首歌"); 11 } 12 }
這回直接上測試,由於java底層封裝瞭實現細節(之後會詳細講),所以代碼非常簡單,格式也基本上固定。
調用Proxy類的靜態方法newProxyInstance即可,該方法會返回代理類對象
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
接收的三個參數依次為:
- ClassLoader loader:指定當前目標對象使用類加載器,寫法固定
- Class<?>[] interfaces:目標對象實現的接口的類型,寫法固定
- InvocationHandler h:事件處理接口,需傳入一個實現類,一般直接使用匿名內部類
測試代碼
1 public class Test{ 2 public static void main(String[] args) { 3 Singer target = new Singer(); 4 ISinger proxy = (ISinger) Proxy.newProxyInstance( 5 target.getClass().getClassLoader(), 6 target.getClass().getInterfaces(), 7 new InvocationHandler() { 8 @Override 9 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 10 System.out.println("向觀眾問好"); 11 //執行目標對象方法 12 Object returnValue = method.invoke(target, args); 13 System.out.println("謝謝大傢"); 14 return returnValue; 15 } 16 }); 17 proxy.sing(); 18 } 19 }
總結:以上代碼隻有標黃的部分是需要自己寫出,其餘部分全都是固定代碼。由於java封裝瞭newProxyInstance這個方法的實現細節,所以使用起來才能這麼方便,具體的底層原理將會在下一小節說明。
缺點:可以看出靜態代理和JDK代理有一個共同的缺點,就是目標對象必須實現一個或多個接口,加入沒有,則可以使用Cglib代理。
3.Cglib代理
前提條件:
- 需要引入cglib的jar文件,由於Spring的核心包中已經包括瞭Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
- 目標類不能為final
- 目標對象的方法如果為final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法
1 /** 2 * 目標對象,沒有實現任何接口 3 */ 4 public class Singer{ 5 6 public void sing() { 7 System.out.println("唱一首歌"); 8 } 9 }
1 /** 2 * Cglib子類代理工廠 3 */ 4 public class ProxyFactory implements MethodInterceptor{ 5 // 維護目標對象 6 private Object target; 7 8 public ProxyFactory(Object target) { 9 this.target = target; 10 } 11 12 // 給目標對象創建一個代理對象 13 public Object getProxyInstance(){ 14 //1.工具類 15 Enhancer en = new Enhancer(); 16 //2.設置父類 17 en.setSuperclass(target.getClass()); 18 //3.設置回調函數 19 en.setCallback(this); 20 //4.創建子類(代理對象) 21 return en.create(); 22 } 23 24 @Override 25 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 26 System.out.println("向觀眾問好"); 27 //執行目標對象的方法 28 Object returnValue = method.invoke(target, args); 29 System.out.println("謝謝大傢"); 30 return returnValue; 31 } 32 }
這裡的代碼也非常固定,隻有標黃部分是需要自己寫出
測試
1 /** 2 * 測試類 3 */ 4 public class Test{ 5 public static void main(String[] args){ 6 //目標對象 7 Singer target = new Singer(); 8 //代理對象 9 Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance(); 10 //執行代理對象的方法 11 proxy.sing(); 12 } 13 }
總結:三種代理模式各有優缺點和相應的適用范圍,主要看目標對象是否實現瞭接口。以Spring框架所選擇的代理模式舉例
- 在Spring的AOP編程中:
- 如果加入容器的目標對象有實現接口,用JDK代理
- 如果目標對象沒有實現接口,用Cglib代理
到此這篇關於Java的三種代理模式簡述的文章就介紹到這瞭,更多相關Java的三種代理模式內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!