詳解java動態代理機制以及使用場景(一)

提及java動態代理,在我剛開始學java時對這項技術也是十分困惑,明明能夠直接調通的對象方法爲何還要使用動態代理?隨着學習的不斷深刻和工做經驗的積累,慢慢的體會並理解了java動態代理機制。昨天再給公司新同事作技術培訓時有同窗就對動態代理產生了疑問,我這裏梳理一遍一併記錄一下,方便你們查看對本身也是加深記憶。java

(1)什麼是代理?編程

大道理上講代理是一種軟件設計模式,目的地但願能作到代碼重用。具體上講,代理這種設計模式是經過不直接訪問被代理對象的方式,而訪問被代理對象的方法。這個就比如 商戶---->明星經紀人(代理)---->明星這種模式。咱們能夠不經過直接與明星對話的狀況下,而經過明星經紀人(代理)與其產生間接對話。設計模式


(2)什麼狀況下使用代理?api

(1)設計模式中有一個設計原則是開閉原則,是說對修改關閉對擴展開放,咱們在工做中有時會接手不少前人的代碼,裏面代碼邏輯讓人摸不着頭腦(sometimes the code is really like shit),這時就很難去下手修改代碼,那麼這時咱們就能夠經過代理對類進行加強。框架

(2)咱們在使用RPC框架的時候,框架自己並不能提早知道各個業務方要調用哪些接口的哪些方法 。那麼這個時候,就可用經過動態代理的方式來創建一箇中間人給客戶端使用,也方便框架進行搭建邏輯,某種程度上也是客戶端代碼和框架鬆耦合的一種表現。ide

(3)Spring的AOP機制就是採用動態代理的機制來實現切面編程。學習


(3)靜態代理和動態代理測試

咱們根據加載被代理類的時機不一樣,將代理分爲靜態代理和動態代理。若是咱們在代碼編譯時就肯定了被代理的類是哪個,那麼就能夠直接使用靜態代理;若是不能肯定,那麼可使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,好比RPC框架和Spring AOP機制。ui


(4)靜態代理this

咱們先建立一個接口,遺憾的是java api代理機制求被代理類必需要實現某個接口,對於靜態代理方式代理類也要實現和被代理類相同的接口;對於動態代理代理類則不須要顯示的實現被代理類所實現的接口。
/**
 * 頂層接口
 * @author yujie.wang
 *
 */
public interface Person {
    public void sayHello(String content, int age);
    public void sayGoodBye(boolean seeAgin, double time);
}

/**
 * 須要被代理的類 實現了一個接口Person
 * @author yujie.wang
 *
 */
public class Student implements Person{
 
    @Override
    public void sayHello(String content, int age) {
        // TODO Auto-generated method stub
        System.out.println("student say hello" + content + " "+ age);
    }
 
    @Override
    public void sayGoodBye(boolean seeAgin, double time) {
        // TODO Auto-generated method stub
        System.out.println("student sayGoodBye " + time + " "+ seeAgin);
    }
 
}

/**
 * 靜態代理,這個代理類也必需要實現和被代理類相同的Person接口
 * @author yujie.wang
 *
 */
public class ProxyTest implements Person{
    
    private Person o;
    
    public ProxyTest(Person o){
        this.o = o;
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //s爲被代理的對象,某些狀況下 咱們不但願修改已有的代碼,咱們採用代理來間接訪問
        Student s = new Student();
        //建立代理類對象
        ProxyTest proxy = new ProxyTest(s);
        //調用代理類對象的方法
        proxy.sayHello("welcome to java", 20);
        System.out.println("******");
        //調用代理類對象的方法
        proxy.sayGoodBye(true, 100);
 
    }
 
    @Override
    public void sayHello(String content, int age) {
        // TODO Auto-generated method stub
        System.out.println("ProxyTest sayHello begin");
        //在代理類的方法中 間接訪問被代理對象的方法
        o.sayHello(content, age);
        System.out.println("ProxyTest sayHello end");
    }
 
    @Override
    public void sayGoodBye(boolean seeAgin, double time) {
        // TODO Auto-generated method stub
        System.out.println("ProxyTest sayHello begin");
        //在代理類的方法中 間接訪問被代理對象的方法
        o.sayGoodBye(seeAgin, time);
        System.out.println("ProxyTest sayHello end");
    }
 
}

測試代碼輸出:

ProxyTest sayHello begin
student say hellowelcome to java 20
ProxyTest sayHello end
******
ProxyTest sayHello begin
student sayGoodBye 100.0 true
ProxyTest sayHello end

 

靜態代理看起來是比較簡單的,沒有什麼問題只不過是在代理類中引入了被代理類的對象而已。

那麼接下來咱們看看動態代理。

(5)動態代理

咱們先直接上動態代理的代碼,以後再分析代碼的行爲,上面的Person接口和Student被代理類保持不變。

/**
 * 動態代理,動態代理類不要顯示的實現被代理類所實現的接口
 * @author yujie.wang
 *
 */
public class MyInvocationHandler implements InvocationHandler{
    
    private Object object;
    
    public MyInvocationHandler(Object object){
        this.object = object;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("MyInvocationHandler invoke begin");
        System.out.println("proxy: "+ proxy.getClass().getName());
        System.out.println("method: "+ method.getName());
        for(Object o : args){
            System.out.println("arg: "+ o);
        }
        //經過反射調用 被代理類的方法
        method.invoke(object, args);
        System.out.println("MyInvocationHandler invoke end");
        return null;
    }
    
    public static void main(String [] args){
        //建立須要被代理的類
        Student s = new Student();
        //這一句是生成代理類的class文件,前提是你須要在工程根目錄下建立com/sun/proxy目錄,否則會報找不到路徑的io異常
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //得到加載被代理類的 類加載器
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        //指明被代理類實現的接口
        Class<?>[] interfaces = s.getClass().getInterfaces();
        // 建立被代理類的委託類,以後想要調用被代理類的方法時,都會委託給這個類的

        invoke(Object proxy, Method method, Object[] args)方法
        MyInvocationHandler h = new MyInvocationHandler(s);
        //生成代理類
        Person proxy = (Person)Proxy.newProxyInstance(loader, interfaces, h);
        //經過代理類調用 被代理類的方法
        proxy.sayHello("yujie.wang", 20);
        proxy.sayGoodBye(true, 100);
        System.out.println("end");
    }
 
}

運行測試代碼輸出以下結果:

MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayHello
arg: yujie.wang
arg: 20
student say helloyujie.wang 20
MyInvocationHandler invoke end
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayGoodBye
arg: true
arg: 100.0
student sayGoodBye 100.0 true
MyInvocationHandler invoke end
end

仔細分析上面的動態代理實現代碼,咱們看到這裏涉及到java反射包下的一個接口InvocationHandler和一個類Proxy。

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}

這個接口只有一個invoke方法,咱們在經過代理類調用被代理類的方法時,最終都會委託給這個invoke方法執行,

        //經過代理類調用 被代理類的方法
        proxy.sayHello("yujie.wang", 20);
        proxy.sayGoodBye(true, 100);

因此咱們就能夠在這個invoke方法中對被代理類進行加強或作一些其餘操做。

Proxy類的public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法內部經過拼接字節碼的方式來建立代理類,後面我會反編譯出它所建立的代理類看看內容。

咱們看這個方法的三個參數:

ClassLoader loader:指定一個動態加載代理類的類加載器

Class<?>[] interfaces:指明被代理類實現的接口,以後咱們經過拼接字節碼生成的類才能知道調用哪些方法。

InvocationHandler h:這是一個方法委託類,咱們經過代理調用被代理類的方法時,就能夠將方法名和方法參數都委託給這個委託類。

你看咱們如今有了類加載器、類實現的接口、要調用方法的方法名和參數,那麼咱們就能夠作不少事情了。

(6)反編譯Proxy.newProxyInstance所建立的代理類

//這一句是生成代理類的class文件,前提是你須要在工程根目錄下建立com/sun/proxy目錄,否則會報找不到路徑的io異常
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

咱們在代碼中加入上述代碼,代碼就會保存生成的代理類,名稱爲$Proxy0.class

經過jd-gui反編譯代碼以下,其中註釋是我加上去的:

package com.sun.proxy;
 
import com.yujie.proxy.dynamic.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
*代理類也實現了Person接口,看起來和靜態代理的方式也會同樣的
*同時代理類也繼承了Proxy類
*/
public final class $Proxy0 extends Proxy implements Person{
  private static Method m4;
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;
 
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws
  {
    super(paramInvocationHandler);
  }
   //實現了Person接口的方法,這就是咱們調用這個方法Proxy.newProxyInstance必須提供第二個參數的做用
  public final void sayGoodBye(boolean paramBoolean, double paramDouble)
    throws
  {
    try
    {
    // 咱們看到經過調用代理類的方法時,最終方法都會委託給InvocationHandler實現類的invoke方法
    // m4爲代理類經過反射得到的Method
      this.h.invoke(this, m4, new Object[] { Boolean.valueOf(paramBoolean), Double.valueOf(paramDouble) });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  public final boolean equals(Object paramObject)
    throws
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  public final int hashCode()
    throws
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  //實現了Person接口的方法,這就是咱們調用這個方法Proxy.newProxyInstance必須提供第二個參數的做用
  public final void sayHello(String paramString, int paramInt)
    throws
  {
    try
    {
      // 咱們看到經過調用代理類的方法時,最終方法都會委託給InvocationHandler實現類的invoke方法
      // m4爲代理類經過反射得到的Method
      this.h.invoke(this, m3, new Object[] { paramString, Integer.valueOf(paramInt) });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  public final String toString()
    throws
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
 
  static
  {
    try
    {//代理類經過反射 得到的接口方法Method
      m4 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayGoodBye", new Class[] { Boolean.TYPE, Double.TYPE });
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      //代理類經過反射 得到的接口方法Method
      m3 = Class.forName("com.yujie.proxy.dynamic.Person").getMethod("sayHello", new Class[] { Class.forName("java.lang.String"), Integer.TYPE });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

總結一下:

jdk的代理讓咱們在不直接訪問某些對象的狀況下,經過代理機制也能夠訪問被代理對象的方法,這種技術能夠應用在不少地方好比RPC框架,Spring AOP機制,可是咱們看到jdk的代理機制必需要求被代理類實現某個方法,這樣在生成代理類的時候才能知道從新那些方法。這樣一個沒有實現任何接口的類就沒法經過jdk的代理機制進行代理,固然解決方法是使用cglib的代理機制進行代理。  

相關文章
相關標籤/搜索