動態代理是什么
首先說下代理模式,代理模式是常見的一種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
方法,最終達到代理實例執行了委托者的方法。