Java動態代理的理解
主要寫一下動態代理模式
採用的InvocationHandler的方式,採用CgLib方式的看看後面能不能寫出來。
動態代理模式的優點
-
在不知道代理類的情況下便可以建立類並且呼叫方法。
/** 這裡幾個引數的含義 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);方法的解釋
這個方法是用來建立被代理的物件。解釋一下三個引數的含義
- 是為了得到被代理的物件的類載入器,類載入器保證了我們載入的類是同一個類(改天我會寫一篇關於JVM類載入的機制)
- 這個引數值得注意,是得到被代理類所有實現的介面,這一點特別重要(因為動態代理模式有兩種形式,一種是利用這篇文章的動態代理,還有一種是利用CgLib的方式進行動態代理),兩種方式的區別就在於前者只能對實現了統一介面的類動態代理。
- 第三個引數 我理解為利用反射的方式去呼叫被代理類的方法。
我們可以通過兩步來看到生成的代理類的原始碼
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虛擬機器》的第九章