原來(lái)這才是動(dòng)態(tài)代理?。?!
各位小伙伴們大家吼啊!我是 cxuan,距離上次更新已經(jīng)有段時(shí)間了,臨近過(guò)年了,項(xiàng)目這邊也比較忙,而且最近很多時(shí)間都花在看書(shū)、提升自己上面,文章寫(xiě)的比較拖沓,這里我要自我反思(其實(shí)我已經(jīng)籌備了幾篇文章,就等結(jié)尾了,嘿嘿嘿)。
我們上篇文章聊了一波什么是動(dòng)態(tài)代理,然后我又從動(dòng)態(tài)代理的四種實(shí)現(xiàn)為切入點(diǎn),為你講解 JDK 動(dòng)態(tài)代理、CGLIB 動(dòng)態(tài)代理、Javaassist、ASM 反向生成字節(jié)碼的區(qū)別,具體的內(nèi)容你可以參見(jiàn)下面這篇文章。
動(dòng)態(tài)代理竟然如此簡(jiǎn)單!
那么這篇文章我們來(lái)聊一下動(dòng)態(tài)代理的實(shí)現(xiàn)原理。
為了保險(xiǎn)起見(jiàn),我們首先花幾分鐘回顧一下什么是動(dòng)態(tài)代理吧!
什么是動(dòng)態(tài)代理
首先,動(dòng)態(tài)代理是代理模式的一種實(shí)現(xiàn)方式,代理模式除了動(dòng)態(tài)代理還有 靜態(tài)代理,只不過(guò)靜態(tài)代理能夠在編譯時(shí)期確定類(lèi)的執(zhí)行對(duì)象,而動(dòng)態(tài)代理只有在運(yùn)行時(shí)才能夠確定執(zhí)行對(duì)象是誰(shuí)。代理可以看作是對(duì)最終調(diào)用目標(biāo)的一個(gè)封裝,我們能夠通過(guò)操作代理對(duì)象來(lái)調(diào)用目標(biāo)類(lèi),這樣就可以實(shí)現(xiàn)調(diào)用者和目標(biāo)對(duì)象的解耦合。
動(dòng)態(tài)代理的應(yīng)用場(chǎng)景有很多,最常見(jiàn)的就是 AOP 的實(shí)現(xiàn)、RPC 遠(yuǎn)程調(diào)用、Java 注解對(duì)象獲取、日志框架、全局性異常處理、事務(wù)處理等。
動(dòng)態(tài)代理的實(shí)現(xiàn)有很多,但是 JDK 動(dòng)態(tài)代理是很重要的一種,下面我們就 JDK 動(dòng)態(tài)代理來(lái)深入理解一波。
JDK 動(dòng)態(tài)代理
首先我們先來(lái)看一下動(dòng)態(tài)代理的執(zhí)行過(guò)程
在 JDK 動(dòng)態(tài)代理中,實(shí)現(xiàn)了 InvocationHandler的類(lèi)可以看作是 代理類(lèi)(因?yàn)轭?lèi)也是一種對(duì)象,所以我們上面為了描述關(guān)系,把代理類(lèi)形容成了代理對(duì)象)。JDK 動(dòng)態(tài)代理就是圍繞實(shí)現(xiàn)了 InvocationHandler 的代理類(lèi)進(jìn)行的,比如下面就是一個(gè) InvocationHandler 的實(shí)現(xiàn)類(lèi),同時(shí)它也是一個(gè)代理類(lèi)。
public class UserHandler implements InvocationHandler {
private UserDao userDao;
public UserHandler(UserDao userDao){
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
saveUserStart();
Object obj = method.invoke(userDao, args);
saveUserDone();
return obj;
}
public void saveUserStart(){
System.out.println("---- 開(kāi)始插入 ----");
}
public void saveUserDone(){
System.out.println("---- 插入完成 ----");
}
}
代理類(lèi)一個(gè)最最最重要的方法就是 invoke 方法,它有三個(gè)參數(shù)
- Object proxy: 動(dòng)態(tài)代理對(duì)象,關(guān)于這個(gè)方法我們后面會(huì)說(shuō)。
- Method method: 表示最終要執(zhí)行的方法,method.invoke 用于執(zhí)行被代理的方法,也就是真正的目標(biāo)方法
- Object[] args: 這個(gè)參數(shù)就是向目標(biāo)方法傳遞的參數(shù)。
我們構(gòu)造好了代理類(lèi),現(xiàn)在就要使用它來(lái)實(shí)現(xiàn)我們對(duì)目標(biāo)對(duì)象的調(diào)用,那么如何操作呢?請(qǐng)看下面代碼
public static void dynamicProxy(){
UserDao userDao = new UserDaoImpl();
InvocationHandler handler = new UserHandler(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();
UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);
proxy.saveUser();
}
如果要用 JDK 動(dòng)態(tài)代理的話(huà),就需要知道目標(biāo)對(duì)象的類(lèi)加載器、目標(biāo)對(duì)象的接口,當(dāng)然還要知道目標(biāo)對(duì)象是誰(shuí)。構(gòu)造完成后,我們就可以調(diào)用 Proxy.newProxyInstance方法,然后把類(lèi)加載器、目標(biāo)對(duì)象的接口、目標(biāo)對(duì)象綁定上去就完事兒了。
這里需要注意一下 Proxy 類(lèi),它就是動(dòng)態(tài)代理實(shí)現(xiàn)所用到的代理類(lèi)。
Proxy 位于java.lang.reflect 包下,這同時(shí)也旁敲側(cè)擊的表明動(dòng)態(tài)代理的本質(zhì)就是反射。
下面我們就圍繞 JDK 動(dòng)態(tài)代理,來(lái)深入理解一下它的原理,以及搞懂為什么動(dòng)態(tài)代理的本質(zhì)就是反射。
動(dòng)態(tài)代理的實(shí)現(xiàn)原理
在了解動(dòng)態(tài)代理的實(shí)現(xiàn)原理之前,我們先來(lái)了解一下 InvocationHandler 接口
InvocationHandler 接口
JavaDoc 告訴我們,InvocationHandler 是一個(gè)接口,實(shí)現(xiàn)這個(gè)接口的類(lèi)就表示該類(lèi)是一個(gè)代理實(shí)現(xiàn)類(lèi),也就是代理類(lèi)。
InvocationHandler 接口中只有一個(gè) invoke 方法。
動(dòng)態(tài)代理的優(yōu)勢(shì)在于能夠很方便的對(duì)代理類(lèi)中方法進(jìn)行集中處理,而不用修改每個(gè)被代理的方法。因?yàn)樗斜淮淼姆椒?真正執(zhí)行的方法)都是通過(guò)在 InvocationHandler 中的 invoke 方法調(diào)用的。所以我們只需要對(duì) invoke 方法進(jìn)行集中處理。
invoke 方法只有三個(gè)參數(shù)
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
- proxy:代理對(duì)象
- method: 代理對(duì)象調(diào)用的方法
- args:調(diào)用方法中的參數(shù)。
動(dòng)態(tài)代理的整個(gè)代理過(guò)程不像靜態(tài)代理那樣一目了然,清晰易懂,因?yàn)樵趧?dòng)態(tài)代理的過(guò)程中,我們沒(méi)有看到代理類(lèi)的真正代理過(guò)程,也不明白其具體操作,所以要分析動(dòng)態(tài)代理的實(shí)現(xiàn)原理,我們必須借助源碼。
那么問(wèn)題來(lái)了,首先第一步應(yīng)該從哪分析?如果不知道如何分析的話(huà),干脆就使用倒推法,從后往前找,我們直接先從 Proxy.newProxyInstance入手,看看是否能略知一二。
Proxy.newInstance 方法分析
Proxy 提供了創(chuàng)建動(dòng)態(tài)代理類(lèi)和實(shí)例的靜態(tài)方法,它也是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類(lèi)的超類(lèi)。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
Class<?> cl = getProxyClass0(loader, intfs);
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 (Exception e) {
...
}
乍一看起來(lái)有點(diǎn)麻煩,其實(shí)源碼都是這樣,看起來(lái)非常復(fù)雜,但是慢慢分析、厘清條理過(guò)后就好,最重要的是分析源碼不能著急。
上面這個(gè) Proxy.newProxyInstsance 其實(shí)就做了下面幾件事,我畫(huà)了一個(gè)流程圖作為參考。
從上圖中我們也可以看出,newProxyInstsance 方法最重要的幾個(gè)環(huán)節(jié)就是獲得代理類(lèi)、獲得構(gòu)造器,然后構(gòu)造新實(shí)例。
對(duì)反射有一些了解的同學(xué),應(yīng)該會(huì)知道獲得構(gòu)造器和構(gòu)造新實(shí)例是怎么回事。關(guān)于反射,可以參考筆者的這篇文章
學(xué)會(huì)反射后,我被錄取了!
所以我們的重點(diǎn)就放在了獲得代理類(lèi),這是最關(guān)鍵的一步,對(duì)應(yīng)源碼中的 Class cl = getProxyClass0(loader, intfs); 我們進(jìn)入這個(gè)方法一探究竟
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
這個(gè)方法比較簡(jiǎn)單,首先會(huì)直接判斷接口長(zhǎng)度是否大于 65535(剛開(kāi)始看到這里我是有點(diǎn)不明白的,我心想這個(gè)判斷是要判斷什么?interfaces 這不是一個(gè) class 類(lèi)型嗎,從 length 點(diǎn)進(jìn)去也看不到這個(gè)屬性,細(xì)看一下才明白,這居然是可變參數(shù),Class ... 中的 ... 就是可變參數(shù),所以這個(gè)判斷我猜測(cè)應(yīng)該是判斷接口數(shù)量是否大于 65535。)
然后會(huì)直接從 proxyClassCache 中根據(jù) loader 和 interfaces 獲取代理對(duì)象實(shí)例。如果能夠根據(jù) loader 和 interfaces 找到代理對(duì)象,將會(huì)返回緩存中的對(duì)象副本;否則,它將通過(guò) ProxyClassFactory 創(chuàng)建代理類(lèi)。
proxyClassCache.get 就是一系列從緩存中的查詢(xún)操作,注意這里的 proxyClassCache 其實(shí)是一個(gè)WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一個(gè)緩存映射 map,它的主要特點(diǎn)是一個(gè)弱引用的 map,但是它內(nèi)部有一個(gè) SubKey ,這個(gè)子鍵卻是強(qiáng)引用的。
這里我們不用去追究這個(gè) proxyClassCache 是如何進(jìn)行緩存的,只需要知道它的緩存時(shí)機(jī)就可以了:即在類(lèi)加載的時(shí)候進(jìn)行緩存。
如果無(wú)法找到代理對(duì)象,就會(huì)通過(guò) ProxyClassFactory 創(chuàng)建代理,ProxyClassFactory 繼承于 BiFunction
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{...}
ProxyClassFactory 里面有兩個(gè)屬性一個(gè)方法。
proxyClassNamePrefix:這個(gè)屬性表明使用 ProxyClassFactory 創(chuàng)建出來(lái)的代理實(shí)例的命名是以 "$Proxy" 為前綴的。
nextUniqueNumber:這個(gè)屬性表明 ProxyClassFactory 的后綴是使用 AtomicLong 生成的數(shù)字
所以代理實(shí)例的命名一般是 、Proxy1這種。
這個(gè) apply 方法是一個(gè)根據(jù)接口和類(lèi)加載器進(jìn)行代理實(shí)例創(chuàng)建的工廠方法,下面是這段代碼的核心。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
可以看到,代理實(shí)例的命名就是我們上面所描述的那種命名方式,只不過(guò)它這里加上了 proxyPkg 包名的路徑。然后下面就是生成代理實(shí)例的關(guān)鍵代碼。
ProxyGenerator.generateProxyClass 我們跟進(jìn)去是只能看到 .class 文件的,class 文件是虛擬機(jī)編譯之后的結(jié)果,所以我們要看一下 .java 文件源碼。.java 源碼位于 OpenJDK中的 sun.misc 包中的 ProxyGenerator 下。
此類(lèi)的 generateProxyClass() 靜態(tài)方法的核心內(nèi)容就是去調(diào)用 generateClassFile() 實(shí)例方法來(lái)生成 Class 文件。方法太長(zhǎng)了我們不貼了,這里就大致解釋以下其作用:
- 第一步:收集所有要生成的代理方法,將其包裝成 ProxyMethod 對(duì)象并注冊(cè)到 Map 集合中。
- 第二步:收集所有要為 Class 文件生成的字段信息和方法信息。
- 第三步:完成了上面的工作后,開(kāi)始組裝 Class 文件。
而 defineClass0 這個(gè)方法我們點(diǎn)進(jìn)去是 native ,底層是 C/C++ 實(shí)現(xiàn)的,于是我又去看了一下 C/C++ 源碼,路徑在
點(diǎn)開(kāi)之后的 C/C++ 源碼還是挺讓人絕望的。
不過(guò)我們?cè)倩仡^看一下這個(gè) defineClass0 方法,它實(shí)際上就是根據(jù)上面生成的 proxyClassFile 字節(jié)數(shù)組來(lái)生成對(duì)應(yīng)的實(shí)例罷了,所以我們不必再深究 C/C++ 對(duì)于代理對(duì)象的合成過(guò)程了。
所以總結(jié)一下可以看出,JDK 為我們的生成了一個(gè)叫 $Proxy0 的代理類(lèi),這個(gè)類(lèi)文件放在內(nèi)存中的,我們?cè)趧?chuàng)建代理對(duì)象時(shí),就是通過(guò)反射獲得這個(gè)類(lèi)的構(gòu)造方法,然后創(chuàng)建的代理實(shí)例。
所以最開(kāi)始的 dynamicProxy 方法我們反編譯后的代碼就是這樣的
public final class $Proxy0 extends java.lang.reflect.Proxy implements com.cxuan.dynamic.UserDao {
public $Proxy0(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
我們看到代理類(lèi)繼承了 Proxy 類(lèi),所以也就決定了 Java 動(dòng)態(tài)代理只能對(duì)接口進(jìn)行代理。
于是,上面這個(gè)圖你應(yīng)該就可以看懂了。
invoke 方法中第一個(gè)參數(shù) proxy 的作用
細(xì)心的小伙伴們可能都發(fā)現(xiàn)了,invoke 方法中第一個(gè) proxy 的作用是啥?代碼里面好像 proxy 也沒(méi)用到啊,這個(gè)參數(shù)的意義是啥呢?它運(yùn)行時(shí)的類(lèi)型是啥啊?為什么不使用 this 代替呢?
Stackoverflow 給出了我們一個(gè)回答 https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca
什么意思呢?
就是說(shuō)這個(gè) proxy ,它是真正的代理對(duì)象,invoke 方法可以返回調(diào)用代理對(duì)象方法的返回結(jié)果,也可以返回對(duì)象的真實(shí)代理對(duì)象,也就是 $Proxy0,這也是它運(yùn)行時(shí)的類(lèi)型。
至于為什么不用 this 來(lái)代替 proxy,因?yàn)閷?shí)現(xiàn)了 InvocationHandler 的對(duì)象中的 this ,指代的還是 InvocationHandler 接口實(shí)現(xiàn)類(lèi)本身,而不是真實(shí)的代理對(duì)象。
后記
另外,這段時(shí)間公眾號(hào)出了一些狀況,大家在公眾號(hào)回復(fù)的一些關(guān)鍵詞都沒(méi)有對(duì)應(yīng)的連接了,這里和大家說(shuō)明抱歉。