動態代理是什麼
首先說下代理模式,代理模式是常見的一種java設計模式,特徵是代理類與委託類實現了同樣的介面,代理類主要負責為委託類預處理、過濾、轉發,以及事後處理等。代理類與委託類之間通常會存在關聯關係,一個代理類的例項與它的委託類的例項是關聯的。代理類的例項本身是並不真正關心被呼叫方法的內部邏輯,而是會通過內部訪問呼叫 委託類的例項真正實現了的方法,來為呼叫者提供服務。
有代理的話,在訪問實際物件時,是通過代理例項來訪問、呼叫委託類方法的,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
動態代理對比靜態代理,最大的特點是代理類是在程式執行時生成的,並非在編譯期生成,能做的事情也多了,自然風險也高了。
動態代理最簡單的用法
用一個比較接近生活的例子:中午,餓了的室友 委託 持家有道的你 去點外賣
Hungry.java :介面
public interface Hungry {
void callLunch();
}
Roommate.java :Hungry介面的實現類,也就是委託類
public class Roommate implements Hungry{
private String name;
public Roommate(String name) {
this.name = name;
}
@Override
public void callLunch() {
System.out.println("好餓,今天午飯點外賣吧");
}
}
public class RoommateInvocationHandler<T> implements InvocationHandler {
private T rommate;
public RoommateInvocationHandler(T roommate){
this.rommate = roommate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("下單前,我先幫你看下有沒有平臺優惠券吧");
Object result = method.invoke(rommate , args);
return result;
}
}
InvocationHandler是一個介面,由代理例項內部的invocation handler
實現的介面。每個代理例項都有一個關聯的invocation handler
。當代理例項上呼叫方法時,method.invoke(baseImpl, args)
,此方法將被編碼並織入到代理例項內部的 invocation handler
實現的invoke
方法中。
利用 Proxy 的方式實現動態代理,呼叫 委託類介面 的方法,完成午餐點外賣這個操作
public static void main(String[] args) {
Roommate roommate = new Roommate("zhangsan");
Hungry proxyInstance = (Hungry) Proxy.newProxyInstance(
roommate.getClass().getClassLoader(),
roommate.getClass().getInterfaces(),
new RoommateInvocationHandler<Roommate>(roommate)
);
proxyInstance.callLunch();
}
//輸出結果
下單前,我先幫你看下有沒有平臺優惠券吧
好餓,今天午飯點外賣吧
代理例項proxyInstance
的型別是Hungry,所以只能呼叫Hungry裡規定的方法。Roommate作為介面實現類,不是來自介面的其他的方法,是無法通過動態代理呼叫的。
可以看到代理例項在呼叫委託類實現的方法時,可以很方便地在呼叫方法的前後執行一些操作,在示例程式碼中則是在呼叫方法前簡單輸出了一行: System.out.println("下單前,我先幫你看下有沒有平臺優惠券吧")
,還可以有其他用途,例如記錄這個方法的耗時時間,對方法的引數或者返回結果進行修改等等。這也是Spring,Dagger進行AOP程式設計的原理。
那為什麼繼承InvocationHandler
介面和持有委託類引用的RoommateInvocationHandler呼叫來自Hungry
介面的callLunch()
方法時可以呼叫到委託類對callLunch()的邏輯實現呢,看看它的背後原理:
動態代理的實現原理
從Proxy.newProxyInstance()
入手,逐步分析 InvocationHandler 如何建立代理例項和委託例項的關聯:
public static Object newProxyInstance(ClassLoader loader , Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//InvocationHandler必須非空,說明是個重要角色
Objects.requireNonNull(h);
//獲取委託類的介面
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* 核心:通過類載入器和委託類介面,在記憶體中查詢出或者生成指定的代理類
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 利用指定的invocation handler呼叫它的構造器方法,構建代理類的例項返回
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
來到這一步好像就停下了,那麼接下來探究 cl 這個例項建立過程發生了什麼:
在上面示例程式碼main函式的後面接著補充。利用ProxyGenerator.generateProxyClass
生成這個動態生成的類檔案,寫入了指定路徑的class檔案內
$Proxy0
是 代理類在系統內部的編號,在示例程式碼只生成了一個代理類所以編號是 $Proxy0
。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces());
String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class";
try(FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(classFile);
fos.flush();
}catch (IOException e){
e.printStackTrace();
System.out.println("error:寫入檔案");
}
使用反編譯工具,我這裡用的是jd-gui反編譯,這個$Proxy0類,實現了Proxy
類,繼承了和委託類相同的介面
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy_test.Hungry;
public final class $Proxy0 extends Proxy implements Hungry{
private static Method m1;
private static Method m3; //由下方靜態程式碼塊得知,m3代表callLunch()這一個方法
private static Method m2;
private static Method m0;
/*
* 父類Proxy的構造器,其中 h 屬性為 InvocationHandler引用
* protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
*/
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
//關鍵!可供外界呼叫,方法名與委託類實現介面的方法相同,利用 InvocationHandler呼叫invoke
public final void callLunch() throws {
try{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError){
throw localError;
}
catch (Throwable localThrowable){
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject) throws {}
public final String toString() throws {...}
public final int hashCode() throws {...}
static{
try{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", 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());
}
}
}
事情逐漸明朗起來,從這個動態類的原始碼,可以分析出: $Proxy0
,在構建這個類時,會呼叫了父類Proxy的構造方法,將InvocationHandler
引用傳遞給了父類Proxy的 h
屬性,於是當我們在外界使用 代理例項 呼叫了 callLunch()
這個方法時,就會來到這一句 this.h.invoke(this, m3, null);
由於h
屬性其實是InvocationHandler
引用,呼叫了它的invoke
,也就導致了上面示例程式碼中的RoommateInvocationHandler
類的重寫過的invoke
方法也就被呼叫了,RoommateInvocationHandler
也持有委託類的引用,所以委託類的方法也被呼叫起來了。
Java的繼承機制是單繼承,多介面。代理類因為必須要繼承Proxy類,所以java的動態代理只能對介面進行代理,無法對一個class類進行動態代理。
動態代理原理總結
用大白話的方式講:
有一個類InvocationHandler
,它的性質類似一箇中介,中介類構建時持有了委託物件,所以可以在它的invoke
方法中呼叫了委託物件實現介面的具體方法。當外部呼叫這個InvocationHandler
的invoke方法時,對 invoke
的呼叫最終都轉為對委託物件的方法呼叫。
建立明面上負責代理的代理例項時,在記憶體中動態生成的類不但繼承了Proxy
,也實現了與委託物件相同的介面,因此代理例項可以呼叫此介面的方法,然後通過持有的中介類物件來呼叫中介類物件的invoke
方法,最終達到代理例項執行了委託者的方法。