這篇文章解決三個問題:
-
What 動態代理是什麼
-
How 動態代理怎麼用
-
Why 動態代理的原理
動態代理是代理模式的一種具體實現,是指在程式執行期間,動態的生成目標物件的代理類(直接載入在記憶體中的位元組碼檔案),實現對目標物件所有方法的增強。通過這種方式,我們可以在不改變(或無法改變)目標物件原始碼的情況下,對目標物件的方法執行前後進行干預。
動態代理怎麼用?
首先,準備好我們需要代理的類和介面,因為JDK的動態代理是基於介面實現的,所以被代理的物件必須要有介面。
/**
* SaySomething介面
*/
public interface SaySomething {
public void sayHello();
public void sayBye();
}
/**
* SaySomething的實現類
*/
public class SaySomethingImpl implements SaySomething {
按照動態代理的用法,需要自定義一個處理器,用來編寫自定義邏輯,實現對被代理物件的增強。
自定義的處理器需要滿足以下要求:
-
需要實現InvocationHandler,重寫invoke方法,在invoke方法中通過加入自定義邏輯,實現對目標物件的增強。
-
需要持有一個成員變數,成員變數的是被代理物件的例項,通過構造引數傳入。(用來支援反射呼叫被代理物件的方法)
-
需要提供一個引數為被代理物件介面類的有參構造。(用來支援反射呼叫被代理物件的方法)
/**
* 自定義的處理器,用來編寫自定義邏輯,實現對被代理物件的增強
*/
public class CustomHandler implements InvocationHandler {
//需要有一個成員變數,成員變數為被代理物件,通過構造引數傳入,用來支援方法的反射呼叫。
private SaySomething obj;
//需要有一個有參構造,通過建構函式將被代理物件的例項傳入,用來支援方法的反射呼叫
public CustomHandler(SaySomething obj) {
this.obj = obj;
}
/**
* proxy:動態生成的代理類物件com.sun.proxy.$Proxy0
* method:被代理物件的真實的方法的Method物件
* args:呼叫方法時的入參
*/
這樣我們就完成了自定義處理器的編寫,同時在invoke方法中實現對了代理物件方法的增強,被代理類的所有方法的執行都會執行我們自定義的邏輯。
接下來,需要通過Proxy,newProxyInstance()方法來生成代理物件的例項,並進行方法呼叫測試。
public class JdkProxyTest {
public static void main(String[] args) {
//將生成的代理物件的位元組碼檔案 儲存到硬碟
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//被代理物件的例項
SaySomething obj = new SaySomethingImpl();
//通過建構函式,傳入被代理物件的例項,生成處理器的例項
InvocationHandler handler = new CustomHandler(obj);
//通過Proxy.newProxyInstance方法,傳入被代理物件Class物件、處理器例項,生成代理物件例項
SaySomething proxyInstance = (SaySomething) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
new Class[]{SaySomething.class}, handler);
//呼叫生成的代理物件的sayHello方法
proxyInstance.sayHello();
System.out.println("===================分割線==================");
//呼叫生成的代理物件的sayBye方法
proxyInstance.sayBye();
}
}
執行main方法,檢視控制檯,大功告成。至此,我們已經完整的完成了一次動態代理的使用。
動態代理的原理
生成的proxyInstance物件到底是什麼,為什麼呼叫它的sayHello方法會執行CustomerHandler的invoke方法呢?
直接貼上proxyInstance的位元組碼檔案,我們就會恍然大悟了...
//$Proxy0是SaySomething的實現類,重寫了sayHello和sayBye方法
public final class $Proxy0 extends Proxy implements SaySomething {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayBye");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
//實現了介面的sayHello方法,在方法內部呼叫了CustomerHandler的invoke方法,同時傳入了Method物件,
//所以在CustomerHandler物件中可以通過mathod.invovke方法呼叫SyaSomthing的sayHello方法
public final void sayHello() throws {
try {
//h是父類Proxy中的InvocationHandler物件,其實就是我們自定義的CustomHandler物件
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sayBye() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
//忽略內容
}
public final boolean equals(Object var1) throws {
//忽略內容
}
public final String toString() throws {
//忽略內容
}
}
看到了生成的代理物件的位元組碼檔案,是不是一切都明白你了,原理竟然如此簡單^_^