JDK和CGLIB生成動態代理類的區別


當一個對象(客戶端)不能或者不想直接引用另一個對象(目標對象),這時可以應用代理模式在這兩者之間構建一個橋梁--代理對象。

按照代理對象的創建時期不同,可以分為兩種:

靜態代理:事先寫好代理對象類,在程序發布前就已經存在了;

動態代理:應用程序發布后,通過動態創建代理對象。

靜態代理其實就是一個典型的代理模式實現,在代理類中包裝一個被代理對象,然后影響被代理對象的行為,比較簡單,代碼就不放了。

其中動態代理又可分為:JDK動態代理和CGLIB代理。

原創作品,可以轉載,但是請標注出處地址http://www.cnblogs.com/V1haoge/p/5860749.html

 

1、動態代理(Dynamic Proxy)
  代理分為靜態代理和動態代理,靜態代理是在編譯時就將接口、實現類、代理類一股腦兒全部手動完成,但如果我們需要很多的代理,每一個都這么手動的去創建實屬浪費時間,而且會有大量的重復代碼,此時我們就可以采用動態代理,動態代理可以在程序運行期間根據需要動態的創建代理類及其實例,來完成具體的功能。
  其實方法直接調用就可以完成功能,為什么還要加個代理呢?
  原因是采用代理模式可以有效的將具體的實現與調用方進行解耦,通過面向接口進行編碼完全將具體的實現隱藏在內部。
2、代理實現的一般模式
  其實代理的一般模式就是靜態代理的實現模式:首先創建一個接口(JDK代理都是面向接口的),然后創建具體實現類來實現這個接口,在創建一個代理類同樣實現這個接口,不同指出在於,具體實現類的方法中需要將接口中定義的方法的業務邏輯功能實現,而代理類中的方法只要調用具體類中的對應方法即可,這樣我們在需要使用接口中的某個方法的功能時直接調用代理類的方法即可,將具體的實現類隱藏在底層。
  第一步:定義總接口Iuser.java

1 package ceshi1; 2 public interface Iuser { 3 void eat(String s); 4 }

  第二步:創建具體實現類UserImpl.java

復制代碼
1 package ceshi1; 2 public class UserImpl implements Iuser { 3   @Override 4   public void eat(String s) { 5     System.out.println("我要吃"+s); 6   } 7 }
復制代碼

  第三步:創建代理類UserProxy.java

復制代碼
 1 package ceshi1;  2 public class UserProxy implements Iuser {  3   private Iuser user = new UserImpl();  4   @Override  5   public void eat(String s) {  6     System.out.println("靜態代理前置內容");  7     user.eat(s);  8     System.out.println("靜態代理后置內容");  9   } 10 }
復制代碼

  第四步:創建測試類ProxyTest.java

復制代碼
1 package ceshi1; 2 public class ProxyTest { 3   public static void main(String[] args) { 4     UserProxy proxy = new UserProxy(); 5     proxy.eat("蘋果"); 6   } 7 }
復制代碼

  運行結果:

1 靜態代理前置內容 2 我要吃蘋果 3 靜態代理后置內容

3、動態代理的實現
  動態代理的思維模式與之前的一般模式是一樣的,也是面向接口進行編碼,創建代理類將具體類隱藏解耦,不同之處在於代理類的創建時機不同,動態代理需要在運行時因需實時創建。
  第一步:定義總接口Iuser.java

1 package ceshi1; 2 public interface Iuser { 3   void eat(String s); 4 }

  第二步:創建具體實現類UserImpl.java

復制代碼
1 package ceshi1; 2 public class UserImpl implements Iuser { 3   @Override 4   public void eat(String s) { 5     System.out.println("我要吃"+s); 6   } 7 }
復制代碼

  第三步:創建實現InvocationHandler接口的代理類

復制代碼
 1 package ceshi1;  2 import java.lang.reflect.InvocationHandler;  3 import java.lang.reflect.Method;  4 public class DynamicProxy implements InvocationHandler {  5   private Object object;//用於接收具體實現類的實例對象  6   //使用帶參數的構造器來傳遞具體實現類的對象  7   public DynamicProxy(Object obj){  8     this.object = obj;  9   } 10   @Override 11   public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { 12     System.out.println("前置內容"); 13     method.invoke(object, args); 14     System.out.println("后置內容"); 15     return null; 16   } 17 }
復制代碼

  第四步:創建測試類ProxyTest.java

復制代碼
 1 package ceshi1;  2 import java.lang.reflect.InvocationHandler;  3 import java.lang.reflect.Proxy;  4 public class ProxyTest {  5   public static void main(String[] args) {  6     Iuser user = new UserImpl();  7     InvocationHandler h = new DynamicProxy(user);  8     Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);  9     proxy.eat("蘋果"); 10   } 11 }
復制代碼

  運行結果為:

1 動態代理前置內容 2 我要吃蘋果 3 動態代理后置內容

4、通過上面的動態代理實例我們來仔細分析研究一下動態代理的實現過程
(1)首先我要說的就是接口,為什么JDK的動態代理是基本接口實現的呢?
  因為通過使用接口指向實現類的實例的多態實現方式,可以有效的將具體的實現與調用之間解耦,便於后期修改與維護。
再具體的說就是我們在代理類中創建一個私有成員變量(private修飾),使用接口來指向實現類的對象(純種的多態體現,向上轉型的體現),然后在該代理類中的方法中使用這個創建的實例來調用實現類中的相應方法來完成業務邏輯功能。
這么說起來,我之前說的“將具體實現類完全隱藏”就不怎么正確了,可以改成,將具體實現類的細節向調用方完全隱藏(調用方調用的是代理類中的方法,而不是實現類中的方法)。
  這就是面向接口編程,利用java的多態特性,實現程序代碼的解耦。
(2)創建代理類的過程
  如果你了解靜態代理,那么你會發現動態代理的實現其實與靜態代理類似,都需要創建代理類,但是不同之處也很明顯,創建方式不同!
  不同之處體現在靜態代理我們知根知底,我們知道要對哪個接口、哪個實現類來創建代理類,所以我們在編譯前就直接實現與實現類相同的接口,直接在實現的方法中調用實現類中的相應(同名)方法即可;而動態代理不同,我們不知道它什么時候創建,也不知道要創建針對哪個接口、實現類的代理類(因為它是在運行時因需實時創建的)。
  雖然二者創建時機不同,創建方式也不相同,但是原理是相同的,不同之處僅僅是:靜態代理可以直接編碼創建,而動態代理是利用反射機制來抽象出代理類的創建過程。
  讓我們來分析一下之前的代碼來驗證一下上面的說辭:
    第一點:靜態代理需要實現與實現類相同的接口,而動態代理需要實現的是固定的Java提供的內置接口(一種專門提供來創建動態代理的接口)InvocationHandler接口,因為java在接口中提供了一個可以被自動調用的方法invoke,這個之后再說。
    第二點:private Object object;
        public UserProxy(Object obj){this.object = obj;}
  這幾行代碼與靜態代理之中在代理類中定義的接口指向具體實現類的實例的代碼異曲同工,通過這個構造器可以創建代理類的實例,創建的同時還能將具體實現類的實例與之綁定(object指的就是實現類的實例,這個實例需要在測試類中創建並作為參數來創建代理類的實例),實現了靜態代理類中private Iuser user = new UserImpl();一行代碼的作用相近,這里為什么不是相同,而是相近呢,主要就是因為靜態代理的那句代碼中包含的實現類的實例的創建,而動態代理中實現類的創建需要在測試類中完成,所以此處是相近。
    第三點:invoke(Object proxy, Method method, Object[] args)方法,該方法是InvocationHandler接口中定義的唯一方法,該方法在調用指定的具體方法時會自動調用。其參數為:代理實例、調用的方法、方法的參數列表
  在這個方法中我們定義了幾乎和靜態代理相同的內容,僅僅是在方法的調用上不同,不同的原因與之前分析的一樣(創建時機的不同,創建的方式的不同,即反射),Method類是反射機制中一個重要的類,用於封裝方法,該類中有一個方法那就是invoke(Object object,Object...args)方法,其參數分別表示:所調用方法所屬的類的對象和方法的參數列表,這里的參數列表正是從測試類中傳遞到代理類中的invoke方法三個參數中最后一個參數(調用方法的參數列表)中,在傳遞到method的invoke方法中的第二個參數中的(此處有點啰嗦)。
    第四點:測試類中的異同
  靜態代理中我們測試類中直接創建代理類的對象,使用代理類的對象來調用其方法即可,若是別的接口(這里指的是別的調用方)要調用Iuser的方法,也可以使用此法
動態代理中要復雜的多,首先我們要將之前提到的實現類的實例創建(補充完整),然后利用這個實例作為參數,調用代理來的帶參構造器來創建“代理類實例對象”,這里加引號的原因是因為它並不是真正的代理類的實例對象,而是創建真正代理類實例的一個參數,這個實現了InvocationHandler接口的類嚴格意義上來說並不是代理類,我們可以將其看作是創建代理類的必備中間環節,這是一個調用處理器,也就是處理方法調用的一個類,不是真正意義上的代理類,可以這么說:創建一個方法調用處理器實例。
  下面才是真正的代理類實例的創建,之前創建的”代理類實例對象“僅僅是一個參數
    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
  這里使用了動態代理所依賴的第二個重要類Proxy,此處使用了其靜態方法來創建一個代理實例,其參數分別是:類加載器(可為父類的類加載器)、接口數組、方法調用處理器實例
  這里同樣使用了多態,使用接口指向代理類的實例,最后會用該實例來進行具體方法的調用即可。

另一篇參考地址https://www.cnblogs.com/binyue/p/4519652.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM