天天看點

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動态代理的思維模式與之前的一般模式是一樣的,也是面向接口進行編碼,建立代理類将具體類隐藏解耦,不同之處在于代理類的建立時機不同,動态代理需要在運作時因需實時建立。

1 package ceshi1;
2 public interface Iuser {
3   void eat(String s);
4 }      
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 }      
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 }      
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動态代理的實作。

(待續)