Java Proxy和CGLIB動(dòng)態(tài)代理原理

如果覺得再簡述上閱讀代碼太困難可以點(diǎn)這里:Spring中動(dòng)態(tài)代理詳解

動(dòng)態(tài)代理在Java中有著廣泛的應(yīng)用,比如Spring AOP,Hibernate數(shù)據(jù)查詢、測(cè)試框架的后端mock、RPC,Java注解對(duì)象獲取等。靜態(tài)代理的代理關(guān)系在編譯時(shí)就確定了,而動(dòng)態(tài)代理的代理關(guān)系是在編譯期確定的。靜態(tài)代理實(shí)現(xiàn)簡單,適合于代理類較少且確定的情況,而動(dòng)態(tài)代理則給我們提供了更大的靈活性。今天我們來探討Java中兩種常見的動(dòng)態(tài)代理方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。

? ? JDK原生動(dòng)態(tài)代理

先從 直觀的示例說起,假設(shè)我們有一個(gè)接口(interface Hello)和一個(gè)簡單的實(shí)現(xiàn)類(HelloImp)

// 接口

interface Hello{

? ? String sayHello(String str);

}

// 實(shí)現(xiàn)

class HelloImp implements Hello{

? ? @Override

? ? public String sayHello(String str) {

? ? ? return "HelloImp: " + str;

? ? }

}


這是java中再常見不過的場(chǎng)景了,使用接口制定協(xié)議,然后用不同的實(shí)現(xiàn)來實(shí)現(xiàn)具體行為。假設(shè)你已經(jīng)拿到上述類庫,如果我們通過日志記錄對(duì)sayHello()的調(diào)用,使用靜態(tài)代理可以這樣做:

// 靜態(tài)代理方式

class StaticProxiedHello implements Hello{

...

private Hello hello = new HelloImp();

@Override

public String sayHello(String str) {

logger.info("You said: " + str);

return hello.sayHello(str);

}

}

上述的靜態(tài)代理類StaticProxiedHello做為HelloImp的代理,實(shí)現(xiàn)了相同的Hello接口,使用java動(dòng)態(tài)代理可以這樣做:

? 1.首先實(shí)現(xiàn)一個(gè)InvocationHandler,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的invoke()方法。

? 2.然后在需要使用Hello的時(shí)候,通過JDK動(dòng)態(tài)代理獲取Hello的代理對(duì)象。

// Java Proxy

// 1. 首先實(shí)現(xiàn)一個(gè)InvocationHandler,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的invoke()方法。

class LogInvocationHandler implements InvocationHandler{

...

private Hello hello;

public LogInvocationHandler(Hello hello) {

this.hello = hello;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if("sayHello".equals(method.getName())) {

logger.info("You said: " + Arrays.toString(args));

}

return method.invoke(hello, args);

}

}

// 2. 然后在需要使用Hello的時(shí)候,通過JDK動(dòng)態(tài)代理獲取Hello的代理對(duì)象。

Hello hello = (Hello)Proxy.newProxyInstance( getClass().getClassLoader(), // 1. 類加載器

new Class<?>[] {Hello.class}, // 2. 代理需要實(shí)現(xiàn)的接口,可以有多個(gè)

new LogInvocationHandler(new HelloImp()));// 3. 方法調(diào)用的實(shí)際處理者

System.out.println(hello.sayHello("I love you!"));

運(yùn)行上述代碼輸出結(jié)果:

日志信息: You said: [I love you!]

HelloImp: I love you!

上述代碼的關(guān)鍵是Proxy.newProxyInstance(ClassLoder loder,Class<?> interfance,InvocationHandler handler)方法,該方法會(huì)根據(jù)指定的參數(shù)動(dòng)態(tài)創(chuàng)建代理對(duì)象,三個(gè)參數(shù)的意義如下:

? 1.loder:指定代理對(duì)象的類加載器;

? 2.interface:代理對(duì)象需要實(shí)現(xiàn)的接口,可以同時(shí)指定多個(gè)接口;

? 3.handler:方法調(diào)用的實(shí)際處理者,代理對(duì)象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到這里。

newProxyInstance()會(huì)返回一個(gè)實(shí)現(xiàn)了指定接口的代理對(duì)象,對(duì)該對(duì)象的所有方法調(diào)用都會(huì)轉(zhuǎn)發(fā)給InvocationHandler.invoke()方法,理解上述代碼需要對(duì)java的反射機(jī)制有一定的了解動(dòng)態(tài)代理的神奇之處在于:

? 1.代理對(duì)象是在程序運(yùn)行時(shí)產(chǎn)生的,而不是編譯期;

? 2.對(duì)代理對(duì)象的所有接口方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到InvcationHandler.invoke()方法,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能,安全檢查功能等;之后我們通過某種方式執(zhí)行真正的方法體,實(shí)例中通過反射調(diào)用了Hello對(duì)象的相應(yīng)方法。還可以通過RPC調(diào)用遠(yuǎn)程方法。

注意1:對(duì)于從Object中繼承的方法,JDK Proxy會(huì)把hashCode()、equals()、toString()這三個(gè)非接口方法轉(zhuǎn)發(fā)給InvocationHandler,其余的Object方法則不會(huì)轉(zhuǎn)發(fā)。詳見JDK Proxy官方文檔。

如果對(duì)JDK代理后的對(duì)象類型進(jìn)行深挖,可以看到如下信息:

# Hello代理對(duì)象的類型信息

class=class jdkproxy.$Proxy0

superClass=class java.lang.reflect.Proxy

interfaces: interface jdkproxy.Hello

invocationHandler=jdkproxy.LogInvocationHandler@a09ee92

代理對(duì)象的類型是jdkproxy.$Proxy0,這個(gè)是動(dòng)態(tài)生成的類型,類名是形如$ProxyN的形式;父類是java.lang.reflect.Proxy,所有的JDK動(dòng)態(tài)代理都會(huì)繼承這個(gè)類;同時(shí)實(shí)現(xiàn)了Hello接口也就是我們接口列表中指定的那些接口。

jdkproxy.$Proxy0具體實(shí)現(xiàn):

// JDK代理類具體實(shí)現(xiàn)

public final class $Proxy0 extends Proxy implements Hello {

...

public $Proxy0(InvocationHandler invocationhandler) {

super(invocationhandler);

}

...

@Override

public final String sayHello(String str){

...

return super.h.invoke(this, m3, new Object[] {str});// 將方法調(diào)用轉(zhuǎn)發(fā)給invocationhandler

...

}

...

}


這些邏輯沒什么復(fù)雜之處,但是他們是在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生的,無需我們手動(dòng)編寫。更多詳情,可參考BrightLoong的Java靜態(tài)代理&動(dòng)態(tài)代理筆記

Java動(dòng)態(tài)代理為我們提供了非常靈活的代理機(jī)制,但Java動(dòng)態(tài)代理是基于接口的,如果對(duì)象沒有實(shí)現(xiàn)接口我們?cè)撊绾未砟??CGLIB登場(chǎng)。




CGLIB(Code Generation Library)是一個(gè)基于ASM的字節(jié)碼生成庫,它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB通過繼承方式實(shí)現(xiàn)代理。

來看示例,假設(shè)我們有一個(gè)沒有實(shí)現(xiàn)任何接口的類HelloConcrete:

public class HelloConcrete {

public String sayHello(String str) {

return "HelloConcrete: " + str;

}

}

因?yàn)闆]有實(shí)現(xiàn)接口該類無法使用JDK代理,通過CGBL代理實(shí)現(xiàn)如下:

? 1.首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的intercept()方法。

? 2.然后再需要使用HelloConcrete的時(shí)候通過CGLB動(dòng)態(tài)代理獲得代理對(duì)象。

// CGLIB動(dòng)態(tài)代理

// 1. 首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的intercept()方法。

class MyMethodInterceptor implements MethodInterceptor{

...

@Override

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

logger.info("You said: " + Arrays.toString(args));

return proxy.invokeSuper(obj, args);

}

}

// 2. 然后在需要使用HelloConcrete的時(shí)候,通過CGLIB動(dòng)態(tài)代理獲取代理對(duì)象。

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(HelloConcrete.class);

enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();

System.out.println(hello.sayHello("I love you!"));

運(yùn)行上述代碼輸出結(jié)果:

日志信息: You said: [I love you!]

HelloConcrete: I love you!

上述代碼中,我們通過CGLIB的Enhancer 來指定要代理的目標(biāo)對(duì)象、實(shí)際處理代理邏輯的對(duì)象,最終通過調(diào)用create() 方法得到代理對(duì)象,對(duì)這個(gè)對(duì)象所有非final方法的調(diào)用都會(huì)轉(zhuǎn)發(fā)給MethodInterceptor.intercept() 方法 ,在intercept()方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能、安全檢查功能等;通過調(diào)用MethodProxy.invokeSuper()方法,我們將調(diào)用轉(zhuǎn)發(fā)給原始對(duì)象,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調(diào)用的中轉(zhuǎn)站。

注意:對(duì)于從Object中繼承的方法,CGLIB代理也會(huì)進(jìn)行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不會(huì),因?yàn)樗莊inal方法,CGLIB無法代理。

如果對(duì)CGLIB代理之后的對(duì)象類型進(jìn)行深挖,可以看到如下信息:

# HelloConcrete代理對(duì)象的類型信息

class=class cglib.HelloConcrete

EnhancerByCGLIB

EnhancerByCGLIB

e3734e52

superClass=class lh.HelloConcrete

interfaces:

interface net.sf.cglib.proxy.Factory

invocationHandler=not java proxy class

我們看到使用CGLIB代理之后的對(duì)象類型是cglib.HelloConcrete

EnhancerByCGLIB

EnhancerByCGLIB

e3734e52,這是CGLIB動(dòng)態(tài)生成的類型;父類是HelloConcrete,印證了CGLIB是通過繼承實(shí)現(xiàn)代理;同時(shí)實(shí)現(xiàn)了net.sf.cglib.proxy.Factory接口,這個(gè)接口是CGLIB自己加入的,包含一些工具方法。

注意,既然是繼承就不得不考慮final的問題。我們知道final類型不能有子類,所以CGLIB不能代理final類型,遇到這種情況會(huì)拋出類似如下異常:

java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete

同樣的,final方法是不能重載的,所以也不能通過CGLIB代理,遇到這種情況不會(huì)拋異常,而是會(huì)跳過final方法只代理其他方法。

如果你還對(duì)代理類cglib.HelloConcrete

EnhancerByCGLIB

EnhancerByCGLIB

e3734e52具體實(shí)現(xiàn)感興趣,它大致長這個(gè)樣子:

// CGLIB代理類具體實(shí)現(xiàn)

public class HelloConcrete$$EnhancerByCGLIB$$e3734e52 extends HelloConcrete implements Factory {

...

private MethodInterceptor CGLIB$CALLBACK_0; // ~~

...

public final String sayHello(String paramString) {

...

MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;

if (tmp17_14 != null) { // 將請(qǐng)求轉(zhuǎn)發(fā)給MethodInterceptor.intercept()方法。

return (String)tmp17_14.intercept(this, CGLIB$sayHello$0$Method, new Object[] { paramString }, CGLIB$sayHello$0$Proxy);

}

return super.sayHello(paramString);

}

...

}

上述代碼我們看到,當(dāng)調(diào)用代理對(duì)象的sayHello()方法時(shí),首先會(huì)嘗試轉(zhuǎn)發(fā)給MethodInterceptor.intercept()方法,如果沒有MethodInterceptor就執(zhí)行父類的sayHello()。這些邏輯沒什么復(fù)雜之處,但是他們是在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生的,無需我們手動(dòng)編寫。如何獲取CGLIB代理類字節(jié)碼可參考Access the generated byte[] array directly。

更多關(guān)于CGLIB的介紹可以參考Rafael Winterhalter的cglib: The missing manual,一篇很深入的文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容