動態(tài)代理是一種方便運(yùn)行時動態(tài)構(gòu)建代理、動態(tài)處理方法調(diào)用的機(jī)制,很多場景都是利用類似的機(jī)制實(shí)現(xiàn)的,比如用于包裝RPC的遠(yuǎn)程調(diào)用、面向切面編程(AOP)等
實(shí)現(xiàn)原理
實(shí)現(xiàn)動態(tài)代理的方式很多,比如JDK提供的動態(tài)代理,主要就是利用到了反射機(jī)制。還有一些第三方的框架,比如更高性能的字節(jié)碼操作技術(shù),具體實(shí)現(xiàn)有ASM、CGLIB(基于ASM)、Javassist等。這里通過JDK提供的動態(tài)代理代碼,了解下其實(shí)現(xiàn)原理。主要涉及到Proxy、InvocationHandler兩個類
Proxy
位于java.lang.reflect包下,顯然跟反射有關(guān)。Proxy提供了一個靜態(tài)方法用于動態(tài)創(chuàng)建代理對象,下面用于創(chuàng)建Foo接口的代理對象。
//調(diào)用目標(biāo)方法前后處理實(shí)現(xiàn)
InvocationHandler handler = new MyInvocationHandler(...);
//創(chuàng)建代理類
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
//創(chuàng)建代理對象
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
newInstance(handler);
或者:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);
//InvocationHandler接口
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
//MyInvocationHandler實(shí)現(xiàn)
public MyInvocationHandler implements InvocationHandler {
private Object target;//目標(biāo)對象,實(shí)現(xiàn)了Foo接口
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//前處理
Object result = method.invoke(target,args);
//后處理
return result;
}
}
動態(tài)代理類實(shí)現(xiàn)了Foo接口,也可以創(chuàng)建多個接口的代理類,MyInvocationHandler是InvocationHandler 的實(shí)現(xiàn),用于在調(diào)用目標(biāo)對象的方法前后進(jìn)行其他處理。
生成的代理類有以下特點(diǎn):
- 如果所有接口是public的,則生成的代理類是public,final,且不是abstract的
- 由于代理類創(chuàng)建時實(shí)現(xiàn)了所有指定接口,因此在其Class對象上調(diào)用getInterfaces將返回一個包含接口列表的數(shù)組,調(diào)用getMethods將返回一個包含這些接口中所有方法的Method對象
- 每個代理類都有一個public的構(gòu)造器,接受一個InvocationHandler的入?yún)?/li>
Proxy.newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{
//判空
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
//先從緩存中搜索
Class<?> cl = getProxyClass0(loader, intfs);
//獲取構(gòu)造器,Class<?>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams);
//創(chuàng)建代理對象
return cons.newInstance(new Object[]{h});
}
應(yīng)用場景
- 代理機(jī)制其實(shí)就是一種代理模式,對目標(biāo)方法的調(diào)用通過代理對象完成。這樣可以讓調(diào)用者和實(shí)現(xiàn)者之間解耦,比如進(jìn)行RPC調(diào)用,調(diào)用過程中的尋址、序列化、反序列化等一些基礎(chǔ)公共工作,對于調(diào)用者是不必關(guān)心的,那么通過代理,就可以把這些底層工作給做了,上層只需關(guān)心業(yè)務(wù)本身就好了。
- 同樣開發(fā)中,我們經(jīng)常會遇到調(diào)用第三方接口的情況,一般我們要拼裝請求參數(shù),發(fā)送http請求,返回的處理(比如json轉(zhuǎn)對象),有的還要加簽名,系統(tǒng)中一個還好,多個就頭疼了,那么這時候就可以把這些公共的代碼抽出來采用動態(tài)代理。openFegin這個框架就是基于該原理實(shí)現(xiàn)的。
- spring框架中典型的應(yīng)用就是Spring AOP 和 事務(wù)管理了
- 通用日志處理,安全校驗(yàn),接口耗時統(tǒng)計等
各種動態(tài)代理實(shí)現(xiàn)的比較
通過上面的例子,我們知道JDK動態(tài)代理必須提供接口,也就是它只能生成接口的代理實(shí)現(xiàn)對象,如果被調(diào)用者沒有實(shí)現(xiàn)接口或者部分方法不是接口中的實(shí)現(xiàn),那如何生成代理對象呢?這時就可以考慮其他方式了,我們知道Spring AOP支持兩種代理模式,JDK代理和CGLIB,如果選擇CGLIB是不需要被調(diào)用者實(shí)現(xiàn)接口的。
CGLIB代理機(jī)制是創(chuàng)建目標(biāo)類的子類,這樣子類就可以調(diào)用父類中的方法了,同樣實(shí)現(xiàn)了代理機(jī)制。
那么在開發(fā)中如何選擇呢?
JDK Proxy
- 無需引入額外jar包,JDK官方實(shí)現(xiàn),更可靠穩(wěn)定,保證平滑升級新版本
- 最小化依賴關(guān)系,減少依賴意味著簡化開發(fā)和維護(hù)
- 代碼實(shí)現(xiàn)簡單
CGLIB
- 無需目標(biāo)類實(shí)現(xiàn)接口即可生成代理對象,減少接口的限制
- 性能比JDK Proxy高,后續(xù)JDK升級優(yōu)化可能會降低這種差距