基于JDK的動態代理原理分析
這篇文章解決三個問題:
-
What 動態代理是什么
-
How 動態代理怎么用
-
Why 動態代理的原理
動態代理是什么?
動態代理是代理模式的一種具體實作,是指在程式運行期間,動態的生成目標物件的代理類(直接加載在記憶體中的位元組碼檔案),實作對目標物件所有方法的增強,通過這種方式,我們可以在不改變(或無法改變)目標物件原始碼的情況下,對目標物件的方法執行前后進行干預,
動態代理怎么用?
首先,準備好我們需要代理的類和介面,因為JDK的動態代理是基于介面實作的,所以被代理的物件必須要有介面,
/**
* SaySomething介面
*/
public interface SaySomething {
?
public void sayHello();
?
public void sayBye();
}
/**
* SaySomething的實作類
*/
public class SaySomethingImpl implements SaySomething {
@Override
public void sayHello() {
System.out.println("Hello World");
}
?
@Override
public void sayBye() {
System.out.println("Bye Bye");
}
}
按照動態代理的用法,需要自定義一個處理器,用來撰寫自定義邏輯,實作對被代理物件的增強,
自定義的處理器需要滿足以下要求:
-
需要實作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:呼叫方法時的入參
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目標方法執行前的自定義邏輯處理
System.out.println("-----before------");
?
//執行目標物件的方法,使用反射來執行方法,反射需要傳入目標物件,此時用到了成員變數obj,
Object result = method.invoke(obj, args);
?
//目標方法執行后的自定義邏輯處理
System.out.println("-----after------");
return result;
}
}
這樣我們就完成了自定義處理器的撰寫,同時在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 {
//忽略內容
}
}
看到了生成的代理物件的位元組碼檔案,是不是一切都明白你了,原理竟然如此簡單^_^
本文為個人學習整理,如有描述錯誤或者對相關內容感興趣,歡迎評論或私信交流,一起討論、共同進步,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/451214.html
標籤:Java
