理解java的三種代理模式

代理模式是什麼

代理模式是一種設計模式,簡單說便是在不改變源碼的狀況下,實現對目標對象功能擴展java

好比有個歌手對象叫Singer,這個對象有一個唱歌方法叫sing()。程序員

1 public class Singer{ 2 public void sing(){ 3 System.out.println("唱一首歌"); 4  } 5 }

假如你但願,經過你的某種方式生產出來的歌手對象,在唱歌先後還要想觀衆問好和答謝,也即對目標對象Singer的sing方法進行功能擴展。spring

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方法作了另外一種實現而沒有包含目標對象的方法,也就不能算做代理模式了。因此這裏的包含是關鍵。ide

  缺點:這種實現方式很直觀也很簡單,但其缺點是代理對象必須提早寫出,若是接口層發生了變化,代理對象的代碼也要進行維護。若是能在運行時動態地寫出代理對象,不但減小了一大批代理類的代碼,也少了不斷維護的煩惱,不過運行時的效率一定受到影響。這種方式就是接下來的動態代理。函數

 

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底層封裝了實現細節(以後會詳細講),因此代碼很是簡單,格式也基本上固定。post

調用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代理

 

下一篇博文將詳細講述動態代理和Cglib代理的底層原理,敬請期待。

相關文章
相關標籤/搜索