1. 程式人生 > >Java動態代理的理解

Java動態代理的理解

主要寫一下動態代理模式

採用的InvocationHandler的方式,採用CgLib方式的看看後面能不能寫出來。


動態代理模式的優點

  1. 在不知道代理類的情況下便可以建立類並且呼叫方法。

       /**
       這裡幾個引數的含義
       proxy 是真正的生成的被代理的物件
       proxyT是這個真正的代理類的物件
       這裡便可以看出動態代理的優點:在不知道被代理的物件是什麼以及要實現的介面是什麼樣的情況下,我們便可以構造出了一個這樣動態代理類。
       在每個方法前面輸出 ”冀海川真帥”。
       */
    		public class proxyGenerate<T> {
       				T proxy;
       				public T bind(T proxyB){
       					this.proxy = proxyB;
       					return  Proxy.newInstance(proxy.getClass().getClassLoader(),proxy.getClass().getInterfaces(),methodInvocationHadler);
       				}
       				public Object methodInvocationHandler implements implements InvocationHandler thorws Exception{
       						@Override 
       						public Object invoke(Object proxyT,Method method,Object[] args){
       							System.out.println("冀海川真的帥");
       							return method.invoke(proxy,args);
       						}
       				}
       }
       
    

對上面的方法進行解釋:

首先是 InvocationHandler,下面是它的原始碼:

    public interface InvocationHandler {
		    public Object invoke(Object proxy, Method method, Object[] args)
		        throws Throwable;
	}

這是一個介面,但是這個介面只有一個方法,這個介面主要的作用是利用代理類去呼叫被代理的物件的方法。
通過的是

return method.invoke(proxy,args);

這一樣程式碼來實現的,同理我們也可以在上面新增我們自己定義的方法,這很像Spring當中的AOP程式設計中的切點與切面的問題。

下面是對 Proxy.newInstance(getClass().getClassLoader(),getClass().getInterfaces(),InvocationHandler);方法的解釋

這個方法是用來建立被代理的物件。解釋一下三個引數的含義

  1. 是為了得到被代理的物件的類載入器,類載入器保證了我們載入的類是同一個類(改天我會寫一篇關於JVM類載入的機制)
  2. 這個引數值得注意,是得到被代理類所有實現的介面,這一點特別重要(因為動態代理模式有兩種形式,一種是利用這篇文章的動態代理,還有一種是利用CgLib的方式進行動態代理),兩種方式的區別就在於前者只能對實現了統一介面的類動態代理。
  3. 第三個引數 我理解為利用反射的方式去呼叫被代理類的方法。

我們可以通過兩步來看到生成的代理類的原始碼

public class Test {
	public static void main(String[] args){
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//1
		Person p = new Person();
		Hello test = new proxyGenerate<Hello>().bind(p);
	    System.out.println(test.getClass());

		
		test.sayHello();
	}
}

標1的地方是
第一步:這一步的目的是為了通過Java虛擬機器寫入一個屬性,讓虛擬機器中儲存生成的代理類。這裡要注意的是,要在自己的目錄下建立 com/sun/proxy/$Proxy.class這個檔案
在這裡插入圖片描述
具體的位置就像面的圖一樣。

第二步:就是對剛才生成的類進行反編譯,這裡我是用的jd-gui-1.4.0,我不知道為什麼在DOS命令裡面生成的javap -c 這個生成的命令是JAVA虛擬機器的位元組碼。雖然不是二進位制了但是位元組碼也是看起來有點吃力。反編譯以後的原始碼為


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


public final class $Proxy0
  extends Proxy
  implements Hello
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    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 void sayHello()
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  --------------------------------------------------------------------
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
        --------------------------------------------------------------------
      m3 = Class.forName("����JVM��������������������������.Hello").getMethod("sayHello", new Class[0]);
        --------------------------------------------------------------------
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

這裡我擷取的程式碼並不是全部 還有toString()等方法,因為這些方法在所有類中都存在就不介紹了
大概解釋全部的程式碼
首先是這個Proxy0這個類繼承了Proxy 並且在建構函式中傳遞了一個InvocationHandler這樣的一個引數:

這就意味著呼叫被代理類的方法是由Proxy這個類實現的。

用 --------------------------------------------------------------------標出來的程式碼需要注意:

this.h.invoke(this, m3, null);

這個就很明顯的解釋了h是繼承自Proxy類的InvocationHandler ,直接呼叫InvocationHandler的invoke的方法,對應於理類的sayHello()。m3對應的方法是

    --------------------------------------------------------------------
  m3 = Class.forName("����JVM��������������������������.Hello").getMethod("sayHello", new Class[0]);
    --------------------------------------------------------------------

就是藉口當中的sayHello方法。

最麻煩的部分寫完了下面的就是測試了。
首先寫一個介面 Hello

public interface Hello {
	public void sayHello();
}

然後定義一個被代理類Person

public class Person implements Hello{
	public void sayHello(){
		System.out.println("Hello,jhc");
	}
}

最後是一個測試類前面也寫了再寫一遍

public class Test {
	public static void main(String[] args){
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		Person p = new Person();
		Hello test = new proxyGenerate<Hello>().bind(p);
	    System.out.println(test.getClass());

		
		test.sayHello();
	}
}

最後形成的目錄結構是這樣的:

在這裡插入圖片描述

最後輸出的結果為:
在這裡插入圖片描述

主要參考的是《深入理解JAVA虛擬機器》的第九章