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

原創做品,能夠轉載,可是請標註出處地址http://www.cnblogs.com/V1haoge/p/5860749.htmlhtml

 

一、動態代理(Dynamic Proxy)
  代理分爲靜態代理和動態代理,靜態代理是在編譯時就將接口、實現類、代理類一古腦兒所有手動完成,但若是咱們須要不少的代理,每個都這麼手動的去建立實屬浪費時間,並且會有大量的重複代碼,此時咱們就能夠採用動態代理,動態代理能夠在程序運行期間根據須要動態的建立代理類及其實例,來完成具體的功能。
  其實方法直接調用就能夠完成功能,爲何還要加個代理呢?
  緣由是採用代理模式能夠有效的將具體的實現與調用方進行解耦,經過面向接口進行編碼徹底將具體的實現隱藏在內部。
二、代理實現的通常模式
  其實代理的通常模式就是靜態代理的實現模式:首先建立一個接口(JDK代理都是面向接口的),而後建立具體實現類來實現這個接口,在建立一個代理類一樣實現這個接口,不一樣之處在於,具體實現類的方法中須要將接口中定義的方法的業務邏輯功能實現,而代理類中的方法只要調用具體類中的對應方法便可,這樣咱們在須要使用接口中的某個方法的功能時直接調用代理類的方法便可,將具體的實現類隱藏在底層。
  第一步:定義總接口Iuser.javajava

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.javaide

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 靜態代理後置內容

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

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接口的代理類spa

 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 動態代理後置內容

四、經過上面的動態代理實例咱們來仔細分析研究一下動態代理的實現過程
(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方法生成代理實例(生成實例時須要提供類加載器,咱們可使用接口類的加載器便可)

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

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

五、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 }

  執行結果:

預處理
我要吃葡萄
後處理

六、cglib動態代理分析

  經過代碼實例咱們是能夠看出一點,其實在編碼上,cglib動態代理和JDK動態代理的編碼邏輯相似,都是實現一個接口,再使用另一個提供的類來建立代理實例。這爲咱們編碼和記憶提供了便利,可是也容易帶來混淆。

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

(待續)

相關文章
相關標籤/搜索