動(dòng)態(tài)代理在Java中有著廣泛的應(yīng)用,比如Spring AOP,Hibernate數(shù)據(jù)查詢(xún)、測(cè)試框架的后端mock、RPC,Java注解對(duì)象獲取等。靜態(tài)代理的代理關(guān)系在編譯時(shí)就確定了,而動(dòng)態(tài)代理的代理關(guān)系是在運(yùn)行時(shí)確定的。靜態(tài)代理實(shí)現(xiàn)簡(jiǎn)單,適合于代理類(lèi)較少且確定的情況,而動(dòng)態(tài)代理則給我們提供了更大的靈活性。今天我們來(lái)探討Java中兩種常見(jiàn)的動(dòng)態(tài)代理方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理。
JDK原生動(dòng)態(tài)代理
先從直觀的示例說(shuō)起,假設(shè)我們有一個(gè)接口Hello
和一個(gè)簡(jiǎn)單實(shí)現(xiàn)HelloImpl
:
// Hello接口
public interface Hello{
String sayHello(String str);
}
//Hello接口實(shí)現(xiàn)
public class HelloImpl implements Hello{`
@Override
public String sayHello(String str) {
return "HelloImpl: " + str;
}
}
這是Java種再常見(jiàn)不過(guò)的場(chǎng)景,使用接口制定協(xié)議,然后用不同的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)具體行為。假設(shè)你已經(jīng)拿到上述類(lèi)庫(kù),如果我們想通過(guò)日志記錄對(duì)sayHello()
的調(diào)用,使用靜態(tài)代理可以這樣做:
// 靜態(tài)代理方式
public class StaticProxiedHello implements Hello{
private Hello hello = new HelloImpl();
@Override
public String sayHello(String str) {
System.out.println("You said: " + str);
return hello.sayHello(str);
}
}
上例中靜態(tài)代理類(lèi)StaticProxiedHello
作為HelloImpl
的代理,實(shí)現(xiàn)了相同的Hello
接口。用Java動(dòng)態(tài)代理可以這樣做:
- 首先實(shí)現(xiàn)一個(gè)InvocationHandler,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類(lèi)的invoke()方法。
- 然后在需要使用Hello的時(shí)候,通過(guò)JDK動(dòng)態(tài)代理獲取Hello的代理對(duì)象。
// Java Proxy
// 1. 首先實(shí)現(xiàn)一個(gè)InvocationHandler,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類(lèi)的invoke()方法。
public 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í)候,通過(guò)JDK動(dòng)態(tài)代理獲取Hello的代理對(duì)象。
public static void main(String[] args){
Hello hello = (Hello)Proxy.newProxyInstance(
LogInvocationHandler.Class.getClassLoader(), // 1. 類(lèi)加載器
new Class<?>[] {Hello.class}, // 2. 代理需要實(shí)現(xiàn)的接口,可以有多個(gè)
new LogInvocationHandler(new HelloImpl()));// 3. 方法調(diào)用的實(shí)際處理者
}
上述代碼的關(guān)鍵是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
方法,該方法會(huì)根據(jù)指定的參數(shù)動(dòng)態(tài)創(chuàng)建代理對(duì)象。三個(gè)參數(shù)的意義如下:
-
loader
,指定代理對(duì)象的類(lèi)加載器; -
interfaces
,代理對(duì)象需要實(shí)現(xiàn)的接口,可以同時(shí)指定多個(gè)接口; -
handler
,方法調(diào)用的實(shí)際處理者,代理對(duì)象的方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到這里(*注意1)。
newProxyInstance()
會(huì)返回一個(gè)實(shí)現(xiàn)了指定接口的代理對(duì)象,對(duì)該對(duì)象的所有方法調(diào)用都會(huì)轉(zhuǎn)發(fā)給InvocationHandler.invoke()
方法。理解上述代碼需要對(duì)Java反射機(jī)制有一定了解。動(dòng)態(tài)代理神奇的地方就是:
- 代理對(duì)象是在程序運(yùn)行時(shí)產(chǎn)生的,而不是編譯期;
-
對(duì)代理對(duì)象的所有接口方法調(diào)用都會(huì)轉(zhuǎn)發(fā)到
InvocationHandler.invoke()
方法,在invoke()
方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能、安全檢查功能等;之后我們通過(guò)某種方式執(zhí)行真正的方法體,示例中通過(guò)反射調(diào)用了Hello對(duì)象的相應(yīng)方法,還可以通過(guò)RPC調(diào)用遠(yuǎn)程方法。
注意1:對(duì)于從Object中繼承的方法,JDK Proxy會(huì)把
hashCode()
、equals()
、toString()
這三個(gè)非接口方法轉(zhuǎn)發(fā)給InvocationHandler
,其余的Object方法則不會(huì)轉(zhuǎn)發(fā)。詳見(jiàn)JDK Proxy官方文檔。
如果對(duì)JDK代理后的對(duì)象類(lèi)型進(jìn)行深挖,可以看到如下信息:
# Hello代理對(duì)象的類(lèi)型信息
class=class jdkproxy.$Proxy0
superClass=class java.lang.reflect.Proxy
interfaces:
interface jdkproxy.Hello
invocationHandler=jdkproxy.LogInvocationHandler@a09ee92
代理對(duì)象的類(lèi)型是jdkproxy.$Proxy0
,這是個(gè)動(dòng)態(tài)生成的類(lèi)型,類(lèi)名是形如$ProxyN的形式;父類(lèi)是java.lang.reflect.Proxy
,所有的JDK動(dòng)態(tài)代理都會(huì)繼承這個(gè)類(lèi);同時(shí)實(shí)現(xiàn)了Hello
接口,也就是我們接口列表中指定的那些接口。
如果你還對(duì)jdkproxy.$Proxy0
具體實(shí)現(xiàn)感興趣,它大致長(zhǎng)這個(gè)樣子:
// JDK代理類(lèi)具體實(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
...
}
...
}
這些邏輯沒(méi)什么復(fù)雜之處,但是他們是在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生的,無(wú)需我們手動(dòng)編寫(xiě)。更多詳情,可參考BrightLoong的Java靜態(tài)代理&動(dòng)態(tài)代理筆記
Java動(dòng)態(tài)代理為我們提供了非常靈活的代理機(jī)制,但Java動(dòng)態(tài)代理是基于接口的,如果對(duì)象沒(méi)有實(shí)現(xiàn)接口我們?cè)撊绾未砟兀緾GLIB登場(chǎng)。
CGLIB動(dòng)態(tài)代理
CGLIB(Code Generation Library)是一個(gè)基于ASM的字節(jié)碼生成庫(kù),它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB通過(guò)繼承方式實(shí)現(xiàn)代理。
來(lái)看示例,假設(shè)我們有一個(gè)沒(méi)有實(shí)現(xiàn)任何接口的類(lèi)HelloConcrete
:
public class HelloConcrete {
public String sayHello(String str) {
return "HelloConcrete: " + str;
}
}
因?yàn)闆](méi)有實(shí)現(xiàn)接口該類(lèi)無(wú)法使用JDK代理,通過(guò)CGLIB代理實(shí)現(xiàn)如下:
- 首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類(lèi)的intercept()方法。
- 然后在需要使用HelloConcrete的時(shí)候,通過(guò)CGLIB動(dòng)態(tài)代理獲取代理對(duì)象。
// CGLIB動(dòng)態(tài)代理
// 1. 首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類(lèi)的intercept()方法。
public class MyMethodInterceptor implements MethodInterceptor{
...
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("You said: " + Arrays.toString(args));
return proxy.invokeSuper(obj, args);
}
public static void main(String[] args){
// 2. 然后在需要使用HelloConcrete的時(shí)候,通過(guò)CGLIB動(dòng)態(tài)代理獲取代理對(duì)象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());
HelloConcrete hello = (HelloConcrete)enhancer.create();
}
}
上述代碼中,我們通過(guò)CGLIB的Enhancer
來(lái)指定要代理的目標(biāo)對(duì)象、實(shí)際處理代理邏輯的對(duì)象,最終通過(guò)調(diào)用create()
方法得到代理對(duì)象,對(duì)這個(gè)對(duì)象所有非final方法的調(diào)用都會(huì)轉(zhuǎn)發(fā)給MethodInterceptor.intercept()
方法,在intercept()
方法里我們可以加入任何邏輯,比如修改方法參數(shù),加入日志功能、安全檢查功能等;通過(guò)調(diào)用MethodProxy.invokeSuper()
方法,我們將調(diào)用轉(zhuǎn)發(fā)給原始對(duì)象,具體到本例,就是HelloConcrete
的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler
很類(lèi)似,都是方法調(diào)用的中轉(zhuǎn)站。
注意:對(duì)于從Object中繼承的方法,CGLIB代理也會(huì)進(jìn)行代理,如
hashCode()
、equals()
、toString()
等,但是getClass()
、wait()
等方法不會(huì),因?yàn)樗莊inal方法,CGLIB無(wú)法代理。
如果對(duì)CGLIB代理之后的對(duì)象類(lèi)型進(jìn)行深挖,可以看到如下信息:
# HelloConcrete代理對(duì)象的類(lèi)型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces:
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class
我們看到使用CGLIB代理之后的對(duì)象類(lèi)型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
,這是CGLIB動(dòng)態(tài)生成的類(lèi)型;父類(lèi)是HelloConcrete
,印證了CGLIB是通過(guò)繼承實(shí)現(xiàn)代理;同時(shí)實(shí)現(xiàn)了net.sf.cglib.proxy.Factory
接口,這個(gè)接口是CGLIB自己加入的,包含一些工具方法。
注意,既然是繼承就不得不考慮final的問(wèn)題。我們知道final類(lèi)型不能有子類(lèi),所以CGLIB不能代理final類(lèi)型,遇到這種情況會(huì)拋出類(lèi)似如下異常:
java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete
同樣的,final方法是不能重載的,所以也不能通過(guò)CGLIB代理,遇到這種情況不會(huì)拋異常,而是會(huì)跳過(guò)final方法只代理其他方法。
如果你還對(duì)代理類(lèi)cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
具體實(shí)現(xiàn)感興趣,它大致長(zhǎng)這個(gè)樣子:
// CGLIB代理類(lèi)具體實(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()
方法,如果沒(méi)有MethodInterceptor
就執(zhí)行父類(lèi)的sayHello()
。這些邏輯沒(méi)什么復(fù)雜之處,但是他們是在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生的,無(wú)需我們手動(dòng)編寫(xiě)。如何獲取CGLIB代理類(lèi)字節(jié)碼可參考Access the generated byte[] array directly。
更多關(guān)于CGLIB的介紹可以參考Rafael Winterhalter的cglib: The missing manual,一篇很深入的文章。
結(jié)語(yǔ)
本文介紹了Java兩種常見(jiàn)動(dòng)態(tài)代理機(jī)制的用法和原理,JDK原生動(dòng)態(tài)代理是Java原生支持的,不需要任何外部依賴(lài),但是它只能基于接口進(jìn)行代理;CGLIB通過(guò)繼承的方式進(jìn)行代理,無(wú)論目標(biāo)對(duì)象有沒(méi)有實(shí)現(xiàn)接口都可以代理,但是無(wú)法處理final的情況。
動(dòng)態(tài)代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實(shí)現(xiàn)方式,了解動(dòng)態(tài)代理原理,對(duì)理解Spring AOP大有幫助。
轉(zhuǎn)載自:Java Proxy 和 CGLIB 動(dòng)態(tài)代理原理