java靜態代理與動態代理簡單分析


原創作品,可以轉載,但是請標注出處地址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、JDK動態代理的實現
  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 }

  第三步:創建實現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,此處使用了其靜態方法來創建一個代理實例,其參數分別是:類加載器(可為父類的類加載器)、接口數組、方法調用處理器實例
  這里同樣使用了多態,使用接口指向代理類的實例,最后會用該實例來進行具體方法的調用即可。

(3)InvocationHandler

  InvocationHandler是JDK中提供的專門用於實現基於接口的動態代理的接口,主要用於進行方法調用模塊,而代理類和實例的生成需要借助Proxy類完成。

  每個代理類的實例的調用處理器都是實現該接口實現的,而且是必備的,即每個動態代理實例的實現都必須擁有實現該接口的調用處理器,也可以這么說,每個動態代理實例都對應一個調用處理器。

  這里要區分兩個概念,代理類和代理實例,調用處理器是在創建代理實例的時候才與其關聯起來的,所以它與代理實例是一一對應的,而不是代理類。

(4)Proxy

  Proxy類是JDK提供的用於生成動態代理類和其實例的類。

  我們可以通過Proxy中的靜態方法getProxyClass來生成代理類,需要的參數為類加載器和接口列表(數組),然后再通過反射調用代理類的構造器來生成代理實例,需要以一個InvocationHandler作為參數(體現出方法調用是與實例相關的,而非類)。

1     InvocationHandler handler = new MyInvocationHandler(...);
2     Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
3     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);

  我們也可以直接通過Proxy中的靜態方法newProxyInstance方法來直接生產代理實例,需要提供參數為上面的三個參數,即類加載器,接口數組,InvocationHandler。

1     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);

(5)、總結

  我們總結下JDK動態代理的實現步驟:

    第一步:創建接口,JDK動態代理基於接口實現,所以接口必不可少(准備工作)

    第二步:實現InvocationHandler接口,重寫invoke方法(准備工作)

    第三步:調用Proxy的靜態方法newProxyInstance方法生成代理實例(生成實例時需要提供類加載器,我們可以使用接口類的加載器即可)

    第四步:使用新生成的代理實例調用某個方法實現功能。

  我們的動態代理實現過程中根本沒有涉及到真實類實例。

5、Cglib動態代理的實現

  JDK動態代理擁有局限性,那就是必須面向接口編程,沒有接口就無法實現代理,我們也不可能為了代理而為每個需要實現代理的類強行添加毫無意義的接口,這時我們需要Cglib,這種依靠繼承來實現動態代理的方式,不再要求我們必須要有接口。

  第一步:添加Cglib的Maven依賴

1 <dependency>
2    <groupId>cglib</groupId>
3    <artifactId>cglib</artifactId>
4    <version>3.1</version>
5 </dependency>    

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

1 public class User {
2     public void eat(String s){
3         System.out.println("我要吃" + s);
4     }
5 }

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

 1 import net.sf.cglib.proxy.MethodInterceptor;
 2 import net.sf.cglib.proxy.MethodProxy;
 3 
 4 import java.lang.reflect.Method;
 5 
 6 public class UserInterceptor implements MethodInterceptor {
 7 
 8     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
 9         System.out.println("預處理");
10         Object object =  methodProxy.invokeSuper(o,objects);
11         System.out.println("后處理");
12         return object;
13     }
14 
15 }

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

 1 import net.sf.cglib.proxy.Enhancer;
 2 
 3 public class ProxyTest {
 4     public static void main(String[] args){
 5         Enhancer enchancer = new Enhancer();//字節碼增強器
 6         enchancer.setSuperclass(User.class);//設置被代理類為父類
 7         enchancer.setCallback(new UserInterceptor());//設置回調
 8         User user = (User)enchancer.create();//創建代理實例
 9         user.eat("葡萄");
10     }
11 }

  執行結果:

預處理
我要吃葡萄
后處理

6、cglib動態代理分析

  通過代碼實例我們是可以看出一點,其實在編碼上,cglib動態代理和JDK動態代理的編碼邏輯類似,都是實現一個接口,再使用另外一個提供的類來創建代理實例。這為我們編碼和記憶提供了便利,但是也容易帶來混淆。

  我們有必要仔細分析下Cglib動態代理的實現。

(待續)


免責聲明!

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



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